Add BandwidthService and endpoint for retrieving bandwidth statistics
This commit is contained in:
124
lib/services/bandwidth_service.dart
Normal file
124
lib/services/bandwidth_service.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import 'package:waylume_server/wireguard/peers.dart';
|
|||||||
import 'package:waylume_server/wireguard/utils.dart';
|
import 'package:waylume_server/wireguard/utils.dart';
|
||||||
import 'package:waylume_server/core/utils.dart';
|
import 'package:waylume_server/core/utils.dart';
|
||||||
import 'package:waylume_server/services/vpn_session_service.dart';
|
import 'package:waylume_server/services/vpn_session_service.dart';
|
||||||
|
import 'package:waylume_server/services/bandwidth_service.dart';
|
||||||
|
|
||||||
class PeerRoutes {
|
class PeerRoutes {
|
||||||
Router get router {
|
Router get router {
|
||||||
@@ -17,6 +18,7 @@ class PeerRoutes {
|
|||||||
router.get('/peer/<publicKey>/config', _getPeerConfig);
|
router.get('/peer/<publicKey>/config', _getPeerConfig);
|
||||||
router.patch('/peer/<publicKey>/speed-limit', _setSpeedLimit);
|
router.patch('/peer/<publicKey>/speed-limit', _setSpeedLimit);
|
||||||
router.patch('/peer/<publicKey>/data-cap', _setDataCap);
|
router.patch('/peer/<publicKey>/data-cap', _setDataCap);
|
||||||
|
router.get('/bandwidth-stats', _getBandwidthStats);
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -251,4 +253,28 @@ class PeerRoutes {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Response> _getBandwidthStats(Request request) async {
|
||||||
|
try {
|
||||||
|
final collectorId = request.url.queryParameters['collector_id'];
|
||||||
|
|
||||||
|
final bandwidthData = await BandwidthService.getBandwidthStats(collectorId);
|
||||||
|
|
||||||
|
return Response.ok(
|
||||||
|
jsonEncode({
|
||||||
|
'success': true,
|
||||||
|
'data': bandwidthData,
|
||||||
|
}),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return Response.internalServerError(
|
||||||
|
body: jsonEncode({
|
||||||
|
'success': false,
|
||||||
|
'error': e.toString(),
|
||||||
|
}),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user