Files
georelays/scripts/track_relay_counts.py
lollerfirstandGitHub 760efc97b1 add automated relay count tracking with dual trend charts (#8)
* Add relay count tracking workflow with dual charts

This commit adds:
- New GitHub workflow to track relay counts
- Two charts: BitChat-compatible relays and total functioning relays
- Extended history for both charts (70 days)
- Project structure improvements with scripts and assets directories
- Updated documentation in README.md and other files
- Dependencies in requirements.txt

* Remove overlapping data point labels from relay count charts

* Add .venv to gitignore

* Add relay location maps and automation workflow

* Improve relay maps with proper world map backgrounds using cartopy

* Add relay location map to README

* update readme
2025-11-01 22:19:32 +01:00

234 lines
7.5 KiB
Python
Executable File

#!/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()