2 * qsp.c - QEMU Synchronization Profiler
4 * Copyright (C) 2018, Emilio G. Cota <cota@braap.org>
6 * License: GNU GPL, version 2 or later.
7 * See the COPYING file in the top-level directory.
9 * QSP profiles the time spent in synchronization primitives, which can
10 * help diagnose performance problems, e.g. scalability issues when
13 * The primitives currently supported are mutexes, recursive mutexes and
14 * condition variables. Note that not all related functions are intercepted;
15 * instead we profile only those functions that can have a performance impact,
16 * either due to blocking (e.g. cond_wait, mutex_lock) or cache line
17 * contention (e.g. mutex_lock, mutex_trylock).
19 * QSP's design focuses on speed and scalability. This is achieved
20 * by having threads do their profiling entirely on thread-local data.
21 * The appropriate thread-local data is found via a QHT, i.e. a concurrent hash
22 * table. To aggregate data in order to generate a report, we iterate over
23 * all entries in the hash table. Depending on the number of threads and
24 * synchronization objects this might be expensive, but note that it is
25 * very rarely called -- reports are generated only when requested by users.
27 * Reports are generated as a table where each row represents a call site. A
28 * call site is the triplet formed by the __file__ and __LINE__ of the caller
29 * as well as the address of the "object" (i.e. mutex, rec. mutex or condvar)
30 * being operated on. Optionally, call sites that operate on different objects
31 * of the same type can be coalesced, which can be particularly useful when
32 * profiling dynamically-allocated objects.
34 * Alternative designs considered:
36 * - Use an off-the-shelf profiler such as mutrace. This is not a viable option
37 * for us because QEMU has __malloc_hook set (by one of the libraries it
38 * uses); leaving this hook unset is required to avoid deadlock in mutrace.
40 * - Use a glib HT for each thread, protecting each HT with its own lock.
41 * This isn't simpler than the current design, and is 10% slower in the
42 * atomic_add-bench microbenchmark (-m option).
44 * - For reports, just use a binary tree as we aggregate data, instead of having
45 * an intermediate hash table. This would simplify the code only slightly, but
46 * would perform badly if there were many threads and objects to track.
48 * - Wrap operations on qsp entries with RCU read-side critical sections, so
49 * that qsp_reset() can delete entries. Unfortunately, the overhead of calling
50 * rcu_read_lock/unlock slows down atomic_add-bench -m by 24%. Having
51 * a snapshot that is updated on qsp_reset() avoids this overhead.
54 * - Lennart Poettering's mutrace: http://0pointer.de/blog/projects/mutrace.html
55 * - Lozi, David, Thomas, Lawall and Muller. "Remote Core Locking: Migrating
56 * Critical-Section Execution to Improve the Performance of Multithreaded
57 * Applications", USENIX ATC'12.
59 #include "qemu/osdep.h"
60 #include "qemu/thread.h"
61 #include "qemu/timer.h"
64 #include "exec/tb-hash-xx.h"
75 const char *file
; /* i.e. __FILE__; shortened later */
79 typedef struct QSPCallSite QSPCallSite
;
83 const QSPCallSite
*callsite
;
86 unsigned int n_objs
; /* count of coalesced objs; only used for reporting */
87 #ifndef CONFIG_ATOMIC64
89 * If we cannot update the counts atomically, then use a seqlock.
90 * We don't need an associated lock because the updates are thread-local.
95 typedef struct QSPEntry QSPEntry
;
101 typedef struct QSPSnapshot QSPSnapshot
;
103 /* initial sizing for hash tables */
104 #define QSP_INITIAL_SIZE 64
106 /* If this file is moved, QSP_REL_PATH should be updated accordingly */
107 #define QSP_REL_PATH "util/qsp.c"
109 /* this file's full path. Used to present all call sites with relative paths */
110 static size_t qsp_qemu_path_len
;
112 /* the address of qsp_thread gives us a unique 'thread ID' */
113 static __thread
int qsp_thread
;
116 * Call sites are the same for all threads, so we track them in a separate hash
117 * table to save memory.
119 static struct qht qsp_callsite_ht
;
121 static struct qht qsp_ht
;
122 static QSPSnapshot
*qsp_snapshot
;
123 static bool qsp_initialized
, qsp_initializing
;
125 static const char * const qsp_typenames
[] = {
126 [QSP_MUTEX
] = "mutex",
127 [QSP_BQL_MUTEX
] = "BQL mutex",
128 [QSP_REC_MUTEX
] = "rec_mutex",
129 [QSP_CONDVAR
] = "condvar",
132 QemuMutexLockFunc qemu_bql_mutex_lock_func
= qemu_mutex_lock_impl
;
133 QemuMutexLockFunc qemu_mutex_lock_func
= qemu_mutex_lock_impl
;
134 QemuMutexTrylockFunc qemu_mutex_trylock_func
= qemu_mutex_trylock_impl
;
135 QemuRecMutexLockFunc qemu_rec_mutex_lock_func
= qemu_rec_mutex_lock_impl
;
136 QemuRecMutexTrylockFunc qemu_rec_mutex_trylock_func
=
137 qemu_rec_mutex_trylock_impl
;
138 QemuCondWaitFunc qemu_cond_wait_func
= qemu_cond_wait_impl
;
141 * It pays off to _not_ hash callsite->file; hashing a string is slow, and
142 * without it we still get a pretty unique hash.
145 uint32_t do_qsp_callsite_hash(const QSPCallSite
*callsite
, uint64_t a
)
147 uint64_t b
= (uint64_t)(uintptr_t)callsite
->obj
;
148 uint32_t e
= callsite
->line
;
149 uint32_t f
= callsite
->type
;
151 return tb_hash_func7(a
, b
, e
, f
, 0);
155 uint32_t qsp_callsite_hash(const QSPCallSite
*callsite
)
157 return do_qsp_callsite_hash(callsite
, 0);
160 static inline uint32_t do_qsp_entry_hash(const QSPEntry
*entry
, uint64_t a
)
162 return do_qsp_callsite_hash(entry
->callsite
, a
);
165 static uint32_t qsp_entry_hash(const QSPEntry
*entry
)
167 return do_qsp_entry_hash(entry
, (uint64_t)(uintptr_t)entry
->thread_ptr
);
170 static uint32_t qsp_entry_no_thread_hash(const QSPEntry
*entry
)
172 return do_qsp_entry_hash(entry
, 0);
175 /* without the objects we need to hash the file name to get a decent hash */
176 static uint32_t qsp_entry_no_thread_obj_hash(const QSPEntry
*entry
)
178 const QSPCallSite
*callsite
= entry
->callsite
;
179 uint64_t a
= g_str_hash(callsite
->file
);
180 uint64_t b
= callsite
->line
;
181 uint32_t e
= callsite
->type
;
183 return tb_hash_func7(a
, b
, e
, 0, 0);
186 static bool qsp_callsite_cmp(const void *ap
, const void *bp
)
188 const QSPCallSite
*a
= ap
;
189 const QSPCallSite
*b
= bp
;
193 a
->line
== b
->line
&&
194 a
->type
== b
->type
&&
195 (a
->file
== b
->file
|| !strcmp(a
->file
, b
->file
)));
198 static bool qsp_callsite_no_obj_cmp(const void *ap
, const void *bp
)
200 const QSPCallSite
*a
= ap
;
201 const QSPCallSite
*b
= bp
;
204 (a
->line
== b
->line
&&
205 a
->type
== b
->type
&&
206 (a
->file
== b
->file
|| !strcmp(a
->file
, b
->file
)));
209 static bool qsp_entry_no_thread_cmp(const void *ap
, const void *bp
)
211 const QSPEntry
*a
= ap
;
212 const QSPEntry
*b
= bp
;
214 return qsp_callsite_cmp(a
->callsite
, b
->callsite
);
217 static bool qsp_entry_no_thread_obj_cmp(const void *ap
, const void *bp
)
219 const QSPEntry
*a
= ap
;
220 const QSPEntry
*b
= bp
;
222 return qsp_callsite_no_obj_cmp(a
->callsite
, b
->callsite
);
225 static bool qsp_entry_cmp(const void *ap
, const void *bp
)
227 const QSPEntry
*a
= ap
;
228 const QSPEntry
*b
= bp
;
230 return a
->thread_ptr
== b
->thread_ptr
&&
231 qsp_callsite_cmp(a
->callsite
, b
->callsite
);
235 * Normally we'd call this from a constructor function, but we want it to work
236 * via libutil as well.
238 static void qsp_do_init(void)
240 /* make sure this file's path in the tree is up to date with QSP_REL_PATH */
241 g_assert(strstr(__FILE__
, QSP_REL_PATH
));
242 qsp_qemu_path_len
= strlen(__FILE__
) - strlen(QSP_REL_PATH
);
244 qht_init(&qsp_ht
, qsp_entry_cmp
, QSP_INITIAL_SIZE
,
245 QHT_MODE_AUTO_RESIZE
| QHT_MODE_RAW_MUTEXES
);
246 qht_init(&qsp_callsite_ht
, qsp_callsite_cmp
, QSP_INITIAL_SIZE
,
247 QHT_MODE_AUTO_RESIZE
| QHT_MODE_RAW_MUTEXES
);
250 static __attribute__((noinline
)) void qsp_init__slowpath(void)
252 if (atomic_cmpxchg(&qsp_initializing
, false, true) == false) {
254 atomic_set(&qsp_initialized
, true);
256 while (!atomic_read(&qsp_initialized
)) {
262 /* qsp_init() must be called from _all_ exported functions */
263 static inline void qsp_init(void)
265 if (likely(atomic_read(&qsp_initialized
))) {
268 qsp_init__slowpath();
271 static QSPCallSite
*qsp_callsite_find(const QSPCallSite
*orig
)
273 QSPCallSite
*callsite
;
276 hash
= qsp_callsite_hash(orig
);
277 callsite
= qht_lookup(&qsp_callsite_ht
, orig
, hash
);
278 if (callsite
== NULL
) {
279 void *existing
= NULL
;
281 callsite
= g_new(QSPCallSite
, 1);
282 memcpy(callsite
, orig
, sizeof(*callsite
));
283 qht_insert(&qsp_callsite_ht
, callsite
, hash
, &existing
);
284 if (unlikely(existing
)) {
293 qsp_entry_create(struct qht
*ht
, const QSPEntry
*entry
, uint32_t hash
)
296 void *existing
= NULL
;
298 e
= g_new0(QSPEntry
, 1);
299 e
->thread_ptr
= entry
->thread_ptr
;
300 e
->callsite
= qsp_callsite_find(entry
->callsite
);
302 qht_insert(ht
, e
, hash
, &existing
);
303 if (unlikely(existing
)) {
311 qsp_entry_find(struct qht
*ht
, const QSPEntry
*entry
, uint32_t hash
)
315 e
= qht_lookup(ht
, entry
, hash
);
317 e
= qsp_entry_create(ht
, entry
, hash
);
323 * Note: Entries are never removed, so callers do not have to be in an RCU
324 * read-side critical section.
326 static QSPEntry
*qsp_entry_get(const void *obj
, const char *file
, int line
,
329 QSPCallSite callsite
= {
340 orig
.thread_ptr
= &qsp_thread
;
341 orig
.callsite
= &callsite
;
343 hash
= qsp_entry_hash(&orig
);
344 return qsp_entry_find(&qsp_ht
, &orig
, hash
);
348 * @from is in the global hash table; read it atomically if the host
349 * supports it, otherwise use the seqlock.
351 static void qsp_entry_aggregate(QSPEntry
*to
, const QSPEntry
*from
)
353 #ifdef CONFIG_ATOMIC64
354 to
->ns
+= atomic_read__nocheck(&from
->ns
);
355 to
->n_acqs
+= atomic_read__nocheck(&from
->n_acqs
);
357 unsigned int version
;
361 version
= seqlock_read_begin(&from
->sequence
);
362 ns
= atomic_read__nocheck(&from
->ns
);
363 n_acqs
= atomic_read__nocheck(&from
->n_acqs
);
364 } while (seqlock_read_retry(&from
->sequence
, version
));
367 to
->n_acqs
+= n_acqs
;
372 * @e is in the global hash table; it is only written to by the current thread,
373 * so we write to it atomically (as in "write once") to prevent torn reads.
374 * If the host doesn't support u64 atomics, use the seqlock.
376 static inline void do_qsp_entry_record(QSPEntry
*e
, int64_t delta
, bool acq
)
378 #ifndef CONFIG_ATOMIC64
379 seqlock_write_begin(&e
->sequence
);
381 atomic_set__nocheck(&e
->ns
, e
->ns
+ delta
);
383 atomic_set__nocheck(&e
->n_acqs
, e
->n_acqs
+ 1);
385 #ifndef CONFIG_ATOMIC64
386 seqlock_write_end(&e
->sequence
);
390 static inline void qsp_entry_record(QSPEntry
*e
, int64_t delta
)
392 do_qsp_entry_record(e
, delta
, true);
395 #define QSP_GEN_VOID(type_, qsp_t_, func_, impl_) \
396 static void func_(type_ *obj, const char *file, int line) \
402 impl_(obj, file, line); \
405 e = qsp_entry_get(obj, file, line, qsp_t_); \
406 qsp_entry_record(e, t1 - t0); \
409 #define QSP_GEN_RET1(type_, qsp_t_, func_, impl_) \
410 static int func_(type_ *obj, const char *file, int line) \
417 err = impl_(obj, file, line); \
420 e = qsp_entry_get(obj, file, line, qsp_t_); \
421 do_qsp_entry_record(e, t1 - t0, !err); \
425 QSP_GEN_VOID(QemuMutex
, QSP_BQL_MUTEX
, qsp_bql_mutex_lock
, qemu_mutex_lock_impl
)
426 QSP_GEN_VOID(QemuMutex
, QSP_MUTEX
, qsp_mutex_lock
, qemu_mutex_lock_impl
)
427 QSP_GEN_RET1(QemuMutex
, QSP_MUTEX
, qsp_mutex_trylock
, qemu_mutex_trylock_impl
)
429 QSP_GEN_VOID(QemuRecMutex
, QSP_REC_MUTEX
, qsp_rec_mutex_lock
,
430 qemu_rec_mutex_lock_impl
)
431 QSP_GEN_RET1(QemuRecMutex
, QSP_REC_MUTEX
, qsp_rec_mutex_trylock
,
432 qemu_rec_mutex_trylock_impl
)
438 qsp_cond_wait(QemuCond
*cond
, QemuMutex
*mutex
, const char *file
, int line
)
444 qemu_cond_wait_impl(cond
, mutex
, file
, line
);
447 e
= qsp_entry_get(cond
, file
, line
, QSP_CONDVAR
);
448 qsp_entry_record(e
, t1
- t0
);
451 bool qsp_is_enabled(void)
453 return atomic_read(&qemu_mutex_lock_func
) == qsp_mutex_lock
;
456 void qsp_enable(void)
458 atomic_set(&qemu_mutex_lock_func
, qsp_mutex_lock
);
459 atomic_set(&qemu_mutex_trylock_func
, qsp_mutex_trylock
);
460 atomic_set(&qemu_bql_mutex_lock_func
, qsp_bql_mutex_lock
);
461 atomic_set(&qemu_rec_mutex_lock_func
, qsp_rec_mutex_lock
);
462 atomic_set(&qemu_rec_mutex_trylock_func
, qsp_rec_mutex_trylock
);
463 atomic_set(&qemu_cond_wait_func
, qsp_cond_wait
);
466 void qsp_disable(void)
468 atomic_set(&qemu_mutex_lock_func
, qemu_mutex_lock_impl
);
469 atomic_set(&qemu_mutex_trylock_func
, qemu_mutex_trylock_impl
);
470 atomic_set(&qemu_bql_mutex_lock_func
, qemu_mutex_lock_impl
);
471 atomic_set(&qemu_rec_mutex_lock_func
, qemu_rec_mutex_lock_impl
);
472 atomic_set(&qemu_rec_mutex_trylock_func
, qemu_rec_mutex_trylock_impl
);
473 atomic_set(&qemu_cond_wait_func
, qemu_cond_wait_impl
);
476 static gint
qsp_tree_cmp(gconstpointer ap
, gconstpointer bp
, gpointer up
)
478 const QSPEntry
*a
= ap
;
479 const QSPEntry
*b
= bp
;
480 enum QSPSortBy sort_by
= *(enum QSPSortBy
*)up
;
481 const QSPCallSite
*ca
;
482 const QSPCallSite
*cb
;
485 case QSP_SORT_BY_TOTAL_WAIT_TIME
:
488 } else if (a
->ns
< b
->ns
) {
492 case QSP_SORT_BY_AVG_WAIT_TIME
:
494 double avg_a
= a
->n_acqs
? a
->ns
/ a
->n_acqs
: 0;
495 double avg_b
= b
->n_acqs
? b
->ns
/ b
->n_acqs
: 0;
499 } else if (avg_a
< avg_b
) {
505 g_assert_not_reached();
510 /* Break the tie with the object's address */
511 if (ca
->obj
< cb
->obj
) {
513 } else if (ca
->obj
> cb
->obj
) {
518 /* same obj. Break the tie with the callsite's file */
519 cmp
= strcmp(ca
->file
, cb
->file
);
523 /* same callsite file. Break the tie with the callsite's line */
524 g_assert(ca
->line
!= cb
->line
);
525 if (ca
->line
< cb
->line
) {
527 } else if (ca
->line
> cb
->line
) {
530 /* break the tie with the callsite's type */
531 return cb
->type
- ca
->type
;
536 static void qsp_sort(struct qht
*ht
, void *p
, uint32_t h
, void *userp
)
541 g_tree_insert(tree
, e
, NULL
);
544 static void qsp_aggregate(struct qht
*global_ht
, void *p
, uint32_t h
, void *up
)
547 const QSPEntry
*e
= p
;
551 hash
= qsp_entry_no_thread_hash(e
);
552 agg
= qsp_entry_find(ht
, e
, hash
);
553 qsp_entry_aggregate(agg
, e
);
556 static void qsp_iter_diff(struct qht
*orig
, void *p
, uint32_t hash
, void *htp
)
558 struct qht
*ht
= htp
;
562 new = qht_lookup(ht
, old
, hash
);
563 /* entries are never deleted, so we must have this one */
564 g_assert(new != NULL
);
565 /* our reading of the stats happened after the snapshot was taken */
566 g_assert(new->n_acqs
>= old
->n_acqs
);
567 g_assert(new->ns
>= old
->ns
);
569 new->n_acqs
-= old
->n_acqs
;
572 /* No point in reporting an empty entry */
573 if (new->n_acqs
== 0 && new->ns
== 0) {
574 bool removed
= qht_remove(ht
, new, hash
);
581 static void qsp_diff(struct qht
*orig
, struct qht
*new)
583 qht_iter(orig
, qsp_iter_diff
, new);
587 qsp_iter_callsite_coalesce(struct qht
*orig
, void *p
, uint32_t h
, void *htp
)
589 struct qht
*ht
= htp
;
594 hash
= qsp_entry_no_thread_obj_hash(old
);
595 e
= qht_lookup(ht
, old
, hash
);
597 e
= qsp_entry_create(ht
, old
, hash
);
599 } else if (e
->callsite
->obj
!= old
->callsite
->obj
) {
603 e
->n_acqs
+= old
->n_acqs
;
606 static void qsp_ht_delete(struct qht
*ht
, void *p
, uint32_t h
, void *htp
)
611 static void qsp_mktree(GTree
*tree
, bool callsite_coalesce
)
614 struct qht ht
, coalesce_ht
;
618 * First, see if there's a prior snapshot, so that we read the global hash
619 * table _after_ the snapshot has been created, which guarantees that
620 * the entries we'll read will be a superset of the snapshot's entries.
622 * We must remain in an RCU read-side critical section until we're done
626 snap
= atomic_rcu_read(&qsp_snapshot
);
628 /* Aggregate all results from the global hash table into a local one */
629 qht_init(&ht
, qsp_entry_no_thread_cmp
, QSP_INITIAL_SIZE
,
630 QHT_MODE_AUTO_RESIZE
| QHT_MODE_RAW_MUTEXES
);
631 qht_iter(&qsp_ht
, qsp_aggregate
, &ht
);
633 /* compute the difference wrt the snapshot, if any */
635 qsp_diff(&snap
->ht
, &ht
);
637 /* done with the snapshot; RCU can reclaim it */
641 if (callsite_coalesce
) {
642 qht_init(&coalesce_ht
, qsp_entry_no_thread_obj_cmp
, QSP_INITIAL_SIZE
,
643 QHT_MODE_AUTO_RESIZE
| QHT_MODE_RAW_MUTEXES
);
644 qht_iter(&ht
, qsp_iter_callsite_coalesce
, &coalesce_ht
);
646 /* free the previous hash table, and point htp to coalesce_ht */
647 qht_iter(&ht
, qsp_ht_delete
, NULL
);
652 /* sort the hash table elements by using a tree */
653 qht_iter(htp
, qsp_sort
, tree
);
655 /* free the hash table, but keep the elements (those are in the tree now) */
659 /* free string with g_free */
660 static char *qsp_at(const QSPCallSite
*callsite
)
662 GString
*s
= g_string_new(NULL
);
663 const char *shortened
;
665 /* remove the absolute path to qemu */
666 if (unlikely(strlen(callsite
->file
) < qsp_qemu_path_len
)) {
667 shortened
= callsite
->file
;
669 shortened
= callsite
->file
+ qsp_qemu_path_len
;
671 g_string_append_printf(s
, "%s:%u", shortened
, callsite
->line
);
672 return g_string_free(s
, FALSE
);
675 struct QSPReportEntry
{
678 const char *typename
;
684 typedef struct QSPReportEntry QSPReportEntry
;
687 QSPReportEntry
*entries
;
689 size_t max_n_entries
;
691 typedef struct QSPReport QSPReport
;
693 static gboolean
qsp_tree_report(gpointer key
, gpointer value
, gpointer udata
)
695 const QSPEntry
*e
= key
;
696 QSPReport
*report
= udata
;
697 QSPReportEntry
*entry
;
699 if (report
->n_entries
== report
->max_n_entries
) {
702 entry
= &report
->entries
[report
->n_entries
];
705 entry
->obj
= e
->callsite
->obj
;
706 entry
->n_objs
= e
->n_objs
;
707 entry
->callsite_at
= qsp_at(e
->callsite
);
708 entry
->typename
= qsp_typenames
[e
->callsite
->type
];
709 entry
->time_s
= e
->ns
* 1e-9;
710 entry
->n_acqs
= e
->n_acqs
;
711 entry
->ns_avg
= e
->n_acqs
? e
->ns
/ e
->n_acqs
: 0;
716 pr_report(const QSPReport
*rep
, FILE *f
, fprintf_function pr
)
720 int callsite_len
= 0;
725 /* find out the maximum length of all 'callsite' fields */
726 for (i
= 0; i
< rep
->n_entries
; i
++) {
727 const QSPReportEntry
*e
= &rep
->entries
[i
];
728 size_t len
= strlen(e
->callsite_at
);
735 callsite_len
= MAX(max_len
, strlen("Call site"));
736 /* white space to leave to the right of "Call site" */
737 callsite_rspace
= callsite_len
- strlen("Call site");
739 pr(f
, "Type Object Call site%*s Wait Time (s) "
740 " Count Average (us)\n", callsite_rspace
, "");
742 /* build a horizontal rule with dashes */
743 n_dashes
= 79 + callsite_rspace
;
744 dashes
= g_malloc(n_dashes
+ 1);
745 memset(dashes
, '-', n_dashes
);
746 dashes
[n_dashes
] = '\0';
747 pr(f
, "%s\n", dashes
);
749 for (i
= 0; i
< rep
->n_entries
; i
++) {
750 const QSPReportEntry
*e
= &rep
->entries
[i
];
751 GString
*s
= g_string_new(NULL
);
753 g_string_append_printf(s
, "%-9s ", e
->typename
);
755 g_string_append_printf(s
, "[%12u]", e
->n_objs
);
757 g_string_append_printf(s
, "%14p", e
->obj
);
759 g_string_append_printf(s
, " %s%*s %13.5f %12" PRIu64
" %12.2f\n",
761 callsite_len
- (int)strlen(e
->callsite_at
), "",
762 e
->time_s
, e
->n_acqs
, e
->ns_avg
* 1e-3);
764 g_string_free(s
, TRUE
);
767 pr(f
, "%s\n", dashes
);
771 static void report_destroy(QSPReport
*rep
)
775 for (i
= 0; i
< rep
->n_entries
; i
++) {
776 QSPReportEntry
*e
= &rep
->entries
[i
];
778 g_free(e
->callsite_at
);
780 g_free(rep
->entries
);
783 void qsp_report(FILE *f
, fprintf_function cpu_fprintf
, size_t max
,
784 enum QSPSortBy sort_by
, bool callsite_coalesce
)
786 GTree
*tree
= g_tree_new_full(qsp_tree_cmp
, &sort_by
, g_free
, NULL
);
791 rep
.entries
= g_new0(QSPReportEntry
, max
);
793 rep
.max_n_entries
= max
;
795 qsp_mktree(tree
, callsite_coalesce
);
796 g_tree_foreach(tree
, qsp_tree_report
, &rep
);
797 g_tree_destroy(tree
);
799 pr_report(&rep
, f
, cpu_fprintf
);
800 report_destroy(&rep
);
803 static void qsp_snapshot_destroy(QSPSnapshot
*snap
)
805 qht_iter(&snap
->ht
, qsp_ht_delete
, NULL
);
806 qht_destroy(&snap
->ht
);
812 QSPSnapshot
*new = g_new(QSPSnapshot
, 1);
817 qht_init(&new->ht
, qsp_entry_cmp
, QSP_INITIAL_SIZE
,
818 QHT_MODE_AUTO_RESIZE
| QHT_MODE_RAW_MUTEXES
);
820 /* take a snapshot of the current state */
821 qht_iter(&qsp_ht
, qsp_aggregate
, &new->ht
);
823 /* replace the previous snapshot, if any */
824 old
= atomic_xchg(&qsp_snapshot
, new);
826 call_rcu(old
, qsp_snapshot_destroy
, rcu
);