ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
stats.c File Reference

Go to the source code of this file.

Data Structures

struct  stats_duration_cell_t
struct  stats_duration_shard_t
 For stats duration cell, this is the live record that ties sequence, observations, total_ns, min_ns together. The owning code creates it, updates it while work is active, and releases its handles and pointed-to storage before freeing the record. More...
struct  stats_scope
 stats duration shard keeps the values that move through the subsystem: owner. Some members are pointers into caller- or subsystem-owned memory, so lifetime follows the API that populated them rather than the struct copy itself. More...
struct  stats_sampler
 This is the handoff point for stats scope. Callers fill observer, observer_context, id, started_ns before passing it on; consumers read the values during the operation and return ownership only where the member contract explicitly says they do. More...

Macros

#define STATS_DURATION_SHARDS   16

Functions

asciichat_error_t stats_scope_create (const stats_capabilities_t *capabilities, stats_scope_t **out_scope)
void stats_scope_destroy (stats_scope_t *scope)
void stats_scope_set_observer (stats_scope_t *scope, stats_observer_fn observer, void *context)
void stats_counter_add (stats_scope_t *scope, stats_counter_id_t id, uint64_t amount)
void stats_gauge_set (stats_scope_t *scope, stats_gauge_id_t id, uint64_t value)
void stats_duration_record (stats_scope_t *scope, stats_duration_id_t id, uint64_t elapsed_ns)
asciichat_error_t stats_scope_snapshot (const stats_scope_t *scope, stats_snapshot_t *out)
 stats sampler config keeps the values that move through the subsystem: window_ns, resolution_ns. Some members are pointers into caller- or subsystem-owned memory, so lifetime follows the API that populated them rather than the struct copy itself.
asciichat_error_t stats_sampler_create (const stats_sampler_config_t *config, stats_sampler_t **out_sampler)
asciichat_error_t stats_sampler_update (stats_sampler_t *sampler, const stats_snapshot_t *snapshot, stats_rates_t *out_rates)
void stats_sampler_destroy (stats_sampler_t *sampler)

Macro Definition Documentation

◆ STATS_DURATION_SHARDS

#define STATS_DURATION_SHARDS   16

Definition at line 12 of file lib/stats/stats.c.

Referenced by stats_scope_snapshot().

Function Documentation

◆ stats_counter_add()

void stats_counter_add ( stats_scope_t * scope,
stats_counter_id_t id,
uint64_t amount )

Thread-safe recording with no module allocations. Platform mutex diagnostics may initialize per-thread storage on first use in debug builds. NULL, invalid IDs, and unsupported metrics are ignored. Counters and duration totals wrap at UINT64_MAX; samplers rebaseline on observed decreases. Durations measure attempts (including failures); successful operations are counted separately. A gauge's producer owns its current value.

Definition at line 112 of file lib/stats/stats.c.

112 {
113 if (scope && (unsigned)id < STATS_COUNTER_COUNT && scope->capabilities.counters[id]) {
114 atomic_fetch_add_u64_impl(&scope->counters[id], amount);
115 if (scope->observer)
116 scope->observer(STATS_RECORD_COUNTER, id, amount, scope->observer_context);
117 }
118}
atomic_t counters[STATS_COUNTER_COUNT]
stats_observer_fn observer
void * observer_context

References stats_scope::counters, stats_scope::observer, stats_scope::observer_context, and STATS_RECORD_COUNTER.

Referenced by acip_handle_acds_packet(), acip_send_session_joined(), client_video_render_thread(), database_session_cleanup_expired(), database_session_update_host(), h265_decode(), h265_encode(), h265_encoder_flush(), http_request_cancel(), http_response_finish(), http_server_authorize(), http_server_dispatch_http3(), mcp_dispatch(), mcp_error(), packet_decode_frame_data_malloc(), packet_decode_frame_data_workspace(), rate_limiter_check(), session_display_encode_frame(), stats_runtime_packet(), and video_frame_commit().

◆ stats_duration_record()

void stats_duration_record ( stats_scope_t * scope,
stats_duration_id_t id,
uint64_t elapsed_ns )

Definition at line 151 of file lib/stats/stats.c.

