Branch data Line data Source code
1 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
2 : : // SPDX-FileCopyrightText: 2016 Christian Hergert <christian@hergert.me>
3 : :
4 : : #include <config.h> // for ENABLE_PROFILER, HAVE_SYS_SYSCALL_H, HAVE_UNISTD_H
5 : :
6 : : #ifdef HAVE_SIGNAL_H
7 : : # include <signal.h> // for siginfo_t, sigevent, sigaction, SIGPROF, ...
8 : : #endif
9 : :
10 : : #ifdef ENABLE_PROFILER
11 : : # include <alloca.h>
12 : : # include <errno.h>
13 : : # include <stdint.h>
14 : : # include <stdio.h> // for sscanf
15 : : # include <string.h> // for memcpy, strlen
16 : : # include <sys/syscall.h> // for __NR_gettid
17 : : # include <sys/types.h> // for timer_t
18 : : # include <time.h> // for size_t, CLOCK_MONOTONIC, itimerspec, ...
19 : : # ifdef HAVE_UNISTD_H
20 : : # include <unistd.h> // for getpid, syscall
21 : : # endif
22 : : # include <array>
23 : : #endif
24 : :
25 : : #include <glib-object.h>
26 : : #include <glib.h>
27 : :
28 : : #ifdef ENABLE_PROFILER
29 : : # ifdef G_OS_UNIX
30 : : # include <glib-unix.h>
31 : : # endif
32 : : # include <sysprof-capture.h>
33 : : #endif
34 : :
35 : : #include <js/ProfilingStack.h> // for EnableContextProfilingStack, ...
36 : : #include <js/TypeDecls.h>
37 : : #include <mozilla/Atomics.h> // for ProfilingStack operators
38 : :
39 : : #include "gjs/context.h"
40 : : #include "gjs/jsapi-util.h"
41 : : #include "gjs/mem-private.h"
42 : : #include "gjs/profiler-private.h"
43 : : #include "gjs/profiler.h"
44 : :
45 : : #define FLUSH_DELAY_SECONDS 3
46 : :
47 : : /*
48 : : * This is mostly non-exciting code wrapping the builtin Profiler in
49 : : * mozjs. In particular, the profiler consumer is required to "bring your
50 : : * own sampler". We do the very non-surprising thing of using POSIX
51 : : * timers to deliver SIGPROF to the thread containing the JSContext.
52 : : *
53 : : * However, we do use a Linux'ism that allows us to deliver the signal
54 : : * to only a single thread. Doing this in a generic fashion would
55 : : * require thread-registration so that we can mask SIGPROF from all
56 : : * threads except the JS thread. The gecko engine uses tgkill() to do
57 : : * this with a secondary thread instead of using POSIX timers. We could
58 : : * do this too, but it would still be Linux-only.
59 : : *
60 : : * Another option might be to use pthread_kill() and a secondary thread
61 : : * to perform the notification.
62 : : *
63 : : * From within the signal handler, we process the current stack as
64 : : * delivered to us from the JSContext. Any pointer data that comes from
65 : : * the runtime has to be copied, so we keep our own dedup'd string
66 : : * pointers for JavaScript file/line information. Non-JS instruction
67 : : * pointers are just fine, as they can be resolved by parsing the ELF for
68 : : * the file mapped on disk containing that address.
69 : : *
70 : : * As much of this code has to run from signal handlers, it is very
71 : : * important that we don't use anything that can malloc() or lock, or
72 : : * deadlocks are very likely. Most of GjsProfilerCapture is signal-safe.
73 : : */
74 : :
75 : : #define SAMPLES_PER_SEC G_GUINT64_CONSTANT(1000)
76 : : #define NSEC_PER_SEC G_GUINT64_CONSTANT(1000000000)
77 : :
78 [ # # # # : 0 : G_DEFINE_POINTER_TYPE(GjsProfiler, gjs_profiler)
# # ]
79 : :
80 : : struct _GjsProfiler {
81 : : #ifdef ENABLE_PROFILER
82 : : /* The stack for the JSContext profiler to use for current stack
83 : : * information while executing. We will look into this during our
84 : : * SIGPROF handler.
85 : : */
86 : : ProfilingStack stack;
87 : :
88 : : /* The context being profiled */
89 : : JSContext *cx;
90 : :
91 : : /* Buffers and writes our sampled stacks */
92 : : SysprofCaptureWriter* capture;
93 : : GSource* periodic_flush;
94 : :
95 : : SysprofCaptureWriter* target_capture;
96 : :
97 : : // Cache previous values of counters so that we don't overrun the output
98 : : // with counters that don't change very often
99 : : uint64_t last_counter_values[GJS_N_COUNTERS];
100 : : #endif /* ENABLE_PROFILER */
101 : :
102 : : /* The filename to write to */
103 : : char *filename;
104 : :
105 : : /* An FD to capture to */
106 : : int fd;
107 : :
108 : : #ifdef ENABLE_PROFILER
109 : : /* Our POSIX timer to wakeup SIGPROF */
110 : : timer_t timer;
111 : :
112 : : /* Cached copy of our pid */
113 : : GPid pid;
114 : :
115 : : /* GLib signal handler ID for SIGUSR2 */
116 : : unsigned sigusr2_id;
117 : : unsigned counter_base; // index of first GObject memory counter
118 : : unsigned gc_counter_base; // index of first GC stats counter
119 : : #endif /* ENABLE_PROFILER */
120 : :
121 : : /* If we are currently sampling */
122 : : unsigned running : 1;
123 : : };
124 : :
125 : : static GjsContext *profiling_context;
126 : :
127 : : #ifdef ENABLE_PROFILER
128 : : /*
129 : : * gjs_profiler_extract_maps:
130 : : *
131 : : * This function will write the mapped section information to the
132 : : * capture file so that the callgraph builder can generate symbols
133 : : * from the stack addresses provided.
134 : : *
135 : : * Returns: %TRUE if successful; otherwise %FALSE and the profile
136 : : * should abort.
137 : : */
138 : 5 : [[nodiscard]] static bool gjs_profiler_extract_maps(GjsProfiler* self) {
139 : 5 : int64_t now = g_get_monotonic_time() * 1000L;
140 : :
141 : 5 : g_assert(((void) "Profiler must be set up before extracting maps", self));
142 : :
143 : 5 : GjsAutoChar path = g_strdup_printf("/proc/%jd/maps", intmax_t(self->pid));
144 : :
145 : : char *content_tmp;
146 : : size_t len;
147 [ - + ]: 5 : if (!g_file_get_contents(path, &content_tmp, &len, nullptr))
148 : 0 : return false;
149 : 5 : GjsAutoChar content = content_tmp;
150 : :
151 : 5 : GjsAutoStrv lines = g_strsplit(content, "\n", 0);
152 : :
153 [ + + ]: 1411 : for (size_t ix = 0; lines[ix]; ix++) {
154 : : char file[256];
155 : : unsigned long start;
156 : : unsigned long end;
157 : : unsigned long offset;
158 : : unsigned long inode;
159 : :
160 : 1406 : file[sizeof file - 1] = '\0';
161 : :
162 : 1406 : int r = sscanf(lines[ix], "%lx-%lx %*15s %lx %*x:%*x %lu %255s",
163 : : &start, &end, &offset, &inode, file);
164 [ + + ]: 1406 : if (r != 5)
165 : 346 : continue;
166 : :
167 [ + + ]: 1060 : if (strcmp("[vdso]", file) == 0) {
168 : 5 : offset = 0;
169 : 5 : inode = 0;
170 : : }
171 : :
172 [ - + ]: 1060 : if (!sysprof_capture_writer_add_map(self->capture, now, -1, self->pid,
173 : : start, end, offset, inode, file))
174 : 0 : return false;
175 : : }
176 : :
177 : 5 : return true;
178 : 5 : }
179 : :
180 : 80 : static void setup_counter_helper(SysprofCaptureCounter* counter,
181 : : const char* counter_name,
182 : : unsigned counter_base, size_t ix) {
183 : 80 : g_snprintf(counter->category, sizeof counter->category, "GJS");
184 : 80 : g_snprintf(counter->name, sizeof counter->name, "%s", counter_name);
185 : 80 : g_snprintf(counter->description, sizeof counter->description, "%s",
186 : 80 : GJS_COUNTER_DESCRIPTIONS[ix]);
187 : 80 : counter->id = uint32_t(counter_base + ix);
188 : 80 : counter->type = SYSPROF_CAPTURE_COUNTER_INT64;
189 : 80 : counter->value.v64 = 0;
190 : 80 : }
191 : :
192 : 5 : [[nodiscard]] static bool gjs_profiler_define_counters(GjsProfiler* self) {
193 : 5 : int64_t now = g_get_monotonic_time() * 1000L;
194 : :
195 : 5 : g_assert(self && "Profiler must be set up before defining counters");
196 : :
197 : : std::array<SysprofCaptureCounter, GJS_N_COUNTERS> counters;
198 : 5 : self->counter_base =
199 : 5 : sysprof_capture_writer_request_counter(self->capture, GJS_N_COUNTERS);
200 : :
201 : : # define SETUP_COUNTER(counter_name, ix) \
202 : : setup_counter_helper(&counters[ix], #counter_name, self->counter_base, \
203 : : ix);
204 : 5 : GJS_FOR_EACH_COUNTER(SETUP_COUNTER);
205 : : # undef SETUP_COUNTER
206 : :
207 [ - + ]: 5 : if (!sysprof_capture_writer_define_counters(
208 : 5 : self->capture, now, -1, self->pid, counters.data(), GJS_N_COUNTERS))
209 : 0 : return false;
210 : :
211 : : std::array<SysprofCaptureCounter, Gjs::GCCounters::N_COUNTERS> gc_counters;
212 : 5 : self->gc_counter_base = sysprof_capture_writer_request_counter(
213 : : self->capture, Gjs::GCCounters::N_COUNTERS);
214 : :
215 : 5 : constexpr size_t category_size = sizeof gc_counters[0].category;
216 : 5 : constexpr size_t name_size = sizeof gc_counters[0].name;
217 : 5 : constexpr size_t description_size = sizeof gc_counters[0].description;
218 : :
219 [ + + ]: 15 : for (size_t ix = 0; ix < Gjs::GCCounters::N_COUNTERS; ix++) {
220 : 10 : g_snprintf(gc_counters[ix].category, category_size, "GJS");
221 : 10 : gc_counters[ix].id = uint32_t(self->gc_counter_base + ix);
222 : 10 : gc_counters[ix].type = SYSPROF_CAPTURE_COUNTER_INT64;
223 : 10 : gc_counters[ix].value.v64 = 0;
224 : : }
225 : 5 : g_snprintf(gc_counters[Gjs::GCCounters::GC_HEAP_BYTES].name, name_size,
226 : : "GC bytes");
227 : 5 : g_snprintf(gc_counters[Gjs::GCCounters::GC_HEAP_BYTES].description,
228 : : description_size, "Bytes used in GC heap");
229 : 5 : g_snprintf(gc_counters[Gjs::GCCounters::MALLOC_HEAP_BYTES].name, name_size,
230 : : "Malloc bytes");
231 : 5 : g_snprintf(gc_counters[Gjs::GCCounters::MALLOC_HEAP_BYTES].description,
232 : : description_size, "Malloc bytes owned by tenured GC things");
233 : :
234 : 5 : return sysprof_capture_writer_define_counters(self->capture, now, -1,
235 : 5 : self->pid, gc_counters.data(),
236 : 5 : Gjs::GCCounters::N_COUNTERS);
237 : : }
238 : :
239 : : #endif /* ENABLE_PROFILER */
240 : :
241 : : /*
242 : : * _gjs_profiler_new:
243 : : * @context: The #GjsContext to profile
244 : : *
245 : : * This creates a new profiler for the #JSContext. It is important that
246 : : * this instance is freed with _gjs_profiler_free() before the context is
247 : : * destroyed.
248 : : *
249 : : * Call gjs_profiler_start() to enable the profiler, and gjs_profiler_stop()
250 : : * when you have finished.
251 : : *
252 : : * The profiler works by enabling the JS profiler in spidermonkey so that
253 : : * sample information is available. A POSIX timer is used to signal SIGPROF
254 : : * to the process on a regular interval to collect the most recent profile
255 : : * sample and stash it away. It is a programming error to mask SIGPROF from
256 : : * the thread controlling the JS context.
257 : : *
258 : : * If another #GjsContext already has a profiler, or @context already has one,
259 : : * then returns %NULL instead.
260 : : *
261 : : * Returns: (transfer full) (nullable): A newly allocated #GjsProfiler
262 : : */
263 : : GjsProfiler *
264 : 5 : _gjs_profiler_new(GjsContext *context)
265 : : {
266 : 5 : g_return_val_if_fail(context, nullptr);
267 : :
268 [ - + ]: 5 : if (profiling_context == context) {
269 : 0 : g_critical("You can only create one profiler at a time.");
270 : 0 : return nullptr;
271 : : }
272 : :
273 [ - + ]: 5 : if (profiling_context) {
274 : 0 : g_message("Not going to profile GjsContext %p; you can only profile "
275 : : "one context at a time.", context);
276 : 0 : return nullptr;
277 : : }
278 : :
279 : 5 : GjsProfiler *self = g_new0(GjsProfiler, 1);
280 : :
281 : : #ifdef ENABLE_PROFILER
282 : 5 : self->cx = static_cast<JSContext *>(gjs_context_get_native_context(context));
283 : 5 : self->pid = getpid();
284 : : #endif
285 : 5 : self->fd = -1;
286 : :
287 : 5 : profiling_context = context;
288 : :
289 : 5 : return self;
290 : : }
291 : :
292 : : /*
293 : : * _gjs_profiler_free:
294 : : * @self: A #GjsProfiler
295 : : *
296 : : * Frees a profiler instance and cleans up any allocated data.
297 : : *
298 : : * If the profiler is running, it will be stopped. This may result in blocking
299 : : * to write the contents of the buffer to the underlying file-descriptor.
300 : : */
301 : : void
302 : 5 : _gjs_profiler_free(GjsProfiler *self)
303 : : {
304 [ - + ]: 5 : if (!self)
305 : 0 : return;
306 : :
307 [ - + ]: 5 : if (self->running)
308 : 0 : gjs_profiler_stop(self);
309 : :
310 : 5 : profiling_context = nullptr;
311 : :
312 [ + + ]: 5 : g_clear_pointer(&self->filename, g_free);
313 : : #ifdef ENABLE_PROFILER
314 [ - + ]: 5 : g_clear_pointer(&self->capture, sysprof_capture_writer_unref);
315 [ - + ]: 5 : g_clear_pointer(&self->periodic_flush, g_source_destroy);
316 [ - + ]: 5 : g_clear_pointer(&self->target_capture, sysprof_capture_writer_unref);
317 : :
318 [ - + ]: 5 : if (self->fd != -1)
319 : 0 : close(self->fd);
320 : :
321 : 5 : self->stack.~ProfilingStack();
322 : : #endif
323 : 5 : g_free(self);
324 : : }
325 : :
326 : : /*
327 : : * _gjs_profiler_is_running:
328 : : * @self: A #GjsProfiler
329 : : *
330 : : * Checks if the profiler is currently running. This means that the JS
331 : : * profiler is enabled and POSIX signal timers are registered.
332 : : *
333 : : * Returns: %TRUE if the profiler is active.
334 : : */
335 : : bool
336 : 104 : _gjs_profiler_is_running(GjsProfiler *self)
337 : : {
338 : 104 : g_return_val_if_fail(self, false);
339 : :
340 : 104 : return self->running;
341 : : }
342 : :
343 : : #ifdef ENABLE_PROFILER
344 : :
345 : 2 : static void gjs_profiler_sigprof(int signum [[maybe_unused]], siginfo_t* info,
346 : : void*) {
347 : 2 : GjsProfiler *self = gjs_context_get_profiler(profiling_context);
348 : :
349 : 2 : g_assert(((void) "SIGPROF handler called with invalid signal info", info));
350 : 2 : g_assert(((void) "SIGPROF handler called with other signal",
351 : : info->si_signo == SIGPROF));
352 : :
353 : : /*
354 : : * NOTE:
355 : : *
356 : : * This is the SIGPROF signal handler. Everything done in this thread
357 : : * needs to be things that are safe to do in a signal handler. One thing
358 : : * that is not okay to do, is *malloc*.
359 : : */
360 : :
361 [ + - - + ]: 2 : if (!self || info->si_code != SI_TIMER)
362 : 1 : return;
363 : :
364 : 2 : uint32_t depth = self->stack.stackSize();
365 [ + + ]: 2 : if (depth == 0)
366 : 1 : return;
367 : :
368 : 1 : int64_t now = g_get_monotonic_time() * 1000L;
369 : :
370 : : /* NOTE: cppcheck warns that alloca() is not recommended since it can
371 : : * easily overflow the stack; however, dynamic allocation is not an option
372 : : * here since we are in a signal handler.
373 : : */
374 : : SysprofCaptureAddress* addrs =
375 : : // cppcheck-suppress allocaCalled
376 : 1 : static_cast<SysprofCaptureAddress*>(alloca(sizeof *addrs * depth));
377 : :
378 [ + + ]: 2 : for (uint32_t ix = 0; ix < depth; ix++) {
379 : 1 : js::ProfilingStackFrame& entry = self->stack.frames[ix];
380 : 1 : const char *label = entry.label();
381 : 1 : const char *dynamic_string = entry.dynamicString();
382 : 1 : uint32_t flipped = depth - 1 - ix;
383 : 1 : size_t label_length = strlen(label);
384 : :
385 : : /*
386 : : * 512 is an arbitrarily large size, very likely to be enough to
387 : : * hold the final string.
388 : : */
389 : : char final_string[512];
390 : 1 : char *position = final_string;
391 : 1 : size_t available_length = sizeof (final_string) - 1;
392 : :
393 [ + - ]: 1 : if (label_length > 0) {
394 [ + - ]: 1 : label_length = MIN(label_length, available_length);
395 : :
396 : : /* Start copying the label to the final string */
397 : 1 : memcpy(position, label, label_length);
398 : 1 : available_length -= label_length;
399 : 1 : position += label_length;
400 : :
401 : : /*
402 : : * Add a space in between the label and the dynamic string,
403 : : * if there is one.
404 : : */
405 [ - + - - ]: 1 : if (dynamic_string && available_length > 0) {
406 : 0 : *position++ = ' ';
407 : 0 : available_length--;
408 : : }
409 : : }
410 : :
411 : : /* Now append the dynamic string at the end of the final string.
412 : : * The string is cut in case it doesn't fit the remaining space.
413 : : */
414 [ - + ]: 1 : if (dynamic_string) {
415 : 0 : size_t dynamic_string_length = strlen(dynamic_string);
416 : :
417 [ # # ]: 0 : if (dynamic_string_length > 0) {
418 [ # # ]: 0 : size_t remaining_length = MIN(available_length, dynamic_string_length);
419 : 0 : memcpy(position, dynamic_string, remaining_length);
420 : 0 : position += remaining_length;
421 : : }
422 : : }
423 : :
424 : 1 : *position = 0;
425 : :
426 : : /*
427 : : * GeckoProfiler will put "js::RunScript" on the stack, but it has
428 : : * a stack address of "this", which is not terribly useful since
429 : : * everything will show up as [stack] when building callgraphs.
430 : : */
431 [ + - ]: 1 : if (final_string[0] != '\0')
432 : 1 : addrs[flipped] =
433 : 1 : sysprof_capture_writer_add_jitmap(self->capture, final_string);
434 : : else
435 : 0 : addrs[flipped] = SysprofCaptureAddress(entry.stackAddress());
436 : : }
437 : :
438 [ - + ]: 1 : if (!sysprof_capture_writer_add_sample(self->capture, now, -1, self->pid,
439 : : -1, addrs, depth)) {
440 : 0 : gjs_profiler_stop(self);
441 : 0 : return;
442 : : }
443 : :
444 : : unsigned ids[GJS_N_COUNTERS];
445 : : SysprofCaptureCounterValue values[GJS_N_COUNTERS];
446 : 1 : size_t new_counts = 0;
447 : :
448 : : # define FETCH_COUNTERS(name, ix) \
449 : : { \
450 : : uint64_t count = GJS_GET_COUNTER(name); \
451 : : if (count != self->last_counter_values[ix]) { \
452 : : ids[new_counts] = self->counter_base + ix; \
453 : : values[new_counts].v64 = count; \
454 : : new_counts++; \
455 : : } \
456 : : self->last_counter_values[ix] = count; \
457 : : }
458 [ - + - + : 16 : GJS_FOR_EACH_COUNTER(FETCH_COUNTERS);
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + ]
459 : : # undef FETCH_COUNTERS
460 : :
461 [ - + - + ]: 1 : if (new_counts > 0 &&
462 [ # # ]: 0 : !sysprof_capture_writer_set_counters(self->capture, now, -1, self->pid,
463 : : ids, values, new_counts))
464 : 0 : gjs_profiler_stop(self);
465 : : }
466 : :
467 : 0 : static gboolean profiler_auto_flush_cb(void* user_data) {
468 : 0 : auto* self = static_cast<GjsProfiler*>(user_data);
469 : :
470 [ # # ]: 0 : if (!self->running)
471 : 0 : return G_SOURCE_REMOVE;
472 : :
473 : 0 : sysprof_capture_writer_flush(self->capture);
474 : :
475 : 0 : return G_SOURCE_CONTINUE;
476 : : }
477 : :
478 : : #endif /* ENABLE_PROFILER */
479 : :
480 : : /**
481 : : * gjs_profiler_start:
482 : : * @self: A #GjsProfiler
483 : : *
484 : : * As expected, this starts the GjsProfiler.
485 : : *
486 : : * This will enable the underlying JS profiler and register a POSIX timer to
487 : : * deliver SIGPROF on the configured sampling frequency.
488 : : *
489 : : * To reduce sampling overhead, #GjsProfiler stashes information about the
490 : : * profile to be calculated once the profiler has been disabled. Calling
491 : : * gjs_profiler_stop() will result in that delayed work to be completed.
492 : : *
493 : : * You should call gjs_profiler_stop() when the profiler is no longer needed.
494 : : */
495 : : void
496 : 5 : gjs_profiler_start(GjsProfiler *self)
497 : : {
498 : 5 : g_return_if_fail(self);
499 [ - + ]: 5 : if (self->running)
500 : 0 : return;
501 : :
502 : : #ifdef ENABLE_PROFILER
503 : :
504 : 5 : g_return_if_fail(!self->capture);
505 : :
506 : 5 : struct sigaction sa = {{0}};
507 : 5 : struct sigevent sev = {{0}};
508 : 5 : struct itimerspec its = {{0}};
509 : : struct itimerspec old_its;
510 : :
511 [ - + ]: 5 : if (self->target_capture) {
512 : 0 : self->capture = sysprof_capture_writer_ref(self->target_capture);
513 [ - + ]: 5 : } else if (self->fd != -1) {
514 : 0 : self->capture = sysprof_capture_writer_new_from_fd(self->fd, 0);
515 : 0 : self->fd = -1;
516 : : } else {
517 : 10 : GjsAutoChar path = g_strdup(self->filename);
518 [ + + ]: 5 : if (!path)
519 : 3 : path = g_strdup_printf("gjs-%jd.syscap", intmax_t(self->pid));
520 : :
521 : 5 : self->capture = sysprof_capture_writer_new(path, 0);
522 : 5 : }
523 : :
524 [ - + ]: 5 : if (!self->capture) {
525 : 0 : g_warning("Failed to open profile capture");
526 : 0 : return;
527 : : }
528 : :
529 : : /* Automatically flush to be resilient against SIGINT, etc */
530 [ + - ]: 5 : if (!self->periodic_flush) {
531 : 5 : self->periodic_flush =
532 : 5 : g_timeout_source_new_seconds(FLUSH_DELAY_SECONDS);
533 : 5 : g_source_set_name(self->periodic_flush,
534 : : "[sysprof-capture-writer-flush]");
535 : 5 : g_source_set_priority(self->periodic_flush, G_PRIORITY_LOW + 100);
536 : 5 : g_source_set_callback(self->periodic_flush,
537 : : (GSourceFunc)profiler_auto_flush_cb, self,
538 : : nullptr);
539 : 5 : g_source_attach(self->periodic_flush,
540 : : g_main_context_get_thread_default());
541 : : }
542 : :
543 [ - + ]: 5 : if (!gjs_profiler_extract_maps(self)) {
544 : 0 : g_warning("Failed to extract proc maps");
545 [ # # ]: 0 : g_clear_pointer(&self->capture, sysprof_capture_writer_unref);
546 [ # # ]: 0 : g_clear_pointer(&self->periodic_flush, g_source_destroy);
547 : 0 : return;
548 : : }
549 : :
550 [ - + ]: 5 : if (!gjs_profiler_define_counters(self)) {
551 : 0 : g_warning("Failed to define sysprof counters");
552 [ # # ]: 0 : g_clear_pointer(&self->capture, sysprof_capture_writer_unref);
553 [ # # ]: 0 : g_clear_pointer(&self->periodic_flush, g_source_destroy);
554 : 0 : return;
555 : : }
556 : :
557 : : /* Setup our signal handler for SIGPROF delivery */
558 : 5 : sa.sa_flags = SA_RESTART | SA_SIGINFO;
559 : 5 : sa.sa_sigaction = gjs_profiler_sigprof;
560 : 5 : sigemptyset(&sa.sa_mask);
561 : :
562 [ - + ]: 5 : if (sigaction(SIGPROF, &sa, nullptr) == -1) {
563 : 0 : g_warning("Failed to register sigaction handler: %s", g_strerror(errno));
564 [ # # ]: 0 : g_clear_pointer(&self->capture, sysprof_capture_writer_unref);
565 [ # # ]: 0 : g_clear_pointer(&self->periodic_flush, g_source_destroy);
566 : 0 : return;
567 : : }
568 : :
569 : : /*
570 : : * Create our SIGPROF timer
571 : : *
572 : : * We want to receive a SIGPROF signal on the JS thread using our
573 : : * configured sampling frequency. Instead of allowing any thread to be
574 : : * notified, we set the _tid value to ensure that only our thread gets
575 : : * delivery of the signal. This feature is generally just for
576 : : * threading implementations, but it works for us as well and ensures
577 : : * that the thread is blocked while we capture the stack.
578 : : */
579 : 5 : sev.sigev_notify = SIGEV_THREAD_ID;
580 : 5 : sev.sigev_signo = SIGPROF;
581 : 5 : sev._sigev_un._tid = syscall(__NR_gettid);
582 : :
583 [ - + ]: 5 : if (timer_create(CLOCK_MONOTONIC, &sev, &self->timer) == -1) {
584 : 0 : g_warning("Failed to create profiler timer: %s", g_strerror(errno));
585 [ # # ]: 0 : g_clear_pointer(&self->capture, sysprof_capture_writer_unref);
586 [ # # ]: 0 : g_clear_pointer(&self->periodic_flush, g_source_destroy);
587 : 0 : return;
588 : : }
589 : :
590 : : /* Calculate sampling interval */
591 : 5 : its.it_interval.tv_sec = 0;
592 : 5 : its.it_interval.tv_nsec = NSEC_PER_SEC / SAMPLES_PER_SEC;
593 : 5 : its.it_value.tv_sec = 0;
594 : 5 : its.it_value.tv_nsec = NSEC_PER_SEC / SAMPLES_PER_SEC;
595 : :
596 : : /* Now start this timer */
597 [ - + ]: 5 : if (timer_settime(self->timer, 0, &its, &old_its) != 0) {
598 : 0 : g_warning("Failed to enable profiler timer: %s", g_strerror(errno));
599 : 0 : timer_delete(self->timer);
600 [ # # ]: 0 : g_clear_pointer(&self->capture, sysprof_capture_writer_unref);
601 [ # # ]: 0 : g_clear_pointer(&self->periodic_flush, g_source_destroy);
602 : 0 : return;
603 : : }
604 : :
605 : 5 : self->running = true;
606 : :
607 : : /* Notify the JS runtime of where to put stack info */
608 : 5 : js::SetContextProfilingStack(self->cx, &self->stack);
609 : :
610 : : /* Start recording stack info */
611 : 5 : js::EnableContextProfilingStack(self->cx, true);
612 : :
613 : 5 : g_message("Profiler started");
614 : :
615 : : #else /* !ENABLE_PROFILER */
616 : :
617 : : self->running = true;
618 : : g_message("Profiler is disabled. Recompile with it enabled to use.");
619 : :
620 : : #endif /* ENABLE_PROFILER */
621 : : }
622 : :
623 : : /**
624 : : * gjs_profiler_stop:
625 : : * @self: A #GjsProfiler
626 : : *
627 : : * Stops a currently running #GjsProfiler. If the profiler is not running,
628 : : * this function will do nothing.
629 : : *
630 : : * Some work may be delayed until the end of the capture. Such delayed work
631 : : * includes flushing the resulting samples and file location information to
632 : : * disk.
633 : : *
634 : : * This may block while writing to disk. Generally, the writes are delivered
635 : : * to a tmpfs device, and are therefore negligible.
636 : : */
637 : : void
638 : 5 : gjs_profiler_stop(GjsProfiler *self)
639 : : {
640 : : /* Note: can be called from a signal handler */
641 : :
642 : 5 : g_assert(self);
643 : :
644 [ - + ]: 5 : if (!self->running)
645 : 0 : return;
646 : :
647 : : #ifdef ENABLE_PROFILER
648 : :
649 : 5 : struct itimerspec its = {{0}};
650 : 5 : timer_settime(self->timer, 0, &its, nullptr);
651 : 5 : timer_delete(self->timer);
652 : :
653 : 5 : js::EnableContextProfilingStack(self->cx, false);
654 : 5 : js::SetContextProfilingStack(self->cx, nullptr);
655 : :
656 : 5 : sysprof_capture_writer_flush(self->capture);
657 : :
658 [ + - ]: 5 : g_clear_pointer(&self->capture, sysprof_capture_writer_unref);
659 [ + - ]: 5 : g_clear_pointer(&self->periodic_flush, g_source_destroy);
660 : :
661 : 5 : g_message("Profiler stopped");
662 : :
663 : : #endif /* ENABLE_PROFILER */
664 : :
665 : 5 : self->running = false;
666 : : }
667 : :
668 : : #ifdef ENABLE_PROFILER
669 : :
670 : : static gboolean
671 : 0 : gjs_profiler_sigusr2(void *data)
672 : : {
673 : 0 : GjsContext* context = GJS_CONTEXT(data);
674 : 0 : GjsProfiler *current_profiler = gjs_context_get_profiler(context);
675 : :
676 [ # # ]: 0 : if (current_profiler) {
677 [ # # ]: 0 : if (_gjs_profiler_is_running(current_profiler))
678 : 0 : gjs_profiler_stop(current_profiler);
679 : : else
680 : 0 : gjs_profiler_start(current_profiler);
681 : : }
682 : :
683 : 0 : return G_SOURCE_CONTINUE;
684 : : }
685 : :
686 : : #endif /* ENABLE_PROFILER */
687 : :
688 : : /*
689 : : * _gjs_profiler_setup_signals:
690 : : * @context: a #GjsContext with a profiler attached
691 : : *
692 : : * If you want to simply allow profiling of your process with minimal
693 : : * fuss, simply call gjs_profiler_setup_signals(). This will allow
694 : : * enabling and disabling the profiler with SIGUSR2. You must call
695 : : * this from main() immediately when your program starts and must not
696 : : * block SIGUSR2 from your signal mask.
697 : : *
698 : : * If this is not sufficient, use gjs_profiler_chain_signal() from your
699 : : * own signal handler to pass the signal to a GjsProfiler.
700 : : */
701 : : void
702 : 0 : _gjs_profiler_setup_signals(GjsProfiler *self,
703 : : GjsContext *context)
704 : : {
705 : 0 : g_return_if_fail(context == profiling_context);
706 : :
707 : : #ifdef ENABLE_PROFILER
708 : :
709 [ # # ]: 0 : if (self->sigusr2_id != 0)
710 : 0 : return;
711 : :
712 : 0 : self->sigusr2_id = g_unix_signal_add(SIGUSR2, gjs_profiler_sigusr2, context);
713 : :
714 : : #else /* !ENABLE_PROFILER */
715 : :
716 : : g_message("Profiler is disabled. Not setting up signals.");
717 : : (void)self;
718 : :
719 : : #endif /* ENABLE_PROFILER */
720 : : }
721 : :
722 : : /**
723 : : * gjs_profiler_chain_signal:
724 : : * @context: a #GjsContext with a profiler attached
725 : : * @info: #siginfo_t passed in to signal handler
726 : : *
727 : : * Use this to pass a signal info caught by another signal handler to a
728 : : * GjsProfiler. This might be needed if you have your own complex signal
729 : : * handling system for which GjsProfiler cannot simply add a SIGUSR2 handler.
730 : : *
731 : : * This function should only be called from the JS thread.
732 : : *
733 : : * Returns: %TRUE if the signal was handled.
734 : : */
735 : : bool
736 : 0 : gjs_profiler_chain_signal(GjsContext *context,
737 : : siginfo_t *info)
738 : : {
739 : : #ifdef ENABLE_PROFILER
740 : :
741 [ # # ]: 0 : if (info) {
742 [ # # ]: 0 : if (info->si_signo == SIGPROF) {
743 : 0 : gjs_profiler_sigprof(SIGPROF, info, nullptr);
744 : 0 : return true;
745 : : }
746 : :
747 [ # # ]: 0 : if (info->si_signo == SIGUSR2) {
748 : 0 : gjs_profiler_sigusr2(context);
749 : 0 : return true;
750 : : }
751 : : }
752 : :
753 : : #else // !ENABLE_PROFILER
754 : :
755 : : (void)context;
756 : : (void)info;
757 : :
758 : : #endif /* ENABLE_PROFILER */
759 : :
760 : 0 : return false;
761 : : }
762 : :
763 : : /**
764 : : * gjs_profiler_set_capture_writer:
765 : : * @self: A #GjsProfiler
766 : : * @capture: (nullable): A #SysprofCaptureWriter
767 : : *
768 : : * Set the capture writer to which profiling data is written when the @self
769 : : * is stopped.
770 : : */
771 : 0 : void gjs_profiler_set_capture_writer(GjsProfiler* self, gpointer capture) {
772 : 0 : g_return_if_fail(self);
773 : 0 : g_return_if_fail(!self->running);
774 : :
775 : : #ifdef ENABLE_PROFILER
776 [ # # ]: 0 : g_clear_pointer(&self->target_capture, sysprof_capture_writer_unref);
777 : 0 : self->target_capture =
778 [ # # ]: 0 : capture ? sysprof_capture_writer_ref(
779 : : reinterpret_cast<SysprofCaptureWriter*>(capture))
780 : : : NULL;
781 : : #else
782 : : // Unused in the no-profiler case
783 : : (void)capture;
784 : : #endif
785 : : }
786 : :
787 : : /**
788 : : * gjs_profiler_set_filename:
789 : : * @self: A #GjsProfiler
790 : : * @filename: string containing a filename
791 : : *
792 : : * Set the file to which profiling data is written when the @self is stopped.
793 : : * By default, this is `gjs-$PID.syscap` in the current directory.
794 : : */
795 : : void
796 : 2 : gjs_profiler_set_filename(GjsProfiler *self,
797 : : const char *filename)
798 : : {
799 : 2 : g_return_if_fail(self);
800 : 2 : g_return_if_fail(!self->running);
801 : :
802 : 2 : g_free(self->filename);
803 : 2 : self->filename = g_strdup(filename);
804 : : }
805 : :
806 : 0 : void _gjs_profiler_add_mark(GjsProfiler* self, int64_t time_nsec,
807 : : int64_t duration_nsec, const char* group,
808 : : const char* name, const char* message) {
809 : 0 : g_return_if_fail(self);
810 : 0 : g_return_if_fail(group);
811 : 0 : g_return_if_fail(name);
812 : :
813 : : #ifdef ENABLE_PROFILER
814 [ # # # # ]: 0 : if (self->running && self->capture != nullptr) {
815 : 0 : sysprof_capture_writer_add_mark(self->capture, time_nsec, -1, self->pid,
816 : : duration_nsec, group, name, message);
817 : : }
818 : : #else
819 : : // Unused in the no-profiler case
820 : : (void)time_nsec;
821 : : (void)duration_nsec;
822 : : (void)message;
823 : : #endif
824 : : }
825 : :
826 : 0 : bool _gjs_profiler_sample_gc_memory_info(
827 : : GjsProfiler* self, int64_t gc_counters[Gjs::GCCounters::N_COUNTERS]) {
828 : 0 : g_return_val_if_fail(self, false);
829 : :
830 : : #ifdef ENABLE_PROFILER
831 [ # # # # ]: 0 : if (self->running && self->capture) {
832 : : unsigned ids[Gjs::GCCounters::N_COUNTERS];
833 : : SysprofCaptureCounterValue values[Gjs::GCCounters::N_COUNTERS];
834 : :
835 [ # # ]: 0 : for (size_t ix = 0; ix < Gjs::GCCounters::N_COUNTERS; ix++) {
836 : 0 : ids[ix] = self->gc_counter_base + ix;
837 : 0 : values[ix].v64 = gc_counters[ix];
838 : : }
839 : :
840 : 0 : int64_t now = g_get_monotonic_time() * 1000L;
841 [ # # ]: 0 : if (!sysprof_capture_writer_set_counters(self->capture, now, -1,
842 : : self->pid, ids, values,
843 : : Gjs::GCCounters::N_COUNTERS))
844 : 0 : return false;
845 : : }
846 : : #else
847 : : // Unused in the no-profiler case
848 : : (void)gc_counters;
849 : : #endif
850 : 0 : return true;
851 : : }
852 : :
853 : 0 : void gjs_profiler_set_fd(GjsProfiler* self, int fd) {
854 : 0 : g_return_if_fail(self);
855 : 0 : g_return_if_fail(!self->filename);
856 : 0 : g_return_if_fail(!self->running);
857 : :
858 : : #ifdef ENABLE_PROFILER
859 [ # # ]: 0 : if (self->fd != fd) {
860 [ # # ]: 0 : if (self->fd != -1)
861 : 0 : close(self->fd);
862 : 0 : self->fd = fd;
863 : : }
864 : : #else
865 : : (void)fd; // Unused in the no-profiler case
866 : : #endif
867 : : }
|