mirror of
https://github.com/permissionlesstech/georelays.git
synced 2026-07-24 23:25:18 +00:00
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
This commit is contained in:
Executable
+233
@@ -0,0 +1,233 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user