#!/usr/bin/env python3 """ Track Relay Count Changes This script analyzes the git history of relay data files to track how the number of relay entries changes over time. It generates charts showing the trend over the last 70 commits that modified the files (approximately 70 days). Two charts are generated: 1. BitChat Relay Count - from nostr_relays.csv (relays supporting kind 20000) 2. Total Relay Count - from relay_discovery_results.json (all functioning relays) """ import os import subprocess import re import json import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates from datetime import datetime import numpy as np # Function to get the count of BitChat relays in a specific commit def get_relay_count(commit_hash): """ Get the count of BitChat relay entries in the CSV file at a specific commit. Args: commit_hash: Git commit hash Returns: int: Number of relay entries (excluding header) """ try: # Get the file content at this commit result = subprocess.run( ['git', 'show', f'{commit_hash}:nostr_relays.csv'], capture_output=True, text=True, check=True ) # Count lines excluding header lines = result.stdout.strip().split('\n') # Subtract 1 for the header row return len(lines) - 1 if lines else 0 except subprocess.CalledProcessError: # File might not exist in this commit return 0 # Function to get the count of total functioning relays in a specific commit def get_total_relay_count(commit_hash): """ Get the count of total functioning relays from the JSON file at a specific commit. Args: commit_hash: Git commit hash Returns: int: Number of functioning relays """ try: # Get the file content at this commit result = subprocess.run( ['git', 'show', f'{commit_hash}:relay_discovery_results.json'], capture_output=True, text=True, check=True ) # Parse JSON and get the count from functioning_relays array data = json.loads(result.stdout) return len(data.get('functioning_relays', [])) except (subprocess.CalledProcessError, json.JSONDecodeError, KeyError) as e: print(f"Error getting total relay count from commit {commit_hash}: {e}") return 0 # Function to extract date from commit message def extract_date_from_commit(commit_hash): """ Extract the date from a commit. Args: commit_hash: Git commit hash Returns: datetime: Commit date """ try: result = subprocess.run( ['git', 'show', '-s', '--format=%ci', commit_hash], capture_output=True, text=True, check=True ) commit_date = result.stdout.strip() return datetime.strptime(commit_date, '%Y-%m-%d %H:%M:%S %z') except Exception as e: print(f"Error extracting date from commit {commit_hash}: {e}") return None def create_plot(data_frame, title, y_label, output_path): """ Create and save a plot from the given data. Args: data_frame: DataFrame containing 'date' and 'count' columns title: Title for the plot y_label: Label for Y-axis output_path: Path to save the plot """ if data_frame.empty: print(f"No data found to generate {title} chart") return plt.figure(figsize=(12, 6)) plt.plot(data_frame['date'], data_frame['count'], marker='o', linestyle='-', linewidth=2) # Add title and labels plt.title(title, fontsize=16) plt.xlabel('Date', fontsize=12) plt.ylabel(y_label, fontsize=12) # Format the x-axis to show dates nicely plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) plt.gca().xaxis.set_major_locator(mdates.AutoDateLocator()) # Add grid and rotate date labels plt.grid(True, linestyle='--', alpha=0.7) plt.xticks(rotation=45) # Add current count in the corner if not data_frame.empty: latest_count = data_frame['count'].iloc[-1] plt.annotate( f"Latest Count: {latest_count}", xy=(0.02, 0.96), xycoords='axes fraction', fontsize=12, bbox=dict(boxstyle="round,pad=0.3", fc="white", alpha=0.8) ) # Adjust layout and save plt.tight_layout() # Save the chart plt.savefig(output_path, dpi=300) # Close the figure to prevent memory leaks plt.close() # Return summary return len(data_frame), latest_count if not data_frame.empty else 'N/A' def main(): """ Main function to track relay count changes and generate charts. """ # Change to the root directory of the project if necessary script_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.dirname(script_dir) os.chdir(root_dir) # Ensure the assets directory exists os.makedirs('assets', exist_ok=True) # Get commits that modified the nostr_relays.csv file bitchat_result = subprocess.run( ['git', 'log', '--format=%H', '-n', '70', '--', 'nostr_relays.csv'], capture_output=True, text=True ) bitchat_commit_hashes = bitchat_result.stdout.strip().split('\n') # Get commits that modified the relay_discovery_results.json file total_result = subprocess.run( ['git', 'log', '--format=%H', '-n', '70', '--', 'relay_discovery_results.json'], capture_output=True, text=True ) total_commit_hashes = total_result.stdout.strip().split('\n') # Collect BitChat relay data bitchat_data = [] for commit_hash in bitchat_commit_hashes: if not commit_hash: continue date = extract_date_from_commit(commit_hash) count = get_relay_count(commit_hash) if date and count > 0: # Only add valid entries bitchat_data.append({ 'date': date, 'commit': commit_hash, 'count': count }) # Collect total relay data total_data = [] for commit_hash in total_commit_hashes: if not commit_hash: continue date = extract_date_from_commit(commit_hash) count = get_total_relay_count(commit_hash) if date and count > 0: # Only add valid entries total_data.append({ 'date': date, 'commit': commit_hash, 'count': count }) # Create DataFrames and sort by date bitchat_df = pd.DataFrame(bitchat_data).sort_values('date') if bitchat_data else pd.DataFrame() total_df = pd.DataFrame(total_data).sort_values('date') if total_data else pd.DataFrame() # Create and save plots bitchat_stats = create_plot( bitchat_df, 'BitChat-Compatible Relay Count Over Time', 'Number of BitChat Relays', 'assets/bitchat_relay_count_chart.png' ) total_stats = create_plot( total_df, 'Total Functioning Relay Count Over Time', 'Number of Functioning Relays', 'assets/total_relay_count_chart.png' ) # Print summary for log if bitchat_stats: print(f"Generated BitChat relay chart with {bitchat_stats[0]} data points") print(f"Latest BitChat relay count: {bitchat_stats[1]}") if total_stats: print(f"Generated total relay chart with {total_stats[0]} data points") print(f"Latest total relay count: {total_stats[1]}") if __name__ == "__main__": main()