diff --git a/src/libsysprof-analyze/sysprof-callgraph-private.h b/src/libsysprof-analyze/sysprof-callgraph-private.h index b83ba7f4..92f70640 100644 --- a/src/libsysprof-analyze/sysprof-callgraph-private.h +++ b/src/libsysprof-analyze/sysprof-callgraph-private.h @@ -57,6 +57,8 @@ struct _SysprofCallgraph GHashTable *symbol_to_summary; GPtrArray *symbols; + SysprofCallgraphFlags flags; + gsize augment_size; SysprofAugmentationFunc augment_func; gpointer augment_func_data; @@ -66,6 +68,7 @@ struct _SysprofCallgraph }; void _sysprof_callgraph_new_async (SysprofDocument *document, + SysprofCallgraphFlags flags, GListModel *traceables, gsize augment_size, SysprofAugmentationFunc augment_func, diff --git a/src/libsysprof-analyze/sysprof-callgraph.c b/src/libsysprof-analyze/sysprof-callgraph.c index 114362a4..555345c6 100644 --- a/src/libsysprof-analyze/sysprof-callgraph.c +++ b/src/libsysprof-analyze/sysprof-callgraph.c @@ -284,17 +284,19 @@ sysprof_callgraph_add_traceable (SysprofCallgraph *self, guint stack_depth; guint n_symbols; int pid; + int tid; g_assert (SYSPROF_IS_CALLGRAPH (self)); g_assert (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable)); pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (traceable)); + tid = sysprof_document_traceable_get_thread_id (traceable); stack_depth = sysprof_document_traceable_get_stack_depth (traceable); if (stack_depth == 0 || stack_depth > MAX_STACK_DEPTH) return; - symbols = g_newa (SysprofSymbol *, stack_depth + 3); + symbols = g_newa (SysprofSymbol *, stack_depth + 4); n_symbols = sysprof_document_symbolize_traceable (self->document, traceable, symbols, @@ -323,6 +325,13 @@ sysprof_callgraph_add_traceable (SysprofCallgraph *self, */ if (final_context == SYSPROF_ADDRESS_CONTEXT_KERNEL) symbols[n_symbols++] = _sysprof_document_kernel_symbol (self->document); + + /* If the user requested thread-ids within each process, then + * insert a symbol for that before the real stacks. + */ + if ((self->flags & SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS) != 0) + symbols[n_symbols++] = _sysprof_document_thread_symbol (self->document, pid, tid); + symbols[n_symbols++] = _sysprof_document_process_symbol (self->document, pid); symbols[n_symbols++] = everything; @@ -364,6 +373,7 @@ sysprof_callgraph_new_worker (GTask *task, void _sysprof_callgraph_new_async (SysprofDocument *document, + SysprofCallgraphFlags flags, GListModel *traceables, gsize augment_size, SysprofAugmentationFunc augment_func, @@ -387,6 +397,7 @@ _sysprof_callgraph_new_async (SysprofDocument *document, summary_free = (GDestroyNotify)sysprof_callgraph_summary_free_self; self = g_object_new (SYSPROF_TYPE_CALLGRAPH, NULL); + self->flags = flags; self->document = g_object_ref (document); self->traceables = g_object_ref (traceables); self->augment_size = augment_size; diff --git a/src/libsysprof-analyze/sysprof-callgraph.h b/src/libsysprof-analyze/sysprof-callgraph.h index e5057687..2873effa 100644 --- a/src/libsysprof-analyze/sysprof-callgraph.h +++ b/src/libsysprof-analyze/sysprof-callgraph.h @@ -62,6 +62,14 @@ typedef void (*SysprofAugmentationFunc) (SysprofCallgraph *callgraph, gboolean summarize, gpointer user_data); +typedef enum _SysprofCallgraphFlags +{ + SYSPROF_CALLGRAPH_FLAGS_NONE = 0, + SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS = 1 << 1, +} SysprofCallgraphFlags; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_callgraph_flags_get_type (void) G_GNUC_CONST; SYSPROF_AVAILABLE_IN_ALL GListModel *sysprof_callgraph_list_symbols (SysprofCallgraph *self); SYSPROF_AVAILABLE_IN_ALL diff --git a/src/libsysprof-analyze/sysprof-document-allocation.c b/src/libsysprof-analyze/sysprof-document-allocation.c index 773bc7f2..c1091560 100644 --- a/src/libsysprof-analyze/sysprof-document-allocation.c +++ b/src/libsysprof-analyze/sysprof-document-allocation.c @@ -41,7 +41,7 @@ enum { PROP_IS_FREE, PROP_SIZE, PROP_STACK_DEPTH, - PROP_TID, + PROP_THREAD_ID, N_PROPS }; @@ -76,12 +76,26 @@ sysprof_document_allocation_get_stack_addresses (SysprofDocumentTraceable *trace return depth; } +static int +sysprof_document_allocation_get_thread_id (SysprofDocumentTraceable *traceable) +{ + SysprofDocumentAllocation *self = (SysprofDocumentAllocation *)traceable; + const SysprofCaptureAllocation *allocation; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_ALLOCATION (self), 0); + + allocation = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureAllocation); + + return SYSPROF_DOCUMENT_FRAME_INT32 (self, allocation->tid); +} + static void traceable_iface_init (SysprofDocumentTraceableInterface *iface) { iface->get_stack_depth = sysprof_document_allocation_get_stack_depth; iface->get_stack_address = sysprof_document_allocation_get_stack_address; iface->get_stack_addresses = sysprof_document_allocation_get_stack_addresses; + iface->get_thread_id = sysprof_document_allocation_get_thread_id; } G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofDocumentAllocation, sysprof_document_allocation, SYSPROF_TYPE_DOCUMENT_FRAME, @@ -115,8 +129,8 @@ sysprof_document_allocation_get_property (GObject *object, g_value_set_uint (value, sysprof_document_traceable_get_stack_depth (SYSPROF_DOCUMENT_TRACEABLE (self))); break; - case PROP_TID: - g_value_set_int (value, sysprof_document_allocation_get_tid (self)); + case PROP_THREAD_ID: + g_value_set_int (value, sysprof_document_traceable_get_thread_id (SYSPROF_DOCUMENT_TRACEABLE (self))); break; default: @@ -132,17 +146,17 @@ sysprof_document_allocation_class_init (SysprofDocumentAllocationClass *klass) object_class->get_property = sysprof_document_allocation_get_property; /** - * SysprofDocumentAllocation:tid: + * SysprofDocumentAllocation:thread-id: * - * The task-id or thread-id of the thread which was traced. + * The thread-id where the stack was traced. * - * On Linux, this is generally set to the value of the gettid() syscall. + * On Linux, this is generally set to the value of `gettid()`. * * Since: 45 */ - properties [PROP_TID] = - g_param_spec_int ("tid", NULL, NULL, - G_MININT32, G_MAXINT32, 0, + properties [PROP_THREAD_ID] = + g_param_spec_int ("thread-id", NULL, NULL, + -1, G_MAXINT32, 0, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** @@ -226,18 +240,6 @@ sysprof_document_allocation_get_size (SysprofDocumentAllocation *self) return SYSPROF_DOCUMENT_FRAME_INT64 (self, allocation->alloc_size); } -int -sysprof_document_allocation_get_tid (SysprofDocumentAllocation *self) -{ - const SysprofCaptureAllocation *allocation; - - g_return_val_if_fail (SYSPROF_IS_DOCUMENT_ALLOCATION (self), 0); - - allocation = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureAllocation); - - return SYSPROF_DOCUMENT_FRAME_INT32 (self, allocation->tid); -} - gboolean sysprof_document_allocation_is_free (SysprofDocumentAllocation *self) { diff --git a/src/libsysprof-analyze/sysprof-document-private.h b/src/libsysprof-analyze/sysprof-document-private.h index 2f54d047..11906ab3 100644 --- a/src/libsysprof-analyze/sysprof-document-private.h +++ b/src/libsysprof-analyze/sysprof-document-private.h @@ -58,6 +58,9 @@ GRefString *_sysprof_document_ref_string (SysprofDocument *self, EggBitset *_sysprof_document_traceables (SysprofDocument *self); SysprofSymbol *_sysprof_document_process_symbol (SysprofDocument *self, int pid); +SysprofSymbol *_sysprof_document_thread_symbol (SysprofDocument *self, + int pid, + int tid); SysprofSymbol *_sysprof_document_kernel_symbol (SysprofDocument *self); G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-document-sample.c b/src/libsysprof-analyze/sysprof-document-sample.c index 73ba990c..b6b48b2a 100644 --- a/src/libsysprof-analyze/sysprof-document-sample.c +++ b/src/libsysprof-analyze/sysprof-document-sample.c @@ -38,7 +38,7 @@ struct _SysprofDocumentSampleClass enum { PROP_0, PROP_STACK_DEPTH, - PROP_TID, + PROP_THREAD_ID, N_PROPS }; @@ -73,12 +73,26 @@ sysprof_document_sample_get_stack_addresses (SysprofDocumentTraceable *traceable return depth; } +static int +sysprof_document_sample_get_thread_id (SysprofDocumentTraceable *traceable) +{ + SysprofDocumentSample *self = (SysprofDocumentSample *)traceable; + const SysprofCaptureSample *sample; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_SAMPLE (self), -1); + + sample = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureSample); + + return SYSPROF_DOCUMENT_FRAME_INT32 (self, sample->tid); +} + static void traceable_iface_init (SysprofDocumentTraceableInterface *iface) { iface->get_stack_depth = sysprof_document_sample_get_stack_depth; iface->get_stack_address = sysprof_document_sample_get_stack_address; iface->get_stack_addresses = sysprof_document_sample_get_stack_addresses; + iface->get_thread_id = sysprof_document_sample_get_thread_id; } G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofDocumentSample, sysprof_document_sample, SYSPROF_TYPE_DOCUMENT_FRAME, @@ -100,8 +114,8 @@ sysprof_document_sample_get_property (GObject *object, g_value_set_uint (value, sysprof_document_traceable_get_stack_depth (SYSPROF_DOCUMENT_TRACEABLE (self))); break; - case PROP_TID: - g_value_set_int (value, sysprof_document_sample_get_tid (self)); + case PROP_THREAD_ID: + g_value_set_int (value, sysprof_document_sample_get_thread_id (SYSPROF_DOCUMENT_TRACEABLE (self))); break; default: @@ -117,17 +131,17 @@ sysprof_document_sample_class_init (SysprofDocumentSampleClass *klass) object_class->get_property = sysprof_document_sample_get_property; /** - * SysprofDocumentSample:tid: + * SysprofDocumentSample:thread-id: * - * The task-id or thread-id of the thread which was sampled. + * The thread-id where the sample occurred. * - * On Linux, this is generally set to the value of the gettid() syscall. + * On Linux, this is generally set to the value of gettid(). * * Since: 45 */ - properties [PROP_TID] = - g_param_spec_int ("tid", NULL, NULL, - G_MININT32, G_MAXINT32, 0, + properties [PROP_THREAD_ID] = + g_param_spec_int ("thread-id", NULL, NULL, + -1, G_MAXINT32, 0, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); /** @@ -149,15 +163,3 @@ static void sysprof_document_sample_init (SysprofDocumentSample *self) { } - -int -sysprof_document_sample_get_tid (SysprofDocumentSample *self) -{ - const SysprofCaptureSample *sample; - - g_return_val_if_fail (SYSPROF_IS_DOCUMENT_SAMPLE (self), 0); - - sample = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureSample); - - return SYSPROF_DOCUMENT_FRAME_INT32 (self, sample->tid); -} diff --git a/src/libsysprof-analyze/sysprof-document-traceable.c b/src/libsysprof-analyze/sysprof-document-traceable.c index 620bc0cc..d6fca4d7 100644 --- a/src/libsysprof-analyze/sysprof-document-traceable.c +++ b/src/libsysprof-analyze/sysprof-document-traceable.c @@ -43,6 +43,18 @@ sysprof_document_traceable_default_init (SysprofDocumentTraceableInterface *ifac g_param_spec_uint ("stack-depth", NULL, NULL, 0, G_MAXUINT16, 0, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + /** + * SysprofDocumentTraceable:thread-id: + * + * The "thread-id" property contains the thread identifier for the traceable. + * + * Since: 45 + */ + g_object_interface_install_property (iface, + g_param_spec_int ("thread-id", NULL, NULL, + -1, G_MAXINT, -1, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); } guint @@ -58,6 +70,12 @@ sysprof_document_traceable_get_stack_address (SysprofDocumentTraceable *self, return SYSPROF_DOCUMENT_TRACEABLE_GET_IFACE (self)->get_stack_address (self, position); } +int +sysprof_document_traceable_get_thread_id (SysprofDocumentTraceable *self) +{ + return SYSPROF_DOCUMENT_TRACEABLE_GET_IFACE (self)->get_thread_id (self); +} + guint sysprof_document_traceable_get_stack_addresses (SysprofDocumentTraceable *self, guint64 *addresses, diff --git a/src/libsysprof-analyze/sysprof-document-traceable.h b/src/libsysprof-analyze/sysprof-document-traceable.h index 459990ba..4b55dec7 100644 --- a/src/libsysprof-analyze/sysprof-document-traceable.h +++ b/src/libsysprof-analyze/sysprof-document-traceable.h @@ -40,6 +40,7 @@ struct _SysprofDocumentTraceableInterface guint (*get_stack_addresses) (SysprofDocumentTraceable *self, guint64 *addresses, guint n_addresses); + int (*get_thread_id) (SysprofDocumentTraceable *self); }; SYSPROF_AVAILABLE_IN_ALL @@ -51,5 +52,7 @@ SYSPROF_AVAILABLE_IN_ALL guint sysprof_document_traceable_get_stack_addresses (SysprofDocumentTraceable *self, guint64 *addresses, guint n_addresses); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_document_traceable_get_thread_id (SysprofDocumentTraceable *self); G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-document.c b/src/libsysprof-analyze/sysprof-document.c index 4075e4a2..405183f5 100644 --- a/src/libsysprof-analyze/sysprof-document.c +++ b/src/libsysprof-analyze/sysprof-document.c @@ -78,6 +78,7 @@ struct _SysprofDocument GHashTable *files_first_position; GHashTable *pid_to_process_info; + GHashTable *tid_to_symbol; SysprofMountNamespace *mount_namespace; @@ -269,6 +270,7 @@ sysprof_document_init (SysprofDocument *self) self->files_first_position = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); self->pid_to_process_info = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)sysprof_process_info_unref); + self->tid_to_symbol = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); self->mount_namespace = sysprof_mount_namespace_new (); } @@ -1190,6 +1192,7 @@ sysprof_document_callgraph_cb (GObject *object, /** * sysprof_document_callgraph_async: * @self: a #SysprofDocument + * @flags: flags for generating the callgraph * @traceables: a list model of traceables for the callgraph * @augment_size: the size of data to reserve for augmentation in * the callgraph. @@ -1212,6 +1215,7 @@ sysprof_document_callgraph_cb (GObject *object, */ void sysprof_document_callgraph_async (SysprofDocument *self, + SysprofCallgraphFlags flags, GListModel *traceables, gsize augment_size, SysprofAugmentationFunc augment_func, @@ -1231,6 +1235,7 @@ sysprof_document_callgraph_async (SysprofDocument *self, g_task_set_source_tag (task, sysprof_document_callgraph_async); _sysprof_callgraph_new_async (self, + flags, traceables, augment_size, augment_func, @@ -1282,6 +1287,39 @@ _sysprof_document_process_symbol (SysprofDocument *self, return info->fallback_symbol; } +SysprofSymbol * +_sysprof_document_thread_symbol (SysprofDocument *self, + int pid, + int tid) +{ + SysprofSymbol *ret; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + if (!(ret = g_hash_table_lookup (self->tid_to_symbol, GINT_TO_POINTER (tid)))) + { + char pidstr[32]; + char tidstr[32]; + + g_snprintf (pidstr, sizeof pidstr, "(%d)", pid); + + if (tid == pid) + g_snprintf (tidstr, sizeof tidstr, "Thread-%d (Main)", tid); + else + g_snprintf (tidstr, sizeof tidstr, "Thread-%d", tid); + + ret = _sysprof_symbol_new (g_ref_string_new (tidstr), + NULL, + g_ref_string_new (pidstr), + 0, 0, + SYSPROF_SYMBOL_KIND_THREAD); + + g_hash_table_insert (self->tid_to_symbol, GINT_TO_POINTER (tid), ret); + } + + return ret; +} + SysprofSymbol * _sysprof_document_kernel_symbol (SysprofDocument *self) { diff --git a/src/libsysprof-analyze/sysprof-document.h b/src/libsysprof-analyze/sysprof-document.h index 735c7450..594e92ee 100644 --- a/src/libsysprof-analyze/sysprof-document.h +++ b/src/libsysprof-analyze/sysprof-document.h @@ -66,6 +66,7 @@ guint sysprof_document_symbolize_traceable (SysprofDocument SysprofAddressContext *final_context); SYSPROF_AVAILABLE_IN_ALL void sysprof_document_callgraph_async (SysprofDocument *self, + SysprofCallgraphFlags flags, GListModel *traceables, gsize augment_size, SysprofAugmentationFunc augment_func, diff --git a/src/libsysprof-analyze/sysprof-symbol.h b/src/libsysprof-analyze/sysprof-symbol.h index 059c87ff..50b39129 100644 --- a/src/libsysprof-analyze/sysprof-symbol.h +++ b/src/libsysprof-analyze/sysprof-symbol.h @@ -31,6 +31,7 @@ typedef enum _SysprofSymbolKind { SYSPROF_SYMBOL_KIND_ROOT = 1, SYSPROF_SYMBOL_KIND_PROCESS, + SYSPROF_SYMBOL_KIND_THREAD, SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH, SYSPROF_SYMBOL_KIND_USER, SYSPROF_SYMBOL_KIND_KERNEL, diff --git a/src/libsysprof-analyze/tests/test-callgraph.c b/src/libsysprof-analyze/tests/test-callgraph.c index 49ca0bea..43a1d310 100644 --- a/src/libsysprof-analyze/tests/test-callgraph.c +++ b/src/libsysprof-analyze/tests/test-callgraph.c @@ -33,8 +33,10 @@ typedef struct _Augment } Augment; static char *kallsyms_path; +static gboolean include_threads; static const GOptionEntry entries[] = { { "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms_path, "The path to kallsyms to use for decoding", "PATH" }, + { "threads", 't', 0, G_OPTION_ARG_NONE, &include_threads, "Include threads in the stack traces" }, { 0 } }; @@ -122,6 +124,7 @@ main (int argc, g_autoptr(SysprofMultiSymbolizer) multi = NULL; g_autoptr(GListModel) samples = NULL; g_autoptr(GError) error = NULL; + SysprofCallgraphFlags flags = 0; setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); @@ -159,7 +162,11 @@ main (int argc, samples = sysprof_document_list_samples (document); + if (include_threads) + flags |= SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS; + sysprof_document_callgraph_async (document, + flags, samples, sizeof (Augment), augment_cb, NULL, NULL, diff --git a/src/libsysprof-gtk/style.css b/src/libsysprof-gtk/style.css index 4295aab5..68498553 100644 --- a/src/libsysprof-gtk/style.css +++ b/src/libsysprof-gtk/style.css @@ -43,11 +43,13 @@ callgraphview symbol { } callgraphview symbol.context-switch, callgraphview symbol.process, +callgraphview symbol.thread, callgraphview symbol.root, callgraphview symbol.unwindable { font-weight: 600; } callgraphview row:not(:selected) treeexpander expander:checked+box symbol.process, +callgraphview row:not(:selected) treeexpander expander:checked+box symbol.thread, callgraphview row:not(:selected) treeexpander expander:checked+box symbol.root { border-radius: 9999px; background-color: alpha(currentColor, .05); diff --git a/src/libsysprof-gtk/sysprof-callgraph-view-private.h b/src/libsysprof-gtk/sysprof-callgraph-view-private.h index 5c78481f..55ce88b8 100644 --- a/src/libsysprof-gtk/sysprof-callgraph-view-private.h +++ b/src/libsysprof-gtk/sysprof-callgraph-view-private.h @@ -50,6 +50,8 @@ struct _SysprofCallgraphView GCancellable *cancellable; guint reload_source; + + guint include_threads : 1; }; struct _SysprofCallgraphViewClass diff --git a/src/libsysprof-gtk/sysprof-callgraph-view.c b/src/libsysprof-gtk/sysprof-callgraph-view.c index e8f66031..f66758c7 100644 --- a/src/libsysprof-gtk/sysprof-callgraph-view.c +++ b/src/libsysprof-gtk/sysprof-callgraph-view.c @@ -31,6 +31,7 @@ enum { PROP_0, PROP_CALLGRAPH, PROP_DOCUMENT, + PROP_INCLUDE_THREADS, PROP_TRACEABLES, N_PROPS }; @@ -249,6 +250,7 @@ functions_selection_changed_cb (SysprofCallgraphView *self, default: case SYSPROF_SYMBOL_KIND_PROCESS: + case SYSPROF_SYMBOL_KIND_THREAD: case SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH: case SYSPROF_SYMBOL_KIND_UNWINDABLE: case SYSPROF_SYMBOL_KIND_USER: @@ -383,6 +385,10 @@ sysprof_callgraph_view_get_property (GObject *object, g_value_set_object (value, sysprof_callgraph_view_get_document (self)); break; + case PROP_INCLUDE_THREADS: + g_value_set_boolean (value, sysprof_callgraph_view_get_include_threads (self)); + break; + case PROP_TRACEABLES: g_value_set_object (value, sysprof_callgraph_view_get_traceables (self)); break; @@ -406,6 +412,10 @@ sysprof_callgraph_view_set_property (GObject *object, sysprof_callgraph_view_set_document (self, g_value_get_object (value)); break; + case PROP_INCLUDE_THREADS: + sysprof_callgraph_view_set_include_threads (self, g_value_get_boolean (value)); + break; + case PROP_TRACEABLES: sysprof_callgraph_view_set_traceables (self, g_value_get_object (value)); break; @@ -435,6 +445,11 @@ sysprof_callgraph_view_class_init (SysprofCallgraphViewClass *klass) SYSPROF_TYPE_DOCUMENT, (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + properties[PROP_INCLUDE_THREADS] = + g_param_spec_boolean ("include-threads", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + properties[PROP_TRACEABLES] = g_param_spec_object ("traceables", NULL, NULL, G_TYPE_LIST_MODEL, @@ -566,11 +581,17 @@ sysprof_callgraph_view_reload_cb (GObject *object, static gboolean sysprof_callgraph_view_reload (SysprofCallgraphView *self) { + SysprofCallgraphFlags flags = 0; + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); g_clear_handle_id (&self->reload_source, g_source_remove); + if (self->include_threads) + flags |= SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS; + sysprof_document_callgraph_async (self->document, + flags, self->traceables, SYSPROF_CALLGRAPH_VIEW_GET_CLASS (self)->augment_size, SYSPROF_CALLGRAPH_VIEW_GET_CLASS (self)->augment_func, @@ -702,3 +723,27 @@ sysprof_callgraph_view_get_callgraph (SysprofCallgraphView *self) return self->callgraph; } + +gboolean +sysprof_callgraph_view_get_include_threads (SysprofCallgraphView *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self), FALSE); + + return self->include_threads; +} + +void +sysprof_callgraph_view_set_include_threads (SysprofCallgraphView *self, + gboolean include_threads) +{ + g_return_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + include_threads = !!include_threads; + + if (self->include_threads != include_threads) + { + self->include_threads = include_threads; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INCLUDE_THREADS]); + sysprof_callgraph_view_queue_reload (self); + } +} diff --git a/src/libsysprof-gtk/sysprof-callgraph-view.h b/src/libsysprof-gtk/sysprof-callgraph-view.h index c4523e80..0d6074fe 100644 --- a/src/libsysprof-gtk/sysprof-callgraph-view.h +++ b/src/libsysprof-gtk/sysprof-callgraph-view.h @@ -36,19 +36,24 @@ typedef struct _SysprofCallgraphView SysprofCallgraphView; typedef struct _SysprofCallgraphViewClass SysprofCallgraphViewClass; SYSPROF_AVAILABLE_IN_ALL -GType sysprof_callgraph_view_get_type (void) G_GNUC_CONST; +GType sysprof_callgraph_view_get_type (void) G_GNUC_CONST; SYSPROF_AVAILABLE_IN_ALL -SysprofCallgraph *sysprof_callgraph_view_get_callgraph (SysprofCallgraphView *self); +SysprofCallgraph *sysprof_callgraph_view_get_callgraph (SysprofCallgraphView *self); SYSPROF_AVAILABLE_IN_ALL -SysprofDocument *sysprof_callgraph_view_get_document (SysprofCallgraphView *self); +SysprofDocument *sysprof_callgraph_view_get_document (SysprofCallgraphView *self); SYSPROF_AVAILABLE_IN_ALL -void sysprof_callgraph_view_set_document (SysprofCallgraphView *self, - SysprofDocument *document); +void sysprof_callgraph_view_set_document (SysprofCallgraphView *self, + SysprofDocument *document); SYSPROF_AVAILABLE_IN_ALL -GListModel *sysprof_callgraph_view_get_traceables (SysprofCallgraphView *self); +GListModel *sysprof_callgraph_view_get_traceables (SysprofCallgraphView *self); SYSPROF_AVAILABLE_IN_ALL -void sysprof_callgraph_view_set_traceables (SysprofCallgraphView *self, - GListModel *model); +void sysprof_callgraph_view_set_traceables (SysprofCallgraphView *self, + GListModel *model); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_callgraph_view_get_include_threads (SysprofCallgraphView *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_callgraph_view_set_include_threads (SysprofCallgraphView *self, + gboolean include_threads); G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCallgraphView, g_object_unref) diff --git a/src/libsysprof-gtk/sysprof-symbol-label.c b/src/libsysprof-gtk/sysprof-symbol-label.c index 2730aae0..812b6fa6 100644 --- a/src/libsysprof-gtk/sysprof-symbol-label.c +++ b/src/libsysprof-gtk/sysprof-symbol-label.c @@ -48,6 +48,7 @@ static const char *kind_classes[] = { NULL, "root", "process", + "thread", "context-switch", "user", "kernel", diff --git a/src/libsysprof-gtk/tests/test-callgraph.c b/src/libsysprof-gtk/tests/test-callgraph.c index 5a5837ec..f87791a7 100644 --- a/src/libsysprof-gtk/tests/test-callgraph.c +++ b/src/libsysprof-gtk/tests/test-callgraph.c @@ -28,8 +28,10 @@ static GMainLoop *main_loop; static char *kallsyms_path; static char *filename; +static gboolean include_threads; static const GOptionEntry entries[] = { { "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms_path, "The path to kallsyms to use for decoding", "PATH" }, + { "threads", 't', 0, G_OPTION_ARG_NONE, &include_threads, "Include threads in the callgraph" }, { 0 } }; @@ -101,6 +103,7 @@ main (int argc, view = g_object_new (SYSPROF_TYPE_WEIGHTED_CALLGRAPH_VIEW, "traceables", model, "document", document, + "include-threads", include_threads, NULL); g_signal_connect_swapped (window, "close-request", diff --git a/src/tools/callgraph.c b/src/tools/callgraph.c deleted file mode 100644 index e379b83d..00000000 --- a/src/tools/callgraph.c +++ /dev/null @@ -1,478 +0,0 @@ -/* callgraph.c - * - * Copyright 2023 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include - -static GMainLoop *main_loop; -static char *kallsyms_path; -static char *filename; -static double total; -static gboolean memprof; -static const GOptionEntry entries[] = { - { "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms_path, "The path to kallsyms to use for decoding", "PATH" }, - { "memprof", 'm', 0, G_OPTION_ARG_NONE, &memprof, "Read memory callgraph instead of samples" }, - { 0 } -}; - -typedef struct _Augment -{ - guint32 size; - guint32 total; -} Augment; - -static gboolean -on_key_pressed_cb (GtkEventControllerKey *key, - guint keyval, - guint keycode, - GdkModifierType state, - GtkTreeExpander *expander) -{ - GtkTreeListRow *row = gtk_tree_expander_get_list_row (expander); - - if (keyval == GDK_KEY_space) - gtk_tree_list_row_set_expanded (row, !gtk_tree_list_row_get_expanded (row)); - else if (keyval == GDK_KEY_Right) - gtk_tree_list_row_set_expanded (row, TRUE); - else if (keyval == GDK_KEY_Left) - gtk_tree_list_row_set_expanded (row, FALSE); - else - return FALSE; - - return TRUE; -} - -static void -function_setup (GtkSignalListItemFactory *factory, - GtkListItem *list_item, - SysprofCallgraph *callgraph) -{ - GtkEventController *controller; - GtkWidget *expander; - GtkWidget *text; - - g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); - g_assert (GTK_IS_LIST_ITEM (list_item)); - g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); - - expander = gtk_tree_expander_new (); - text = g_object_new (GTK_TYPE_INSCRIPTION, - "hexpand", TRUE, - NULL); - gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander), text); - gtk_list_item_set_child (list_item, expander); - - controller = gtk_event_controller_key_new (); - g_signal_connect (controller, - "key-pressed", - G_CALLBACK (on_key_pressed_cb), - expander); - gtk_widget_add_controller (GTK_WIDGET (expander), controller); -} - -static void -function_bind (GtkSignalListItemFactory *factory, - GtkListItem *list_item, - SysprofCallgraph *callgraph) -{ - SysprofCallgraphFrame *frame; - GtkTreeListRow *row; - SysprofSymbol *symbol; - const char *name; - GtkWidget *expander; - GtkWidget *text; - - g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); - g_assert (GTK_IS_LIST_ITEM (list_item)); - g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); - - row = gtk_list_item_get_item (list_item); - frame = gtk_tree_list_row_get_item (row); - symbol = sysprof_callgraph_frame_get_symbol (frame); - name = sysprof_symbol_get_name (symbol); - - expander = gtk_list_item_get_child (list_item); - text = gtk_tree_expander_get_child (GTK_TREE_EXPANDER (expander)); - gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (expander), row); - gtk_inscription_set_text (GTK_INSCRIPTION (text), name); -} - -static void -total_setup (GtkSignalListItemFactory *factory, - GtkListItem *list_item, - SysprofCallgraph *callgraph) -{ - GtkWidget *child; - - g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); - g_assert (GTK_IS_LIST_ITEM (list_item)); - g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); - - child = g_object_new (GTK_TYPE_PROGRESS_BAR, - "show-text", TRUE, - NULL); - gtk_list_item_set_child (list_item, child); -} - -static void -total_bind (GtkSignalListItemFactory *factory, - GtkListItem *list_item, - gpointer user_data) -{ - g_autofree char *text = NULL; - SysprofCallgraphFrame *frame; - GtkTreeListRow *row; - GtkWidget *child; - double fraction; - Augment *aug; - - g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); - g_assert (GTK_IS_LIST_ITEM (list_item)); - - row = gtk_list_item_get_item (list_item); - frame = gtk_tree_list_row_get_item (row); - aug = sysprof_callgraph_frame_get_augment (frame); - fraction = aug->total / total; - - text = g_strdup_printf ("%.2lf", fraction*100.); - - child = gtk_list_item_get_child (list_item); - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (child), fraction); - gtk_progress_bar_set_text (GTK_PROGRESS_BAR (child), text); -} - -static void -self_setup (GtkSignalListItemFactory *factory, - GtkListItem *list_item, - SysprofCallgraph *callgraph) -{ - GtkWidget *child; - - g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); - g_assert (GTK_IS_LIST_ITEM (list_item)); - g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); - - child = g_object_new (GTK_TYPE_PROGRESS_BAR, - "show-text", TRUE, - NULL); - gtk_list_item_set_child (list_item, child); -} - -static void -self_bind (GtkSignalListItemFactory *factory, - GtkListItem *list_item, - gpointer user_data) -{ - g_autofree char *text = NULL; - SysprofCallgraphFrame *frame; - GtkTreeListRow *row; - GtkWidget *child; - double fraction; - Augment *aug; - - g_assert (GTK_IS_SIGNAL_LIST_ITEM_FACTORY (factory)); - g_assert (GTK_IS_LIST_ITEM (list_item)); - - row = gtk_list_item_get_item (list_item); - frame = gtk_tree_list_row_get_item (row); - aug = sysprof_callgraph_frame_get_augment (frame); - fraction = aug->size / total; - - text = g_strdup_printf ("%.2lf", fraction*100.); - - child = gtk_list_item_get_child (list_item); - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (child), fraction); - gtk_progress_bar_set_text (GTK_PROGRESS_BAR (child), text); -} - -static GListModel * -create_model_func (gpointer item, - gpointer user_data) -{ - if (g_list_model_get_n_items (G_LIST_MODEL (item)) > 0) - return g_object_ref (G_LIST_MODEL (item)); - return NULL; -} - -static int -sort_by_self (gconstpointer a, - gconstpointer b, - gpointer user_data) -{ - SysprofCallgraphFrame *frame_a = (SysprofCallgraphFrame *)a; - SysprofCallgraphFrame *frame_b = (SysprofCallgraphFrame *)b; - Augment *aug_a = sysprof_callgraph_frame_get_augment (frame_a); - Augment *aug_b = sysprof_callgraph_frame_get_augment (frame_b); - double self_a = aug_a->size / total; - double self_b = aug_b->size / total; - - if (self_a < self_b) - return -1; - else if (self_a > self_b) - return 1; - else - return 0; -} - -static int -sort_by_total (gconstpointer a, - gconstpointer b, - gpointer user_data) -{ - SysprofCallgraphFrame *frame_a = (SysprofCallgraphFrame *)a; - SysprofCallgraphFrame *frame_b = (SysprofCallgraphFrame *)b; - Augment *aug_a = sysprof_callgraph_frame_get_augment (frame_a); - Augment *aug_b = sysprof_callgraph_frame_get_augment (frame_b); - double total_a = aug_a->total / total; - double total_b = aug_b->total / total; - - if (total_a < total_b) - return -1; - else if (total_a > total_b) - return 1; - else - return 0; -} - -static void -show_callgraph (SysprofCallgraph *callgraph) -{ - g_autofree char *name = g_path_get_basename (filename); - g_autoptr(GtkTreeListModel) tree = NULL; - g_autoptr(GtkMultiSelection) model = NULL; - g_autoptr(SysprofCallgraphFrame) root = NULL; - GtkColumnViewColumn *column; - GtkScrolledWindow *scroller; - GtkColumnView *column_view; - GtkListItemFactory *factory; - g_autoptr(GtkTreeListRowSorter) sorter = NULL; - g_autoptr(GtkSortListModel) sort_model = NULL; - g_autoptr(GtkCustomSorter) self_sorter = NULL; - g_autoptr(GtkCustomSorter) total_sorter = NULL; - GtkSorter *column_sorter = NULL; - GtkWindow *window; - Augment *aug; - - root = g_list_model_get_item (G_LIST_MODEL (callgraph), 0); - aug = sysprof_callgraph_frame_get_augment (root); - total = aug->total; - - self_sorter = gtk_custom_sorter_new (sort_by_self, NULL, NULL); - total_sorter = gtk_custom_sorter_new (sort_by_total, NULL, NULL); - - window = g_object_new (GTK_TYPE_WINDOW, - "default-width", 1024, - "default-height", 800, - "title", name, - NULL); - scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW, NULL); - gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (scroller)); - column_view = g_object_new (GTK_TYPE_COLUMN_VIEW, NULL); - gtk_widget_add_css_class (GTK_WIDGET (column_view), "data-table"); - gtk_scrolled_window_set_child (scroller, GTK_WIDGET (column_view)); - - tree = gtk_tree_list_model_new (g_object_ref (G_LIST_MODEL (callgraph)), - FALSE, FALSE, create_model_func, NULL, NULL); - column_sorter = gtk_column_view_get_sorter (column_view); - sorter = gtk_tree_list_row_sorter_new (g_object_ref (column_sorter)); - sort_model = gtk_sort_list_model_new (g_object_ref (G_LIST_MODEL (tree)), g_object_ref (GTK_SORTER (sorter))); - model = gtk_multi_selection_new (g_object_ref (G_LIST_MODEL (sort_model))); - gtk_column_view_set_model (column_view, GTK_SELECTION_MODEL (model)); - - factory = gtk_signal_list_item_factory_new (); - g_signal_connect_object (factory, "setup", G_CALLBACK (function_setup), callgraph, 0); - g_signal_connect_object (factory, "bind", G_CALLBACK (function_bind), callgraph, 0); - column = gtk_column_view_column_new ("Function", factory); - gtk_column_view_column_set_expand (column, TRUE); - gtk_column_view_append_column (column_view, column); - - factory = gtk_signal_list_item_factory_new (); - g_signal_connect_object (factory, "setup", G_CALLBACK (self_setup), callgraph, 0); - g_signal_connect (factory, "bind", G_CALLBACK (self_bind), NULL); - column = gtk_column_view_column_new ("Self", factory); - gtk_column_view_column_set_expand (column, FALSE); - gtk_column_view_column_set_sorter (column, GTK_SORTER (self_sorter)); - gtk_column_view_append_column (column_view, column); - - factory = gtk_signal_list_item_factory_new (); - g_signal_connect_object (factory, "setup", G_CALLBACK (total_setup), callgraph, 0); - g_signal_connect (factory, "bind", G_CALLBACK (total_bind), NULL); - column = gtk_column_view_column_new ("Total", factory); - gtk_column_view_column_set_expand (column, FALSE); - gtk_column_view_column_set_sorter (column, GTK_SORTER (total_sorter)); - gtk_column_view_append_column (column_view, column); - gtk_column_view_sort_by_column (column_view, column, GTK_SORT_DESCENDING); - - g_signal_connect_swapped (window, - "close-request", - G_CALLBACK (g_main_loop_quit), - main_loop); - gtk_window_present (window); -} - -static void -callgraph_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofDocument *document = SYSPROF_DOCUMENT (object); - g_autoptr(GError) error = NULL; - g_autoptr(SysprofCallgraph) callgraph = sysprof_document_callgraph_finish (document, result, &error); - - g_print ("Done.\n"); - - g_assert_no_error (error); - g_assert_true (SYSPROF_IS_CALLGRAPH (callgraph)); - - show_callgraph (callgraph); -} - -static void -augment_sample_cb (SysprofCallgraph *callgraph, - SysprofCallgraphNode *node, - SysprofDocumentFrame *frame, - gboolean summarize, - gpointer user_data) -{ - Augment *aug; - - g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); - g_assert (node != NULL); - g_assert (SYSPROF_IS_DOCUMENT_SAMPLE (frame)); - g_assert (user_data == NULL); - - aug = sysprof_callgraph_get_augment (callgraph, node); - aug->size += 1; - - for (; node; node = sysprof_callgraph_node_parent (node)) - { - aug = sysprof_callgraph_get_augment (callgraph, node); - aug->total += 1; - } -} - -static void -augment_memprof_cb (SysprofCallgraph *callgraph, - SysprofCallgraphNode *node, - SysprofDocumentFrame *frame, - gboolean summarize, - gpointer user_data) -{ - SysprofDocumentAllocation *alloc = (SysprofDocumentAllocation *)frame; - Augment *aug; - gint64 size; - - g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); - g_assert (node != NULL); - g_assert (SYSPROF_IS_DOCUMENT_ALLOCATION (alloc)); - g_assert (user_data == NULL); - - size = sysprof_document_allocation_get_size (alloc); - - if (sysprof_document_allocation_is_free (alloc)) - return; - - aug = sysprof_callgraph_get_augment (callgraph, node); - aug->size += size; - - for (; node; node = sysprof_callgraph_node_parent (node)) - { - aug = sysprof_callgraph_get_augment (callgraph, node); - aug->total += size; - } -} - -int -main (int argc, - char *argv[]) -{ - g_autoptr(GOptionContext) context = g_option_context_new ("- show a callgraph"); - g_autoptr(SysprofDocumentLoader) loader = NULL; - g_autoptr(SysprofDocument) document = NULL; - g_autoptr(SysprofMultiSymbolizer) multi = NULL; - g_autoptr(GListModel) model = NULL; - g_autoptr(GError) error = NULL; - - gtk_init (); - sysprof_clock_init (); - - g_option_context_add_main_entries (context, entries, NULL); - if (!g_option_context_parse (context, &argc, &argv, &error)) - { - g_printerr ("%s\n", error->message); - return 1; - } - - if (argc != 2) - { - g_print ("usage: %s [OPTIONS] CAPTURE_FILE\n", argv[0]); - return 1; - } - - filename = argv[1]; - - main_loop = g_main_loop_new (NULL, FALSE); - - multi = sysprof_multi_symbolizer_new (); - - if (kallsyms_path) - { - g_autoptr(GFile) kallsyms_file = g_file_new_for_path (kallsyms_path); - GFileInputStream *kallsyms_stream = g_file_read (kallsyms_file, NULL, NULL); - - sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new_for_symbols (G_INPUT_STREAM (kallsyms_stream))); - } - else - { - sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new ()); - } - - sysprof_multi_symbolizer_take (multi, sysprof_elf_symbolizer_new ()); - sysprof_multi_symbolizer_take (multi, sysprof_jitmap_symbolizer_new ()); - - loader = sysprof_document_loader_new (filename); - sysprof_document_loader_set_symbolizer (loader, SYSPROF_SYMBOLIZER (multi)); - - g_print ("Loading %s, ignoring embedded symbols...\n", filename); - if (!(document = sysprof_document_loader_load (loader, NULL, &error))) - g_error ("Failed to load document: %s", error->message); - - g_print ("Loaded and symbolized. Generating callgraph of %s ...\n", - memprof ? "allocations" : "samples"); - if (memprof) - model = sysprof_document_list_allocations (document); - else - model = sysprof_document_list_samples (document); - sysprof_document_callgraph_async (document, - model, - sizeof (Augment), - memprof ? augment_memprof_cb : augment_sample_cb, - NULL, - NULL, - NULL, - callgraph_cb, - main_loop); - - g_main_loop_run (main_loop); - - return 0; -} diff --git a/src/tools/meson.build b/src/tools/meson.build index 295e534c..c48c4e10 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -56,12 +56,3 @@ if get_option('agent') install: true, ) endif - -if get_option('gtk') - callgraph = executable('callgraph', ['callgraph.c'], - dependencies: [libsysprof_analyze_static_dep, - dependency('gtk4', version: gtk_req_version)], - c_args: tools_cflags, - install: false, - ) -endif