151 {
152 if (!scope || (unsigned)id >= STATS_DURATION_COUNT || !scope->capabilities.durations[id])
153 return;
154 stats_duration_shard_t *shard = duration_shard(scope);
155 if (shard) {
156 stats_duration_cell_t *cell = &shard->durations[id];
157 uint64_t sequence = atomic_load(&cell->sequence);
158 atomic_store(&cell->sequence, duration_add(sequence, 1));
159 uint64_t observations = atomic_load(&cell->observations);
160 if (!observations || elapsed_ns < atomic_load(&cell->min_ns))
161 atomic_store(&cell->min_ns, elapsed_ns);
162 if (elapsed_ns > atomic_load(&cell->max_ns))
163 atomic_store(&cell->max_ns, elapsed_ns);
164 atomic_store(&cell->last_ns, elapsed_ns);
165 atomic_store(&cell->recorded_ns, time_get_ns());
166 atomic_store(&cell->observations, duration_add(observations, 1));
167 atomic_store(&cell->total_ns, duration_add(atomic_load(&cell->total_ns), elapsed_ns));
168 atomic_store(&cell->sequence, duration_add(sequence, 2));
169 if (scope->observer)
170 scope->observer(STATS_RECORD_DURATION, id, elapsed_ns, scope->observer_context);
171 return;
172 }
173 // Unusually many lifetime producers retain the bounded, coherent fallback.
174 if (mutex_lock(&scope->duration_mutex) != 0)
175 return;
176 stats_duration_snapshot_t *duration = &scope->durations[id];
177 duration->last_ns = elapsed_ns;
178 scope->duration_recorded_ns[id] = time_get_ns();
179 if (!duration->observations || elapsed_ns < duration->min_ns)
180 duration->min_ns = elapsed_ns;
181 if (elapsed_ns > duration->max_ns)
182 duration->max_ns = elapsed_ns;
183 duration->observations = duration->observations == UINT64_MAX ? 0 : duration->observations + 1;
184 // Unsigned wrap is intentional; an observed decrease causes samplers to rebaseline.
185 duration->total_ns = duration->total_ns > UINT64_MAX - elapsed_ns ? elapsed_ns - (UINT64_MAX - duration->total_ns) - 1
186 : duration->total_ns + elapsed_ns;
187 mutex_unlock(&scope->duration_mutex);
188 if (scope->observer)
189 scope->observer(STATS_RECORD_DURATION, id, elapsed_ns, scope->observer_context);
190}
unsigned long long uint64_t
Definition common.h:63
bool durations[STATS_DURATION_COUNT]
_Atomic uint64_t min_ns
_Atomic uint64_t recorded_ns
_Atomic uint64_t max_ns
_Atomic uint64_t last_ns
_Atomic uint64_t observations
_Atomic uint64_t total_ns
_Atomic uint64_t sequence
For stats duration cell, this is the live record that ties sequence, observations,...
stats_duration_cell_t durations[STATS_DURATION_COUNT]
mutex_t duration_mutex
stats_capabilities_t capabilities
stats_duration_snapshot_t durations[STATS_DURATION_COUNT]
uint64_t duration_recorded_ns[STATS_DURATION_COUNT]

References stats_scope::capabilities, stats_scope::duration_mutex, stats_scope::duration_recorded_ns, stats_capabilities_t::durations, stats_duration_shard_t::durations, stats_scope::durations, stats_duration_cell_t::last_ns, stats_duration_snapshot_t::last_ns, stats_duration_cell_t::max_ns, stats_duration_snapshot_t::max_ns, stats_duration_cell_t::min_ns, stats_duration_snapshot_t::min_ns, stats_duration_cell_t::observations, stats_duration_snapshot_t::observations, stats_scope::observer, stats_scope::observer_context, stats_duration_cell_t::recorded_ns, stats_duration_cell_t::sequence, STATS_DURATION_COUNT, STATS_RECORD_DURATION, stats_duration_cell_t::total_ns, and stats_duration_snapshot_t::total_ns.

