libe3 0.2.5
C++ library for E3AP RAN vendor integration
Loading...
Searching...
No Matches
latrec.c
Go to the documentation of this file.
1/**
2 * @file latrec.c
3 * @brief Per-thread ring storage and sequence allocator for latrec.
4 *
5 * latrec.h is usable on its own with an explicitly owned latrec_t. This file
6 * backs the convenience API on top of it: one ring per calling thread, opened
7 * on first stamp and named <prefix>.<tid>, plus the process-wide sequence
8 * counter. Keeping the storage in one translation unit gives a single ring per
9 * thread rather than one per includer.
10 *
11 * SPDX-FileCopyrightText: Copyright (c) 2026 Northeastern University
12 * SPDX-License-Identifier: Apache-2.0
13 */
14
15#include "libe3/latrec.h"
16
17#include <errno.h> /* program_invocation_short_name */
18#include <pthread.h>
19#include <sys/syscall.h>
20
21/* Capacity of a per-thread ring, as a power of two. The mapping is pre-faulted
22 * at open, so the ring is resident and a stamp costs the same at any size; the
23 * size sets the footprint and how long a run fits before wrapping. It is
24 * file-backed, so that footprint is page cache and disk, not anonymous memory.
25 *
26 * Every thread that stamps in a LIBE3_ENABLE_LATREC build opens a ring, with
27 * no per-run opt-out, so this default is what an ordinary run of an enabled
28 * build costs per thread. It is deliberately modest for that reason: a long
29 * capture raises it per role (see entries_log2() below and docs/latrec.md)
30 * rather than every test and example paying for a capacity only a benchmark
31 * needs. A ring that wrapped says so: rec_count keeps counting past the
32 * capacity. */
33#ifndef LATREC_TLS_ENTRIES_LOG2
34#define LATREC_TLS_ENTRIES_LOG2 18 /* 256 K records = 8 MiB per thread */
35#endif
36#define LATREC_ENTRIES_LOG2_MIN 12
37#define LATREC_ENTRIES_LOG2_MAX 28
38#define LATREC_MAX_RINGS 64
39
40/* Threads refer here until they open a ring of their own. It stays zeroed, so
41 * enabled == 0 and a stamp from a thread that never called latrec_tls_open()
42 * is discarded at the same check that disables tracing globally. */
44
46
48static int n_open_rings;
49static pthread_mutex_t rings_mu = PTHREAD_MUTEX_INITIALIZER;
50static pthread_once_t atexit_once = PTHREAD_ONCE_INIT;
51
52/* Threads do not close their rings, so rec_count and the closing clock pair
53 * are refreshed at exit. Records are valid without this (t_ns != 0); it only
54 * fills in the header counts. */
55static void flush_open_rings(void)
56{
57 pthread_mutex_lock(&rings_mu);
58 for (int i = 0; i < n_open_rings; i++) latrec_heartbeat(open_rings[i]);
59 pthread_mutex_unlock(&rings_mu);
60}
61
62static void install_atexit(void) { atexit(flush_open_rings); }
63
64/* LATREC_ENTRIES_LOG2 overrides the compiled-in capacity; out-of-range and
65 * unparseable values fall back to it. */
66static unsigned entries_log2_from(const char* name)
67{
68 const char* e = getenv(name);
69 if (e && *e) {
70 char* end = NULL;
71 long v = strtol(e, &end, 10);
72 if (end && !*end && v >= LATREC_ENTRIES_LOG2_MIN && v <= LATREC_ENTRIES_LOG2_MAX)
73 return (unsigned)v;
74 fprintf(stderr, "[latrec] ignoring %s=%s (expected %d..%d)\n",
76 }
77 return 0;
78}
79
80/* Ring capacity for a role: LATREC_ENTRIES_LOG2_<ROLE> if set, else
81 * LATREC_ENTRIES_LOG2, else the compiled-in default. The per-role name is the
82 * role uppercased with every non-alphanumeric character replaced by '_', so
83 * role "oai.mac" reads LATREC_ENTRIES_LOG2_OAI_MAC. Roles differ by orders of
84 * magnitude in stamp rate, so sizing them together either wastes memory or
85 * wraps the busiest ring. */
86static unsigned entries_log2(const char* role)
87{
88 if (role && *role) {
89 char name[128];
90 int n = snprintf(name, sizeof(name), "LATREC_ENTRIES_LOG2_%s", role);
91 if (n > 0 && (size_t)n < sizeof(name)) {
92 for (char* c = name + sizeof("LATREC_ENTRIES_LOG2_") - 1; *c; c++)
93 *c = (*c >= 'a' && *c <= 'z') ? (char)(*c - 'a' + 'A')
94 : ((*c >= 'A' && *c <= 'Z') || (*c >= '0' && *c <= '9')) ? *c : '_';
95 const unsigned v = entries_log2_from(name);
96 if (v)
97 return v;
98 }
99 }
100 const unsigned v = entries_log2_from("LATREC_ENTRIES_LOG2");
101 return v ? v : LATREC_TLS_ENTRIES_LOG2;
102}
103
104/* Process-wide ring directory. Written once at start-up by whoever owns the
105 * process, read by every thread that opens a ring; the mutex is only there to
106 * make that publication well-defined, since ring opens are not on a hot path. */
107static char output_dir[512];
108
109void latrec_set_output_dir(const char* dir)
110{
111 pthread_mutex_lock(&rings_mu);
112 if (dir && *dir) snprintf(output_dir, sizeof(output_dir), "%s", dir);
113 else output_dir[0] = '\0';
114 pthread_mutex_unlock(&rings_mu);
115}
116
117latrec_t* latrec_tls_open_as(const char* role)
118{
119 if (latrec_tls != &disabled_ring) return latrec_tls; /* already opened */
120
121 /* Heap, not TLS: flush_open_rings() and the mapping are still live at
122 * process exit, while a thread's TLS is released when it exits. Not
123 * freed, like the mapping. */
124 latrec_t* r = (latrec_t*)calloc(1, sizeof(*r));
125 if (!r) return latrec_tls; /* stays disabled */
126
127 if (!role || !*role) {
128#ifdef __GLIBC__
129 role = program_invocation_short_name;
130#else
131 role = "latrec";
132#endif
133 }
134 char name[64];
135 snprintf(name, sizeof(name), "%s.%ld", role, (long)syscall(SYS_gettid));
136
137 char dir[sizeof(output_dir)];
138 pthread_mutex_lock(&rings_mu);
139 memcpy(dir, output_dir, sizeof(dir));
140 pthread_mutex_unlock(&rings_mu);
141
142 /* A failed open leaves a disabled ring, which every later latrec_stamp()
143 * skips at the enabled check. */
144 latrec_open_in(r, dir, name, entries_log2(role));
145 latrec_tls = r;
146
147 if (r->enabled) {
148 pthread_once(&atexit_once, install_atexit);
149 pthread_mutex_lock(&rings_mu);
150 /* Beyond the cap a ring still records; it gets no exit flush. */
152 pthread_mutex_unlock(&rings_mu);
153 }
154 return latrec_tls;
155}
156
158
159uint64_t latrec_seq_next(void)
160{
161 static uint64_t counter;
162 return __atomic_add_fetch(&counter, 1, __ATOMIC_RELAXED);
163}
static pthread_mutex_t rings_mu
Definition latrec.c:49
static void install_atexit(void)
Definition latrec.c:62
static void flush_open_rings(void)
Definition latrec.c:55
latrec_t * latrec_tls_open(void)
Definition latrec.c:157
void latrec_set_output_dir(const char *dir)
Definition latrec.c:109
#define LATREC_MAX_RINGS
Definition latrec.c:38
static unsigned entries_log2(const char *role)
Definition latrec.c:86
static unsigned entries_log2_from(const char *name)
Definition latrec.c:66
uint64_t latrec_seq_next(void)
Definition latrec.c:159
latrec_t * latrec_tls_open_as(const char *role)
Definition latrec.c:117
static int n_open_rings
Definition latrec.c:48
static latrec_t disabled_ring
Definition latrec.c:43
#define LATREC_ENTRIES_LOG2_MAX
Definition latrec.c:37
static pthread_once_t atexit_once
Definition latrec.c:50
static char output_dir[512]
Definition latrec.c:107
#define LATREC_TLS_ENTRIES_LOG2
Definition latrec.c:34
static latrec_t * open_rings[64]
Definition latrec.c:47
__thread latrec_t * latrec_tls
Definition latrec.c:45
#define LATREC_ENTRIES_LOG2_MIN
Definition latrec.c:36
Lock-free per-thread latency stamp recorder (header-only, C99).
static int latrec_open_in(latrec_t *r, const char *dir, const char *name, unsigned entries_log2)
Definition latrec.h:475
static void latrec_heartbeat(latrec_t *r)
Definition latrec.h:583
int enabled
Definition latrec.h:412