This source file includes following definitions.
- saturated_add
- make_log
- approximate_median
- evict_lower_half
- record_backtrace
- add_sample
- handle_profiler_signal
- deliver_profiler_signal
- setup_cpu_timer
- DEFUN
- DEFUN
- DEFUN
- export_log
- DEFUN
- DEFUN
- DEFUN
- DEFUN
- DEFUN
- malloc_probe
- cmpfn_profiler
- hashfn_profiler
- syms_of_profiler
- syms_of_profiler_for_pdumper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 #include <config.h>
21 #include "lisp.h"
22 #include "syssignal.h"
23 #include "systime.h"
24 #include "pdumper.h"
25
26
27
28
29 static EMACS_INT
30 saturated_add (EMACS_INT a, EMACS_INT b)
31 {
32 return min (a + b, MOST_POSITIVE_FIXNUM);
33 }
34
35
36
37 typedef struct Lisp_Hash_Table log_t;
38
39 static Lisp_Object cmpfn_profiler (Lisp_Object, Lisp_Object,
40 struct Lisp_Hash_Table *);
41 static Lisp_Object hashfn_profiler (Lisp_Object, struct Lisp_Hash_Table *);
42
43 static const struct hash_table_test hashtest_profiler =
44 {
45 LISPSYM_INITIALLY (Qprofiler_backtrace_equal),
46 LISPSYM_INITIALLY (Qnil) ,
47 LISPSYM_INITIALLY (Qnil) ,
48 cmpfn_profiler,
49 hashfn_profiler,
50 };
51
52 struct profiler_log {
53 Lisp_Object log;
54 EMACS_INT gc_count;
55 EMACS_INT discarded;
56 };
57
58 static struct profiler_log
59 make_log (void)
60 {
61
62
63
64
65 EMACS_INT heap_size
66 = clip_to_bounds (0, profiler_log_size, MOST_POSITIVE_FIXNUM);
67 ptrdiff_t max_stack_depth
68 = clip_to_bounds (0, profiler_max_stack_depth, PTRDIFF_MAX);;
69 struct profiler_log log
70 = { make_hash_table (hashtest_profiler, heap_size,
71 DEFAULT_REHASH_SIZE,
72 DEFAULT_REHASH_THRESHOLD,
73 Qnil, false),
74 0, 0 };
75 struct Lisp_Hash_Table *h = XHASH_TABLE (log.log);
76
77
78
79 ptrdiff_t i = ASIZE (h->key_and_value) >> 1;
80 while (i > 0)
81 set_hash_value_slot (h, --i, make_nil_vector (max_stack_depth));
82 return log;
83 }
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 static EMACS_INT approximate_median (log_t *log,
102 ptrdiff_t start, ptrdiff_t size)
103 {
104 eassert (size > 0);
105 if (size < 2)
106 return XFIXNUM (HASH_VALUE (log, start));
107 if (size < 3)
108
109
110 return ((XFIXNUM (HASH_VALUE (log, start))
111 + XFIXNUM (HASH_VALUE (log, start + 1)))
112 / 2);
113 else
114 {
115 ptrdiff_t newsize = size / 3;
116 ptrdiff_t start2 = start + newsize;
117 EMACS_INT i1 = approximate_median (log, start, newsize);
118 EMACS_INT i2 = approximate_median (log, start2, newsize);
119 EMACS_INT i3 = approximate_median (log, start2 + newsize,
120 size - 2 * newsize);
121 return (i1 < i2
122 ? (i2 < i3 ? i2 : (i1 < i3 ? i3 : i1))
123 : (i1 < i3 ? i1 : (i2 < i3 ? i3 : i2)));
124 }
125 }
126
127 static void evict_lower_half (struct profiler_log *plog)
128 {
129 log_t *log = XHASH_TABLE (plog->log);
130 ptrdiff_t size = ASIZE (log->key_and_value) / 2;
131 EMACS_INT median = approximate_median (log, 0, size);
132
133 for (ptrdiff_t i = 0; i < size; i++)
134
135
136 if (XFIXNUM (HASH_VALUE (log, i)) <= median)
137 {
138 Lisp_Object key = HASH_KEY (log, i);
139 EMACS_INT count = XFIXNUM (HASH_VALUE (log, i));
140 plog->discarded = saturated_add (plog->discarded, count);
141 {
142 Lisp_Object tmp;
143 XSET_HASH_TABLE (tmp, log);
144 Fremhash (key, tmp);
145 }
146 eassert (BASE_EQ (Qunbound, HASH_KEY (log, i)));
147 eassert (log->next_free == i);
148
149 eassert (VECTORP (key));
150 for (ptrdiff_t j = 0; j < ASIZE (key); j++)
151 ASET (key, j, Qnil);
152
153 set_hash_value_slot (log, i, key);
154 }
155 }
156
157
158
159
160
161 static void
162 record_backtrace (struct profiler_log *plog, EMACS_INT count)
163 {
164 eassert (HASH_TABLE_P (plog->log));
165 log_t *log = XHASH_TABLE (plog->log);
166 if (log->next_free < 0)
167 evict_lower_half (plog);
168 ptrdiff_t index = log->next_free;
169
170
171 Lisp_Object backtrace = HASH_VALUE (log, index);
172 eassert (BASE_EQ (Qunbound, HASH_KEY (log, index)));
173 get_backtrace (backtrace);
174
175 {
176
177
178
179 Lisp_Object hash;
180 ptrdiff_t j = hash_lookup (log, backtrace, &hash);
181 if (j >= 0)
182 {
183 EMACS_INT old_val = XFIXNUM (HASH_VALUE (log, j));
184 EMACS_INT new_val = saturated_add (old_val, count);
185 set_hash_value_slot (log, j, make_fixnum (new_val));
186 }
187 else
188 {
189
190 eassert (0 <= log->next_free);
191 ptrdiff_t j = hash_put (log, backtrace, make_fixnum (count), hash);
192
193
194 eassert (index == j);
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210 }
211 }
212 }
213
214
215
216 #ifdef PROFILER_CPU_SUPPORT
217
218
219
220 #ifdef HAVE_ITIMERSPEC
221 static timer_t profiler_timer;
222 static bool profiler_timer_ok;
223 #endif
224
225
226 static enum profiler_cpu_running
227 { NOT_RUNNING,
228 #ifdef HAVE_ITIMERSPEC
229 TIMER_SETTIME_RUNNING,
230 #endif
231 SETITIMER_RUNNING
232 }
233 profiler_cpu_running;
234
235
236 static struct profiler_log cpu;
237
238
239 static struct profiler_log memory;
240
241
242 static EMACS_INT current_sampling_interval;
243
244
245
246 static void
247 add_sample (struct profiler_log *plog, EMACS_INT count)
248 {
249 if (EQ (backtrace_top_function (), QAutomatic_GC))
250
251
252
253
254
255
256 plog->gc_count = saturated_add (plog->gc_count, count);
257 else
258 record_backtrace (plog, count);
259 }
260
261
262 static void
263 handle_profiler_signal (int signal)
264 {
265 EMACS_INT count = 1;
266 #if defined HAVE_ITIMERSPEC && defined HAVE_TIMER_GETOVERRUN
267 if (profiler_timer_ok)
268 {
269 int overruns = timer_getoverrun (profiler_timer);
270 eassert (overruns >= 0);
271 count += overruns;
272 }
273 #endif
274 add_sample (&cpu, count);
275 }
276
277 static void
278 deliver_profiler_signal (int signal)
279 {
280 deliver_process_signal (signal, handle_profiler_signal);
281 }
282
283 static int
284 setup_cpu_timer (Lisp_Object sampling_interval)
285 {
286 int billion = 1000000000;
287
288 if (! RANGED_FIXNUMP (1, sampling_interval,
289 (TYPE_MAXIMUM (time_t) < EMACS_INT_MAX / billion
290 ? ((EMACS_INT) TYPE_MAXIMUM (time_t) * billion
291 + (billion - 1))
292 : EMACS_INT_MAX)))
293 return -1;
294
295 current_sampling_interval = XFIXNUM (sampling_interval);
296 struct timespec interval
297 = make_timespec (current_sampling_interval / billion,
298 current_sampling_interval % billion);
299 struct sigaction action;
300 emacs_sigaction_init (&action, deliver_profiler_signal);
301 sigaction (SIGPROF, &action, 0);
302
303 #ifdef HAVE_ITIMERSPEC
304 if (! profiler_timer_ok)
305 {
306
307 static clockid_t const system_clock[] = {
308 #ifdef CLOCK_THREAD_CPUTIME_ID
309 CLOCK_THREAD_CPUTIME_ID,
310 #endif
311 #ifdef CLOCK_PROCESS_CPUTIME_ID
312 CLOCK_PROCESS_CPUTIME_ID,
313 #endif
314 #ifdef CLOCK_MONOTONIC
315 CLOCK_MONOTONIC,
316 #endif
317 CLOCK_REALTIME
318 };
319 struct sigevent sigev;
320 sigev.sigev_value.sival_ptr = &profiler_timer;
321 sigev.sigev_signo = SIGPROF;
322 sigev.sigev_notify = SIGEV_SIGNAL;
323
324 for (int i = 0; i < ARRAYELTS (system_clock); i++)
325 if (timer_create (system_clock[i], &sigev, &profiler_timer) == 0)
326 {
327 profiler_timer_ok = true;
328 break;
329 }
330 }
331
332 if (profiler_timer_ok)
333 {
334 struct itimerspec ispec;
335 ispec.it_value = ispec.it_interval = interval;
336 if (timer_settime (profiler_timer, 0, &ispec, 0) == 0)
337 return TIMER_SETTIME_RUNNING;
338 }
339 #endif
340
341 #ifdef HAVE_SETITIMER
342 struct itimerval timer;
343 timer.it_value = timer.it_interval = make_timeval (interval);
344 if (setitimer (ITIMER_PROF, &timer, 0) == 0)
345 return SETITIMER_RUNNING;
346 #endif
347
348 return NOT_RUNNING;
349 }
350
351 DEFUN ("profiler-cpu-start", Fprofiler_cpu_start, Sprofiler_cpu_start,
352 1, 1, 0,
353 doc:
354
355 )
356 (Lisp_Object sampling_interval)
357 {
358 if (profiler_cpu_running)
359 error ("CPU profiler is already running");
360
361 if (NILP (cpu.log))
362 cpu = make_log ();
363
364 int status = setup_cpu_timer (sampling_interval);
365 if (status < 0)
366 {
367 profiler_cpu_running = NOT_RUNNING;
368 error ("Invalid sampling interval");
369 }
370 else
371 {
372 profiler_cpu_running = status;
373 if (! profiler_cpu_running)
374 error ("Unable to start profiler timer");
375 }
376
377 return Qt;
378 }
379
380 DEFUN ("profiler-cpu-stop", Fprofiler_cpu_stop, Sprofiler_cpu_stop,
381 0, 0, 0,
382 doc:
383 )
384 (void)
385 {
386 switch (profiler_cpu_running)
387 {
388 case NOT_RUNNING:
389 return Qnil;
390
391 #ifdef HAVE_ITIMERSPEC
392 case TIMER_SETTIME_RUNNING:
393 {
394 struct itimerspec disable = { 0, };
395 timer_settime (profiler_timer, 0, &disable, 0);
396 }
397 break;
398 #endif
399
400 #ifdef HAVE_SETITIMER
401 case SETITIMER_RUNNING:
402 {
403 struct itimerval disable = { 0, };
404 setitimer (ITIMER_PROF, &disable, 0);
405 }
406 break;
407 #endif
408 }
409
410 signal (SIGPROF, SIG_IGN);
411 profiler_cpu_running = NOT_RUNNING;
412 return Qt;
413 }
414
415 DEFUN ("profiler-cpu-running-p",
416 Fprofiler_cpu_running_p, Sprofiler_cpu_running_p,
417 0, 0, 0,
418 doc: )
419 (void)
420 {
421 return profiler_cpu_running ? Qt : Qnil;
422 }
423
424 static Lisp_Object
425 export_log (struct profiler_log *log)
426 {
427 Lisp_Object result = log->log;
428 if (log->gc_count)
429 Fputhash (CALLN (Fvector, QAutomatic_GC, Qnil),
430 make_fixnum (log->gc_count),
431 result);
432 if (log->discarded)
433 Fputhash (CALLN (Fvector, QDiscarded_Samples, Qnil),
434 make_fixnum (log->discarded),
435 result);
436
437
438
439 if (profiler_cpu_running)
440 *log = make_log ();
441 return result;
442 }
443
444 DEFUN ("profiler-cpu-log", Fprofiler_cpu_log, Sprofiler_cpu_log,
445 0, 0, 0,
446 doc:
447
448
449
450 )
451 (void)
452 {
453 return (export_log (&cpu));
454 }
455 #endif
456
457
458
459
460 bool profiler_memory_running;
461
462 DEFUN ("profiler-memory-start", Fprofiler_memory_start, Sprofiler_memory_start,
463 0, 0, 0,
464 doc:
465
466
467
468 )
469 (void)
470 {
471 if (profiler_memory_running)
472 error ("Memory profiler is already running");
473
474 if (NILP (memory.log))
475 memory = make_log ();
476
477 profiler_memory_running = true;
478
479 return Qt;
480 }
481
482 DEFUN ("profiler-memory-stop",
483 Fprofiler_memory_stop, Sprofiler_memory_stop,
484 0, 0, 0,
485 doc:
486 )
487 (void)
488 {
489 if (!profiler_memory_running)
490 return Qnil;
491 profiler_memory_running = false;
492 return Qt;
493 }
494
495 DEFUN ("profiler-memory-running-p",
496 Fprofiler_memory_running_p, Sprofiler_memory_running_p,
497 0, 0, 0,
498 doc: )
499 (void)
500 {
501 return profiler_memory_running ? Qt : Qnil;
502 }
503
504 DEFUN ("profiler-memory-log",
505 Fprofiler_memory_log, Sprofiler_memory_log,
506 0, 0, 0,
507 doc:
508
509
510
511 )
512 (void)
513 {
514 return (export_log (&memory));
515 }
516
517
518
519
520
521 void
522 malloc_probe (size_t size)
523 {
524 add_sample (&memory, min (size, MOST_POSITIVE_FIXNUM));
525 }
526
527 DEFUN ("function-equal", Ffunction_equal, Sfunction_equal, 2, 2, 0,
528 doc:
529
530 )
531 (Lisp_Object f1, Lisp_Object f2)
532 {
533 bool res;
534 if (EQ (f1, f2))
535 res = true;
536 else if (COMPILEDP (f1) && COMPILEDP (f2))
537 res = EQ (AREF (f1, COMPILED_BYTECODE), AREF (f2, COMPILED_BYTECODE));
538 else if (CONSP (f1) && CONSP (f2) && CONSP (XCDR (f1)) && CONSP (XCDR (f2))
539 && EQ (Qclosure, XCAR (f1))
540 && EQ (Qclosure, XCAR (f2)))
541 res = EQ (XCDR (XCDR (f1)), XCDR (XCDR (f2)));
542 else
543 res = false;
544 return res ? Qt : Qnil;
545 }
546
547 static Lisp_Object
548 cmpfn_profiler (Lisp_Object bt1, Lisp_Object bt2, struct Lisp_Hash_Table *h)
549 {
550 if (EQ (bt1, bt2))
551 return Qt;
552 else if (VECTORP (bt1) && VECTORP (bt2))
553 {
554 ptrdiff_t l = ASIZE (bt1);
555 if (l != ASIZE (bt2))
556 return Qnil;
557 for (ptrdiff_t i = 0; i < l; i++)
558 if (NILP (Ffunction_equal (AREF (bt1, i), AREF (bt2, i))))
559 return Qnil;
560 return Qt;
561 }
562 else
563 return Qnil;
564 }
565
566 static Lisp_Object
567 hashfn_profiler (Lisp_Object bt, struct Lisp_Hash_Table *h)
568 {
569 EMACS_UINT hash;
570 if (VECTORP (bt))
571 {
572 hash = 0;
573 ptrdiff_t l = ASIZE (bt);
574 for (ptrdiff_t i = 0; i < l; i++)
575 {
576 Lisp_Object f = AREF (bt, i);
577 EMACS_UINT hash1
578 = (COMPILEDP (f) ? XHASH (AREF (f, COMPILED_BYTECODE))
579 : (CONSP (f) && CONSP (XCDR (f)) && EQ (Qclosure, XCAR (f)))
580 ? XHASH (XCDR (XCDR (f))) : XHASH (f));
581 hash = sxhash_combine (hash, hash1);
582 }
583 }
584 else
585 hash = XHASH (bt);
586 return make_ufixnum (SXHASH_REDUCE (hash));
587 }
588
589 static void syms_of_profiler_for_pdumper (void);
590
591 void
592 syms_of_profiler (void)
593 {
594 DEFVAR_INT ("profiler-max-stack-depth", profiler_max_stack_depth,
595 doc: );
596 profiler_max_stack_depth = 16;
597 DEFVAR_INT ("profiler-log-size", profiler_log_size,
598 doc:
599
600 );
601 profiler_log_size = 10000;
602
603 DEFSYM (Qprofiler_backtrace_equal, "profiler-backtrace-equal");
604 DEFSYM (QDiscarded_Samples, "Discarded Samples");
605
606 defsubr (&Sfunction_equal);
607
608 #ifdef PROFILER_CPU_SUPPORT
609 profiler_cpu_running = NOT_RUNNING;
610 cpu.log = Qnil;
611 staticpro (&cpu.log);
612 defsubr (&Sprofiler_cpu_start);
613 defsubr (&Sprofiler_cpu_stop);
614 defsubr (&Sprofiler_cpu_running_p);
615 defsubr (&Sprofiler_cpu_log);
616 #endif
617 profiler_memory_running = false;
618 memory.log = Qnil;
619 staticpro (&memory.log);
620 defsubr (&Sprofiler_memory_start);
621 defsubr (&Sprofiler_memory_stop);
622 defsubr (&Sprofiler_memory_running_p);
623 defsubr (&Sprofiler_memory_log);
624
625 pdumper_do_now_and_after_load (syms_of_profiler_for_pdumper);
626 }
627
628 static void
629 syms_of_profiler_for_pdumper (void)
630 {
631 if (dumped_with_pdumper_p ())
632 {
633 #ifdef PROFILER_CPU_SUPPORT
634 cpu.log = Qnil;
635 #endif
636 memory.log = Qnil;
637 }
638 else
639 {
640 #ifdef PROFILER_CPU_SUPPORT
641 eassert (NILP (cpu.log));
642 #endif
643 eassert (NILP (memory.log));
644 }
645
646 }