Referenced by acip_handle_acds_packet(), client_audio_render_thread(), client_video_render_thread(), h265_decode(), h265_encode(), h265_encoder_flush(), http_server_dispatch_http3(), packet_decode_frame_data_malloc(), packet_decode_frame_data_workspace(), server_connection_pong_received(), session_display_encode_frame(), and session_host_render_audio().

◆ stats_gauge_set()

void stats_gauge_set ( stats_scope_t * scope,
stats_gauge_id_t id,
uint64_t value )

Definition at line 120 of file lib/stats/stats.c.

120 {
121 if (scope && (unsigned)id < STATS_GAUGE_COUNT && scope->capabilities.gauges[id]) {
122 atomic_store_u64_impl(&scope->gauges[id], value);
123 if (scope->observer)
124 scope->observer(STATS_RECORD_GAUGE, id, value, scope->observer_context);
125 }
126}
atomic_t gauges[STATS_GAUGE_COUNT]

References stats_scope::gauges, stats_scope::observer, stats_scope::observer_context, and STATS_RECORD_GAUGE.

Referenced by database_stats_snapshot(), http_server_dispatch_http3(), stats_runtime_peer_close(), and stats_runtime_peer_open().

◆ stats_sampler_create()

asciichat_error_t stats_sampler_create ( const stats_sampler_config_t * config,
stats_sampler_t ** out_sampler )

Bounded history: ceil(window/resolution) must be in [1, 4096]. Both durations must be nonzero and resolution <= window. Each consumer owns its own sampler; sampler operations are not thread-safe. All allocation happens at creation.

Definition at line 255 of file lib/stats/stats.c.

255 {
256 if (!out_sampler)
257 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing stats sampler output");
258 *out_sampler = NULL;
259 if (!config || !config->resolution_ns || config->window_ns < config->resolution_ns)
260 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid stats sampling interval");
261 uint64_t slots = config->window_ns / config->resolution_ns + (config->window_ns % config->resolution_ns != 0);
262 if (slots > 4096)
263 return SET_ERRNO(ERROR_INVALID_PARAM, "Stats sampler exceeds 4096 history intervals");
264 stats_sampler_t *sampler = SAFE_CALLOC(1, sizeof(*sampler), stats_sampler_t *);
265 if (!sampler)
266 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate stats sampler");
267 sampler->capacity = (size_t)slots + 2;
268 sampler->history = SAFE_CALLOC(sampler->capacity, sizeof(*sampler->history), stats_snapshot_t *);
269 if (!sampler->history) {
270 SAFE_FREE(sampler);
271 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate stats history");
272 }
273 sampler->config = *config;
274 *out_sampler = sampler;
275 return ASCIICHAT_OK;
276}
#define SET_ERRNO(code,...)
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_CALLOC(count, size, cast)
Definition common.h:275
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
struct stats_sampler stats_sampler_t
stats_sampler_config_t config
stats_snapshot_t * history

References ASCIICHAT_OK, stats_sampler::capacity, stats_sampler::config, ERROR_INVALID_PARAM, ERROR_MEMORY, stats_sampler::history, stats_sampler_config_t::resolution_ns, SAFE_CALLOC, SAFE_FREE, SET_ERRNO, and stats_sampler_config_t::window_ns.

Referenced by stats_runtime_peer_open(), and stats_runtime_start().

◆ stats_sampler_destroy()

void stats_sampler_destroy ( stats_sampler_t * sampler)

Definition at line 344 of file lib/stats/stats.c.

344 {
345 if (!sampler)
346 return;
347 SAFE_FREE(sampler->history);
348 SAFE_FREE(sampler);
349}

References stats_sampler::history, and SAFE_FREE.

Referenced by stats_runtime_peer_close(), stats_runtime_start(), and stats_runtime_stop().

◆ stats_sampler_update()

asciichat_error_t stats_sampler_update ( stats_sampler_t * sampler,
const stats_snapshot_t * snapshot,
stats_rates_t * out_rates )

Samples must be supplied regularly, even while idle. Rates use actual elapsed time from the latest retained sample at/before the window boundary (or the oldest available sample during warmup). Resolution controls history retention, not calls. ready becomes true with a positive interval; elapsed_ns exposes window warmup. Scope/capability changes, nonincreasing timestamps, or decreasing totals reset the baseline and return ready=false. Unsupported values are zero/invalid; consult the snapshot capabilities to distinguish unsupported counters from measured zero.

