v2rayN Statistics Service Architecture: How Bandwidth Tracking Works
v2rayN tracks bandwidth by polling Xray's HTTP /debug/vars endpoint or Sing-Box's WebSocket /traffic endpoint every second, calculating traffic deltas in core-specific services, and aggregating them into daily and total counters via a singleton StatisticsManager that persists data to a local SQLite database.
The v2rayN client (from the 2dust/v2rayN repository) implements a modular statistics architecture that captures real-time bandwidth metrics from the underlying proxy core. This system decouples data collection from the UI by using core-specific polling services that feed into a centralized manager, enabling accurate per-server traffic accounting without blocking the main application thread.
Core Components of the Statistics Architecture
StatisticsManager (Singleton Coordinator)
Located in ServiceLib/Manager/StatisticsManager.cs, this singleton owns the in-memory list of ServerStatItem objects and coordinates the lifecycle of core-specific services. It handles initialization, aggregates raw counters into daily and total traffic statistics, and manages SQLite persistence. The manager exposes a delegate-based callback (UpdateServerStatHandler) that services invoke to push delta updates.
StatisticsXrayService (HTTP Polling)
The StatisticsXrayService class in ServiceLib/Services/Statistics/StatisticsXrayService.cs handles Xray cores. It polls the http://127.0.0.1:<StatePort>/debug/vars endpoint every second, parsing the V2rayMetricsVars JSON response to extract stats.outbound metrics. For each proxy tag, it reads uplink and downlink byte counters, aggregates proxy and direct traffic separately, and computes deltas by comparing against the previous snapshot.
StatisticsSingboxService (WebSocket Streaming)
For Sing-Box cores, StatisticsSingboxService in ServiceLib/Services/Statistics/StatisticsSingboxService.cs opens a persistent WebSocket connection to ws://127.0.0.1:<StatePort2>/traffic. It receives TrafficItem JSON messages containing up and down byte values, converts these to ServerSpeedItem objects, and forwards them to the manager. Unlike the HTTP polling approach, this maintains an open connection for real-time streaming.
Data Flow: From Proxy Core to UI
Polling the Core Endpoints
Both services run background loops via Task.Run(Run). The Xray service executes an HTTP GET request to /debug/vars every 1000ms, while the Sing-Box service maintains a WebSocket to /traffic. These endpoints expose raw byte counters from the proxy core's internal statistics.
Delta Calculation and Normalization
After receiving raw metrics, services compute traffic deltas by subtracting the previous snapshot from the current one. In StatisticsXrayService, this produces a ServerSpeedItem containing ProxyUp, ProxyDown, DirectUp, and DirectDown values representing bytes transferred since the last poll.
ServerSpeedItem curItem = new()
{
ProxyUp = server.ProxyUp - _serverSpeedItem.ProxyUp,
ProxyDown = server.ProxyDown - _serverSpeedItem.ProxyDown,
DirectUp = server.DirectUp - _serverSpeedItem.DirectUp,
DirectDown= server.DirectDown- _serverSpeedItem.DirectDown,
};
Aggregation in StatisticsManager
The manager's UpdateServerStat method receives the delta ServerSpeedItem. It loads the corresponding ServerStatItem for the active server (_config.IndexId) and adds the delta values to both daily counters (TodayUp, TodayDown) and total counters (TotalUp, TotalDown). The updated cumulative values are then published to the UI layer.
UI Updates via Event Dispatch
The manager invokes _updateFunc (the delegate passed during initialization), which routes to MainWindowViewModel.UpdateStatisticsHandler. This publishes an AppEvents.DispatcherStatisticsRequested event that StatusBarViewModel and other UI components subscribe to, enabling real-time display of upload/download speeds and cumulative traffic.
// In a ViewModel (e.g., StatusBarViewModel)
AppEvents.DispatcherStatisticsRequested
.AsObservable()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(stat =>
{
UploadSpeed = $"{stat.ProxyUp / 1024:F1} KiB/s";
DownloadSpeed = $"{stat.ProxyDown / 1024:F1} KiB/s";
});
Data Persistence and Lifecycle Management
SQLite Storage Schema
Statistics persist to a local SQLite database via the ServerStatItem entity defined in ServiceLib/Models/ServerStatItem.cs. Each record tracks TodayUp, TodayDown, TotalUp, TotalDown, and the associated server ID. The manager maintains an in-memory list (_lstServerStat) synchronized with the database.
Daily Rollover and Cleanup
During InitData(), the manager checks timestamps and resets daily counters when the date changes. It also removes stale server entries that no longer exist in the configuration, ensuring the database doesn't accumulate orphaned records.
Graceful Shutdown
AppManager calls StatisticsManager.Instance.SaveTo() during application shutdown, flushing the in-memory statistics to SQLite. The Close() method signals background services to exit by setting _exitFlag and disposes network resources (HTTP clients, WebSockets) to prevent resource leaks.
Enabling and Configuring Statistics
Users control statistics collection via the Enable Statistics toggle in Options → Settings. This sets Config.GuiItem.EnableStatistics to true. When modified, OptionSettingViewModel triggers a core restart, causing MainWindowViewModel to re-initialize StatisticsManager with the updated configuration. If disabled, the manager skips initialization and no polling occurs.
// Enabling via configuration
cfg.GuiItem.EnableStatistics = true;
await ConfigHandler.SaveConfig(cfg);
await StatisticsManager.Instance.Init(cfg, async s => { /* UI handler */ });
Summary
- v2rayN statistics architecture uses a singleton
StatisticsManagerto coordinate data collection from Xray and Sing-Box cores. - Core-specific services handle transport differences: HTTP polling for Xray (
/debug/vars) and WebSocket streaming for Sing-Box (/traffic). - Delta calculation occurs in the services before forwarding to the manager, which aggregates traffic into daily and total counters.
- SQLite persistence stores cumulative statistics per server, with automatic daily rollover and cleanup of stale entries.
- Event-driven UI updates use
DispatcherStatisticsRequestedto broadcast real-time speed and traffic data to view models.
Frequently Asked Questions
How does v2rayN calculate real-time upload and download speeds?
v2rayN calculates speeds by polling the proxy core every second and computing the delta between consecutive measurements. The StatisticsXrayService and StatisticsSingboxService store the previous byte counters, subtract them from the current values, and forward the difference to StatisticsManager as bytes-per-second values.
Where does v2rayN store bandwidth statistics?
Bandwidth statistics persist to a local SQLite database via the ServerStatItem entity. The StatisticsManager maintains an in-memory list synchronized with the database, flushing data during graceful shutdown via SaveTo() and reloading during initialization via InitData().
Can I use statistics with both Xray and Sing-Box cores simultaneously?
The architecture supports both cores through separate service implementations, but only one is active at a time depending on which core is running. StatisticsManager instantiates both StatisticsXrayService and StatisticsSingboxService during initialization, but only the service matching the active core collects data while the other remains idle.
How do I reset or clear statistics data?
Invoke StatisticsManager.Instance.ClearAllServerStatistics() to delete all rows from the in-memory collection and database, then call SaveTo() to persist the empty state. This is typically triggered from the UI's "Clear Statistics" menu command, which resets both daily and total counters for all servers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →