Add BandwidthService and endpoint for retrieving bandwidth statistics

This commit is contained in:
ImBenji
2025-08-18 14:12:30 +01:00
parent ebfc01d927
commit f829bd5fe1
2 changed files with 150 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
import 'dart:io';
import 'dart:convert';
class BandwidthService {
// In-memory collector state: collector_id -> peer_public_key -> {upload_last_seen, download_last_seen}
static final Map<String, Map<String, Map<String, int>>> _collectorStates = {};
/// Gets bandwidth stats for a collector
/// If collector_id is provided, returns incremental usage since last call
/// If no collector_id, returns total cumulative usage from WireGuard
static Future<Map<String, Map<String, int>>> getBandwidthStats(String? collectorId) async {
final currentStats = await _getCurrentWireGuardStats();
if (collectorId == null || collectorId.isEmpty) {
// No collector ID - return cumulative totals
return currentStats;
}
// Collector ID provided - calculate increments
return _calculateIncrementalStats(collectorId, currentStats);
}
/// Gets current WireGuard traffic statistics using 'wg show wg0 dump'
static Future<Map<String, Map<String, int>>> _getCurrentWireGuardStats() async {
try {
final result = await Process.run('wg', ['show', 'wg0', 'dump']);
if (result.exitCode != 0) {
throw Exception('Failed to get WireGuard stats: ${result.stderr}');
}
final output = result.stdout.toString().trim();
final lines = output.split('\n');
final stats = <String, Map<String, int>>{};
// Skip first line (server info) and process peer lines
for (int i = 1; i < lines.length; i++) {
final line = lines[i].trim();
if (line.isEmpty) continue;
final parts = line.split('\t');
if (parts.length >= 7) {
final publicKey = parts[0];
final uploadBytes = int.tryParse(parts[5]) ?? 0;
final downloadBytes = int.tryParse(parts[6]) ?? 0;
stats[publicKey] = {
'upload_bytes': uploadBytes,
'download_bytes': downloadBytes,
};
}
}
return stats;
} catch (e) {
print('Error getting WireGuard stats: $e');
return {};
}
}
/// Calculates incremental stats for a collector
static Map<String, Map<String, int>> _calculateIncrementalStats(
String collectorId,
Map<String, Map<String, int>> currentStats
) {
// Initialize collector state if it doesn't exist
_collectorStates[collectorId] ??= {};
final collectorState = _collectorStates[collectorId]!;
final incrementalStats = <String, Map<String, int>>{};
for (final entry in currentStats.entries) {
final publicKey = entry.key;
final currentUpload = entry.value['upload_bytes'] ?? 0;
final currentDownload = entry.value['download_bytes'] ?? 0;
// Get last seen values for this collector and peer
final lastSeen = collectorState[publicKey] ?? {'upload_bytes': 0, 'download_bytes': 0};
final lastSeenUpload = lastSeen['upload_bytes'] ?? 0;
final lastSeenDownload = lastSeen['download_bytes'] ?? 0;
// Calculate increments (handle potential WireGuard resets)
final uploadIncrement = _calculateSafeIncrement(currentUpload, lastSeenUpload);
final downloadIncrement = _calculateSafeIncrement(currentDownload, lastSeenDownload);
// Only include peers with non-zero usage (optimization)
if (uploadIncrement > 0 || downloadIncrement > 0) {
incrementalStats[publicKey] = {
'upload_bytes': uploadIncrement,
'download_bytes': downloadIncrement,
};
}
// Update collector state with current values
collectorState[publicKey] = {
'upload_bytes': currentUpload,
'download_bytes': currentDownload,
};
}
return incrementalStats;
}
/// Safely calculates increment, handling WireGuard resets
static int _calculateSafeIncrement(int current, int lastSeen) {
if (current >= lastSeen) {
return current - lastSeen;
} else {
// WireGuard likely reset (server restart) - use current value as increment
print('WireGuard reset detected: current=$current < lastSeen=$lastSeen');
return current;
}
}
/// Clears collector state (for debugging/maintenance)
static void clearCollectorState(String collectorId) {
_collectorStates.remove(collectorId);
}
/// Gets current collector state (for debugging)
static Map<String, Map<String, Map<String, int>>> getCollectorStates() {
return Map.from(_collectorStates);
}
}