Definition at line 295 of file lib/stats/stats.c.

296 {
297 if (!sampler || !snapshot || !out_rates)
298 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing stats sampler, snapshot, or rates output");
299 memset(out_rates, 0, sizeof(*out_rates));
300 if (!sampler->count || stats_baseline_changed(&sampler->latest, snapshot)) {
301 sampler->head = 0;
302 sampler->count = 1;
303 sampler->history[0] = *snapshot;
304 sampler->latest = *snapshot;
305 return ASCIICHAT_OK;
306 }
307 uint64_t cutoff =
308 snapshot->sampled_ns > sampler->config.window_ns ? snapshot->sampled_ns - sampler->config.window_ns : 0;
309 while (sampler->count > 1) {
310 size_t next = (sampler->head + 1) % sampler->capacity;
311 if (sampler->history[next].sampled_ns > cutoff)
312 break;
313 sampler->head = next;
314 sampler->count--;
315 }
316 const stats_snapshot_t *baseline = &sampler->history[sampler->head];
317 out_rates->elapsed_ns = snapshot->sampled_ns - baseline->sampled_ns;
318 out_rates->ready = true;
319 for (size_t i = 0; i < STATS_COUNTER_COUNT; ++i)
320 if (snapshot->capabilities.counters[i])
321 out_rates->counters_per_second[i] =
322 (double)(snapshot->counters[i] - baseline->counters[i]) * 1e9 / (double)out_rates->elapsed_ns;
323 for (size_t i = 0; i < STATS_DURATION_COUNT; ++i) {
324 uint64_t observations = snapshot->durations[i].observations - baseline->durations[i].observations;
325 if (snapshot->capabilities.durations[i] && observations) {
326 out_rates->duration_valid[i] = true;
327 out_rates->duration_mean_ns[i] =
328 (double)(snapshot->durations[i].total_ns - baseline->durations[i].total_ns) / (double)observations;
329 }
330 }
331 size_t tail = (sampler->head + sampler->count - 1) % sampler->capacity;
332 if (snapshot->sampled_ns - sampler->history[tail].sampled_ns >= sampler->config.resolution_ns) {
333 size_t next = (tail + 1) % sampler->capacity;
334 sampler->history[next] = *snapshot;
335 if (sampler->count == sampler->capacity)
336 sampler->head = (sampler->head + 1) % sampler->capacity;
337 else
338 sampler->count++;
339 }
340 sampler->latest = *snapshot;
341 return ASCIICHAT_OK;
342}
bool counters[STATS_COUNTER_COUNT]
double duration_mean_ns[STATS_DURATION_COUNT]
bool duration_valid[STATS_DURATION_COUNT]
double counters_per_second[STATS_COUNTER_COUNT]
stats_snapshot_t latest
stats_capabilities_t capabilities
uint64_t counters[STATS_COUNTER_COUNT]
stats_duration_snapshot_t durations[STATS_DURATION_COUNT]

References ASCIICHAT_OK, stats_snapshot_t::capabilities, stats_sampler::capacity, stats_sampler::config, stats_sampler::count, stats_capabilities_t::counters, stats_snapshot_t::counters, stats_rates_t::counters_per_second, stats_rates_t::duration_mean_ns, stats_rates_t::duration_valid, stats_capabilities_t::durations, stats_snapshot_t::durations, stats_rates_t::elapsed_ns, ERROR_INVALID_PARAM, stats_sampler::head, stats_sampler::history, stats_sampler::latest, stats_duration_snapshot_t::observations, stats_rates_t::ready, stats_sampler_config_t::resolution_ns, stats_snapshot_t::sampled_ns, SET_ERRNO, STATS_COUNTER_COUNT, STATS_DURATION_COUNT, stats_duration_snapshot_t::total_ns, and stats_sampler_config_t::window_ns.

◆ stats_scope_create()

asciichat_error_t stats_scope_create ( const stats_capabilities_t * capabilities,
stats_scope_t ** out_scope )

