The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
xlat_profiling.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 5aa3f4df72f97d6dcc77de07a316dae86f1de12a $
19 *
20 * @file xlat_profiling.c
21 * @brief Functions for controlling profilers attached to the running server
22 *
23 * The comments in this file use the terms of each profiler. Callgrind
24 * counts costs (instruction fetches, cache misses, branch mispredictions)
25 * into one cost centre per function, and writes the cost centres as a
26 * profile dump. gperftools records samples of the program counter on a
27 * timer, and writes the samples to a profile file.
28 *
29 * @copyright 2026 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
30 */
31RCSID("$Id: 5aa3f4df72f97d6dcc77de07a316dae86f1de12a $")
32
33#include <freeradius-devel/server/base.h>
34#include <freeradius-devel/unlang/xlat_priv.h>
35
36#ifdef HAVE_VALGRIND_CALLGRIND_H
37# include <valgrind/callgrind.h>
38
39/** Log and return false if the server is not running under valgrind
40 *
41 * A natively running process ignores callgrind client requests. Every
42 * callgrind function therefore fails when valgrind is not attached, rather
43 * than silently succeeding. A misconfigured profiling run then shows in
44 * the log instead of producing an empty profile.
45 */
46static bool callgrind_attached(request_t *request)
47{
48 if (RUNNING_ON_VALGRIND) return true;
49
50 RERROR("Refusing callgrind request, the server is not running under valgrind");
51 return false;
52}
53
54/** Switch callgrind instrumentation on
55 *
56 * Start valgrind with `valgrind --tool=callgrind --instr-atstart=no`, then
57 * call `%callgrind.start()` from the `server.start` trigger to keep server
58 * startup out of the profile. Instrumentation is process-wide, so the
59 * thread that calls the function does not matter.
60 *
61 * Use this function when the profile should begin later than process
62 * start. Configuration parsing and module instantiation have finished
63 * when the `server.start` trigger fires.
64 *
65@verbatim
66%callgrind.start()
67@endverbatim
68 *
69 * @ingroup xlat_functions
70 */
71static xlat_action_t xlat_func_callgrind_start(TALLOC_CTX *ctx, fr_dcursor_t *out,
72 UNUSED xlat_ctx_t const *xctx,
73 request_t *request, UNUSED fr_value_box_list_t *args)
74{
75 fr_value_box_t *dst;
76
77 if (!callgrind_attached(request)) return XLAT_ACTION_FAIL;
78
79 CALLGRIND_START_INSTRUMENTATION;
80
81 MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL));
82 dst->vb_bool = true;
84
85 return XLAT_ACTION_DONE;
86}
87
88/** Switch callgrind instrumentation off
89 *
90 * Use this function when the profile should end before process exit. A
91 * call from the `server.stop` trigger keeps thread teardown out of the
92 * profile. A call from a policy ends the profile after a chosen section
93 * of unlang. If no trigger or policy calls this function, callgrind
94 * keeps counting costs until process exit and writes the profile dump at
95 * exit.
96 *
97@verbatim
98%callgrind.stop()
99@endverbatim
100 *
101 * @ingroup xlat_functions
102 */
103static xlat_action_t xlat_func_callgrind_stop(TALLOC_CTX *ctx, fr_dcursor_t *out,
104 UNUSED xlat_ctx_t const *xctx,
105 request_t *request, UNUSED fr_value_box_list_t *args)
106{
107 fr_value_box_t *dst;
108
109 if (!callgrind_attached(request)) return XLAT_ACTION_FAIL;
110
111 CALLGRIND_STOP_INSTRUMENTATION;
112
113 MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL));
114 dst->vb_bool = true;
116
117 return XLAT_ACTION_DONE;
118}
119
120static xlat_arg_parser_t const xlat_func_callgrind_dump_args[] = {
121 { .required = false, .concat = true, .type = FR_TYPE_STRING },
123};
124
125/** Write the cost data collected so far to a profile dump, then reset cost data
126 *
127 * Callgrind records the optional label in the description field of the dump.
128 * The label lets the reader distinguish the dumps when one run produces several.
129 *
130 * Use this function when one run should produce several profile dumps,
131 * such as one dump per load step, or one dump for the load phase and one
132 * dump for shutdown. Instrumentation stays on, and each dump holds only
133 * the costs counted since the previous dump. `callgrind_annotate`
134 * accepts several dumps at once.
135 *
136@verbatim
137%callgrind.dump([<label>])
138@endverbatim
139 *
140 * @ingroup xlat_functions
141 */
142static xlat_action_t xlat_func_callgrind_dump(TALLOC_CTX *ctx, fr_dcursor_t *out,
143 UNUSED xlat_ctx_t const *xctx,
144 request_t *request, fr_value_box_list_t *args)
145{
146 fr_value_box_t *dst, *label;
147
148 XLAT_ARGS(args, &label);
149
150 if (!callgrind_attached(request)) return XLAT_ACTION_FAIL;
151
152 if (label) {
153 CALLGRIND_DUMP_STATS_AT(label->vb_strvalue);
154 } else {
155 CALLGRIND_DUMP_STATS;
156 }
157
158 MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL));
159 dst->vb_bool = true;
161
162 return XLAT_ACTION_DONE;
163}
164
165/** Zero the costs collected so far without writing a dump
166 *
167 * Use this function to discard the costs of a period that the profile
168 * should not include. Examples are the cache warmup after
169 * `%callgrind.start()`, and a settling period after a load step changes
170 * the request rate. Call this function when the period ends. The next
171 * profile dump, or the dump at process exit, then holds only the costs
172 * counted after the call.
173 *
174@verbatim
175%callgrind.zero()
176@endverbatim
177 *
178 * @ingroup xlat_functions
179 */
180static xlat_action_t xlat_func_callgrind_zero(TALLOC_CTX *ctx, fr_dcursor_t *out,
181 UNUSED xlat_ctx_t const *xctx,
182 request_t *request, UNUSED fr_value_box_list_t *args)
183{
184 fr_value_box_t *dst;
185
186 if (!callgrind_attached(request)) return XLAT_ACTION_FAIL;
187
188 CALLGRIND_ZERO_STATS;
189
190 MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL));
191 dst->vb_bool = true;
193
194 return XLAT_ACTION_DONE;
195}
196#endif /* HAVE_VALGRIND_CALLGRIND_H */
197
198#ifdef HAVE_GPERFTOOLS_PROFILER_H
199# include <gperftools/profiler.h>
200/** Start the gperftools CPU profiler, writing samples to a file
201 *
202 * The function fails if the profiler is already running, so a second
203 * start never silently discards a running profile. The `limit files { ... }`
204 * section restricts the filename in the same way as for the `%file.*`
205 * functions.
206 *
207 * Use this function to measure where the server spends wall-clock time.
208 * The profiler samples the program counter on a timer at close to native
209 * speed, so the server handles a realistic load while the profiler runs.
210 * Callgrind measures instruction and cache behaviour instead, and slows
211 * the server by a large factor. The `server.start` trigger is the usual
212 * place to call this function.
213 *
214@verbatim
215%gperftools.start(<filename>)
216@endverbatim
217 *
218 * @ingroup xlat_functions
219 */
220static xlat_action_t xlat_func_gperftools_start(TALLOC_CTX *ctx, fr_dcursor_t *out,
221 UNUSED xlat_ctx_t const *xctx,
222 request_t *request, fr_value_box_list_t *args)
223{
224 fr_value_box_t *dst, *vb;
225 struct ProfilerState state;
226
227 XLAT_ARGS(args, &vb);
228 fr_assert(vb->type == FR_TYPE_STRING);
229
230 if (!xlat_file_allowed(request, vb)) return XLAT_ACTION_FAIL;
231
232 ProfilerGetCurrentState(&state);
233 if (state.enabled) {
234 RERROR("Profiler already running, writing to %s", state.profile_name);
235 return XLAT_ACTION_FAIL;
236 }
237
238 if (ProfilerStart(vb->vb_strvalue) == 0) {
239 RERROR("Failed starting profiler with output file %s", vb->vb_strvalue);
240 return XLAT_ACTION_FAIL;
241 }
242
243 MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL));
244 dst->vb_bool = true;
246
247 return XLAT_ACTION_DONE;
248}
249
250/** Log and return false if the gperftools CPU profiler is not running
251 */
252static bool gperftools_running(request_t *request)
253{
254 struct ProfilerState state;
255
256 ProfilerGetCurrentState(&state);
257 if (state.enabled) return true;
258
259 RERROR("Profiler not running");
260 return false;
261}
262
263/** Write the buffered gperftools samples to the profile file, then stop the profiler
264 *
265 * Use this function to end the profile at a known point, usually from
266 * the `server.stop` trigger. `%gperftools.start()` starts a stopped
267 * profiler again with a new profile file.
268 *
269@verbatim
270%gperftools.stop()
271@endverbatim
272 *
273 * @ingroup xlat_functions
274 */
275static xlat_action_t xlat_func_gperftools_stop(TALLOC_CTX *ctx, fr_dcursor_t *out,
276 UNUSED xlat_ctx_t const *xctx,
277 request_t *request, UNUSED fr_value_box_list_t *args)
278{
279 fr_value_box_t *dst;
280
281 if (!gperftools_running(request)) return XLAT_ACTION_FAIL;
282
283 ProfilerFlush();
284 ProfilerStop();
285
286 MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL));
287 dst->vb_bool = true;
289
290 return XLAT_ACTION_DONE;
291}
292
293/** Write the buffered gperftools samples to the profile file and keep the profiler running
294 *
295 * Use this function during a long run, so that `pprof` can read a partial
296 * profile while the server keeps running. Also use this function before
297 * a step that might crash the server. The buffered samples then reach
298 * the profile file before the crash.
299 *
300@verbatim
301%gperftools.flush()
302@endverbatim
303 *
304 * @ingroup xlat_functions
305 */
306static xlat_action_t xlat_func_gperftools_flush(TALLOC_CTX *ctx, fr_dcursor_t *out,
307 UNUSED xlat_ctx_t const *xctx,
308 request_t *request, UNUSED fr_value_box_list_t *args)
309{
310 fr_value_box_t *dst;
311
312 if (!gperftools_running(request)) return XLAT_ACTION_FAIL;
313
314 ProfilerFlush();
315
316 MEM(dst = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL));
317 dst->vb_bool = true;
319
320 return XLAT_ACTION_DONE;
321}
322#endif /* HAVE_GPERFTOOLS_PROFILER_H */
323
324/** Register the control functions for every profiler that the build compiled in
325 *
326 * @return
327 * - 0 on success.
328 * - -1 on failure.
329 */
331{
332#if defined(HAVE_VALGRIND_CALLGRIND_H) || defined(HAVE_GPERFTOOLS_PROFILER_H)
333 xlat_t *xlat;
334
335#define XLAT_REGISTER_PROFILING(_xlat, _func, _args) \
336do { \
337 if (unlikely((xlat = xlat_func_register(NULL, _xlat, _func, FR_TYPE_BOOL)) == NULL)) return -1; \
338 xlat_func_args_set(xlat, _args); \
339 xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL); \
340} while (0)
341
342#define XLAT_REGISTER_PROFILING_VOID(_xlat, _func) \
343do { \
344 if (unlikely((xlat = xlat_func_register(NULL, _xlat, _func, FR_TYPE_BOOL)) == NULL)) return -1; \
345 xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL); \
346} while (0)
347
348# ifdef HAVE_VALGRIND_CALLGRIND_H
349 XLAT_REGISTER_PROFILING_VOID("callgrind.start", xlat_func_callgrind_start);
350 XLAT_REGISTER_PROFILING_VOID("callgrind.stop", xlat_func_callgrind_stop);
351 XLAT_REGISTER_PROFILING("callgrind.dump", xlat_func_callgrind_dump, xlat_func_callgrind_dump_args);
352 XLAT_REGISTER_PROFILING_VOID("callgrind.zero", xlat_func_callgrind_zero);
353# endif
354
355# ifdef HAVE_GPERFTOOLS_PROFILER_H
356 XLAT_REGISTER_PROFILING("gperftools.start", xlat_func_gperftools_start, xlat_func_file_name_args);
357 XLAT_REGISTER_PROFILING_VOID("gperftools.stop", xlat_func_gperftools_stop);
358 XLAT_REGISTER_PROFILING_VOID("gperftools.flush", xlat_func_gperftools_flush);
359# endif
360
361#undef XLAT_REGISTER_PROFILING
362#undef XLAT_REGISTER_PROFILING_VOID
363#endif
364
365 return 0;
366}
va_list args
Definition acutest.h:770
#define RCSID(id)
Definition build.h:560
#define UNUSED
Definition build.h:384
static int fr_dcursor_append(fr_dcursor_t *cursor, void *v)
Insert a single item at the end of the list.
Definition dcursor.h:406
#define MEM(x)
Definition debug.h:38
#define RUNNING_ON_VALGRIND
Definition dl.c:40
#define RERROR(fmt,...)
Definition log.h:315
@ FR_TYPE_STRING
String of printable characters.
@ FR_TYPE_BOOL
A truth value.
#define fr_assert(_expr)
Definition rad_assert.h:37
void * state
Definition testlib.c:46
#define XLAT_ARGS(_list,...)
Populate local variables with value boxes from the input list.
Definition xlat.h:373
unsigned int required
Argument must be present, and non-empty.
Definition xlat.h:136
#define XLAT_ARG_PARSER_TERMINATOR
Definition xlat.h:160
xlat_action_t
Definition xlat.h:37
@ XLAT_ACTION_FAIL
An xlat function failed.
Definition xlat.h:44
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition xlat.h:43
Definition for a single argument consumed by an xlat function.
Definition xlat.h:135
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:669
static size_t char ** out
Definition value.h:1062
bool xlat_file_allowed(request_t *request, fr_value_box_t const *vb)
xlat_arg_parser_t const xlat_func_file_name_args[]
An xlat calling ctx.
Definition xlat_ctx.h:49
int xlat_profiling_init(void)
Register the control functions for every profiler that the build compiled in.