Copies capabilities. Create before workers start; destroy after all users join. No reset or automatic parent aggregation. New sessions should get new scopes.

Definition at line 71 of file lib/stats/stats.c.

71 {
72 if (!out_scope)
73 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing stats scope output");
74 *out_scope = NULL;
75 if (!capabilities)
76 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing stats capabilities");
77 stats_scope_t *scope = SAFE_CALLOC(1, sizeof(*scope), stats_scope_t *);
78 if (!scope)
79 return SET_ERRNO(ERROR_MEMORY, "Cannot allocate stats scope");
80 if (mutex_init(&scope->snapshot_mutex, "stats_snapshot") != 0) {
81 SAFE_FREE(scope);
82 return SET_ERRNO(ERROR_THREAD, "Cannot initialize stats snapshot mutex");
83 }
84 if (mutex_init(&scope->duration_mutex, "stats_duration") != 0) {
85 mutex_destroy(&scope->snapshot_mutex);
86 SAFE_FREE(scope);
87 return SET_ERRNO(ERROR_THREAD, "Cannot initialize stats duration mutex");
88 }
89 scope->capabilities = *capabilities;
90 // Bypass debug atomic instrumentation so recording never recursively collects or allocates.
91 scope->id = atomic_fetch_add_u64_impl(&next_scope_id, 1) + 1;
92 scope->started_ns = time_get_ns();
93 *out_scope = scope;
94 return ASCIICHAT_OK;
95}
@ ERROR_THREAD
struct stats_scope stats_scope_t
mutex_t snapshot_mutex
uint64_t started_ns

References ASCIICHAT_OK, stats_scope::capabilities, stats_scope::duration_mutex, ERROR_INVALID_PARAM, ERROR_MEMORY, ERROR_THREAD, stats_scope::id, SAFE_CALLOC, SAFE_FREE, SET_ERRNO, stats_scope::snapshot_mutex, and stats_scope::started_ns.

Referenced by stats_runtime_peer_open(), and stats_runtime_start().

◆ stats_scope_destroy()

void stats_scope_destroy ( stats_scope_t * scope)

Definition at line 97 of file lib/stats/stats.c.

97 {
98 if (!scope)
99 return;
100 mutex_destroy(&scope->duration_mutex);
101 mutex_destroy(&scope->snapshot_mutex);
102 SAFE_FREE(scope);
103}

References stats_scope::duration_mutex, SAFE_FREE, and stats_scope::snapshot_mutex.

Referenced by stats_runtime_peer_close(), stats_runtime_peer_open(), stats_runtime_start(), and stats_runtime_stop().

◆ stats_scope_set_observer()

void stats_scope_set_observer ( stats_scope_t * scope,
stats_observer_fn observer,
void * context )

Definition at line 105 of file lib/stats/stats.c.

105 {
106 if (scope) {
107 scope->observer = observer;
108 scope->observer_context = context;
109 }
110}

References stats_scope::observer, and stats_scope::observer_context.

Referenced by ascii_otel_stats_attach().

◆ stats_scope_snapshot()

asciichat_error_t stats_scope_snapshot ( const stats_scope_t * scope,
stats_snapshot_t * out )

stats sampler config keeps the values that move through the subsystem: window_ns, resolution_ns. Some members are pointers into caller- or subsystem-owned memory, so lifetime follows the API that populated them rather than the struct copy itself.

Owned value copy. Concurrent snapshots are serialized. Each duration tuple is

stats rates is intentionally a small, concrete record. Fields such as ready, elapsed_ns give callers the identity and current value they need. It is normally borrowed from, or owned by, the surrounding subsystem—not independently managed. coherent, but independent metrics need not describe one atomic application event. Empty durations have observations/min/max/last/total equal to zero.

Definition at line 192 of file lib/stats/stats.c.

192 {
193 if (!scope || !out)
194 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing stats scope or snapshot output");
195 // Logical constness: collection is unchanged; only snapshot synchronization is mutable.
196 stats_scope_t *mutable_scope = (stats_scope_t *)scope;
197 if (mutex_lock(&mutable_scope->snapshot_mutex) != 0)
198 return SET_ERRNO(ERROR_THREAD, "Cannot lock stats snapshot");
199 memset(out, 0, sizeof(*out));
200 out->scope_id = scope->id;
201 out->started_ns = scope->started_ns;
202 out->capabilities = scope->capabilities;
203 for (size_t i = 0; i < STATS_COUNTER_COUNT; ++i)
204 out->counters[i] = atomic_load_u64_impl(&scope->counters[i]);
205 for (size_t i = 0; i < STATS_GAUGE_COUNT; ++i)
206 out->gauges[i] = atomic_load_u64_impl(&scope->gauges[i]);
207 if (mutex_lock(&mutable_scope->duration_mutex) != 0) {
208 mutex_unlock(&mutable_scope->snapshot_mutex);
209 return SET_ERRNO(ERROR_THREAD, "Cannot lock stats durations");
210 }
211 memcpy(out->durations, scope->durations, sizeof(out->durations));
212 uint64_t recorded_ns[STATS_DURATION_COUNT];
213 memcpy(recorded_ns, scope->duration_recorded_ns, sizeof(recorded_ns));
214 mutex_unlock(&mutable_scope->duration_mutex);
215 for (size_t i = 0; i < STATS_DURATION_SHARDS; ++i) {
216 if (!atomic_load(&scope->shards[i].owner))
217 continue;
218 for (size_t id = 0; id < STATS_DURATION_COUNT; ++id) {
219 const stats_duration_cell_t *cell = &scope->shards[i].durations[id];
221 uint64_t stamp, before;
222 for (;;) {
223 before = atomic_load(&cell->sequence);
224 if (before & 1)
225 continue;
226 sample.observations = atomic_load(&cell->observations);
227 sample.total_ns = atomic_load(&cell->total_ns);
228 sample.min_ns = atomic_load(&cell->min_ns);
229 sample.max_ns = atomic_load(&cell->max_ns);
230 sample.last_ns = atomic_load(&cell->last_ns);
231 stamp = atomic_load(&cell->recorded_ns);
232 if (before == atomic_load(&cell->sequence))
233 break;
234 }
235 if (!sample.observations)
236 continue;
237 stats_duration_snapshot_t *total = &out->durations[id];
238 if (!total->observations || sample.min_ns < total->min_ns)
239 total->min_ns = sample.min_ns;
240 if (sample.max_ns > total->max_ns)
241 total->max_ns = sample.max_ns;
242 if (stamp >= recorded_ns[id]) {
243 recorded_ns[id] = stamp;
244 total->last_ns = sample.last_ns;
245 }
246 total->observations = duration_add(total->observations, sample.observations);
247 total->total_ns = duration_add(total->total_ns, sample.total_ns);
248 }
249 }
250 out->sampled_ns = time_get_ns();
251 mutex_unlock(&mutable_scope->snapshot_mutex);
252 return ASCIICHAT_OK;
253}
#define STATS_DURATION_SHARDS
static bool size_t gotel_query_t * out
Definition queries.h:8
_Atomic uint64_t owner
stats_duration_shard_t shards[16]

References ASCIICHAT_OK, stats_scope::capabilities, stats_scope::counters, stats_scope::duration_mutex, stats_scope::duration_recorded_ns, stats_duration_shard_t::durations, stats_scope::durations, ERROR_INVALID_PARAM, ERROR_THREAD, stats_scope::gauges, stats_scope::id, stats_duration_cell_t::last_ns, stats_duration_snapshot_t::last_ns, stats_duration_cell_t::max_ns, stats_duration_snapshot_t::max_ns, stats_duration_cell_t::min_ns, stats_duration_snapshot_t::min_ns, stats_duration_cell_t::observations, stats_duration_snapshot_t::observations, out, stats_duration_shard_t::owner, stats_duration_cell_t::recorded_ns, stats_duration_cell_t::sequence, SET_ERRNO, stats_scope::shards, stats_scope::snapshot_mutex, stats_scope::started_ns, STATS_COUNTER_COUNT, STATS_DURATION_COUNT, STATS_DURATION_SHARDS, STATS_GAUGE_COUNT, stats_duration_cell_t::total_ns, and stats_duration_snapshot_t::total_ns.

Referenced by update_server_stats().