From c1cfc1e21082004a55cf2867b613209e87381522 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 17 Jul 2023 12:25:59 -0700 Subject: [PATCH] libsysprof-analyze: add basic tracking of threads It's nice to have a list of threads that are in a process and make that available as a listmodel of the process. We can use this to show more information in the UI at some point. --- src/libsysprof-analyze/meson.build | 2 + .../sysprof-document-process.c | 54 +++++ .../sysprof-document-process.h | 2 + src/libsysprof-analyze/sysprof-document.c | 12 ++ .../sysprof-process-info-private.h | 11 ++ src/libsysprof-analyze/sysprof-process-info.c | 5 + src/libsysprof-analyze/sysprof-thread-info.c | 184 ++++++++++++++++++ src/libsysprof-analyze/sysprof-thread-info.h | 39 ++++ 8 files changed, 309 insertions(+) create mode 100644 src/libsysprof-analyze/sysprof-thread-info.c create mode 100644 src/libsysprof-analyze/sysprof-thread-info.h diff --git a/src/libsysprof-analyze/meson.build b/src/libsysprof-analyze/meson.build index c039dae0..3c229ab3 100644 --- a/src/libsysprof-analyze/meson.build +++ b/src/libsysprof-analyze/meson.build @@ -33,6 +33,7 @@ libsysprof_analyze_public_sources = [ 'sysprof-no-symbolizer.c', 'sysprof-symbol.c', 'sysprof-symbolizer.c', + 'sysprof-thread-info.c', 'sysprof-time-span.c', ] @@ -73,6 +74,7 @@ libsysprof_analyze_public_headers = [ 'sysprof-no-symbolizer.h', 'sysprof-symbol.h', 'sysprof-symbolizer.h', + 'sysprof-thread-info.h', 'sysprof-time-span.h', ] diff --git a/src/libsysprof-analyze/sysprof-document-process.c b/src/libsysprof-analyze/sysprof-document-process.c index 02b18b38..ca348983 100644 --- a/src/libsysprof-analyze/sysprof-document-process.c +++ b/src/libsysprof-analyze/sysprof-document-process.c @@ -25,6 +25,7 @@ #include "sysprof-document-frame-private.h" #include "sysprof-document-process-private.h" #include "sysprof-mount.h" +#include "sysprof-thread-info.h" struct _SysprofDocumentProcess { @@ -44,6 +45,7 @@ enum { PROP_MEMORY_MAPS, PROP_MOUNTS, PROP_EXIT_TIME, + PROP_THREADS, PROP_TITLE, N_PROPS }; @@ -92,6 +94,10 @@ sysprof_document_process_get_property (GObject *object, g_value_take_object (value, sysprof_document_process_list_mounts (self)); break; + case PROP_THREADS: + g_value_take_object (value, sysprof_document_process_list_threads (self)); + break; + case PROP_TITLE: g_value_take_string (value, sysprof_document_process_dup_title (self)); break; @@ -134,6 +140,11 @@ sysprof_document_process_class_init (SysprofDocumentProcessClass *klass) G_TYPE_LIST_MODEL, (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + properties [PROP_THREADS] = + g_param_spec_object ("threads", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + properties [PROP_TITLE] = g_param_spec_string ("title", NULL, NULL, NULL, @@ -267,3 +278,46 @@ sysprof_document_process_dup_title (SysprofDocumentProcess *self) return g_strdup_printf (_("Process %d"), pid); } + +/** + * sysprof_document_process_list_threads: + * @self: a #SysprofDocumentProcess + * + * Gets the list of threads for the process. + * + * Returns: (transfer full): a #GListModel of #SysprofThreadInfo. + */ +GListModel * +sysprof_document_process_list_threads (SysprofDocumentProcess *self) +{ + GListStore *store; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), NULL); + + store = g_list_store_new (SYSPROF_TYPE_THREAD_INFO); + + if (self->process_info != NULL) + { + g_autoptr(GPtrArray) threads = g_ptr_array_new_with_free_func (g_object_unref); + EggBitsetIter iter; + guint i; + + if (egg_bitset_iter_init_first (&iter, self->process_info->thread_ids, &i)) + { + do + { + g_ptr_array_add (threads, + g_object_new (SYSPROF_TYPE_THREAD_INFO, + "process", self, + "thread-id", i, + NULL)); + } + while (egg_bitset_iter_next (&iter, &i)); + } + + if (threads->len > 0) + g_list_store_splice (store, 0, 0, threads->pdata, threads->len); + } + + return G_LIST_MODEL (store); +} diff --git a/src/libsysprof-analyze/sysprof-document-process.h b/src/libsysprof-analyze/sysprof-document-process.h index 4d9065a9..73f98d46 100644 --- a/src/libsysprof-analyze/sysprof-document-process.h +++ b/src/libsysprof-analyze/sysprof-document-process.h @@ -48,6 +48,8 @@ SYSPROF_AVAILABLE_IN_ALL GListModel *sysprof_document_process_list_memory_maps (SysprofDocumentProcess *self); SYSPROF_AVAILABLE_IN_ALL GListModel *sysprof_document_process_list_mounts (SysprofDocumentProcess *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_process_list_threads (SysprofDocumentProcess *self); G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentProcess, g_object_unref) diff --git a/src/libsysprof-analyze/sysprof-document.c b/src/libsysprof-analyze/sysprof-document.c index 7b687d7e..a2ee98d8 100644 --- a/src/libsysprof-analyze/sysprof-document.c +++ b/src/libsysprof-analyze/sysprof-document.c @@ -1279,6 +1279,18 @@ sysprof_document_load_worker (GTask *task, } } } + + if (sample->tid != tainted->pid) + { + SysprofProcessInfo *info = _sysprof_document_process_info (self, pid, TRUE); + sysprof_process_info_seen_thread (info, swap_int32 (self->needs_swap, sample->tid)); + } + } + else if (tainted->type == SYSPROF_CAPTURE_FRAME_ALLOCATION) + { + const SysprofCaptureAllocation *alloc = (const SysprofCaptureAllocation *)tainted; + SysprofProcessInfo *info = _sysprof_document_process_info (self, pid, TRUE); + sysprof_process_info_seen_thread (info, swap_int32 (self->needs_swap, alloc->tid)); } else if (tainted->type == SYSPROF_CAPTURE_FRAME_EXIT) { diff --git a/src/libsysprof-analyze/sysprof-process-info-private.h b/src/libsysprof-analyze/sysprof-process-info-private.h index 9e623c0e..2a9025e4 100644 --- a/src/libsysprof-analyze/sysprof-process-info-private.h +++ b/src/libsysprof-analyze/sysprof-process-info-private.h @@ -20,6 +20,8 @@ #pragma once +#include "eggbitset.h" + #include "sysprof-address-layout-private.h" #include "sysprof-mount-namespace-private.h" #include "sysprof-symbol-cache-private.h" @@ -33,6 +35,7 @@ typedef struct _SysprofProcessInfo SysprofSymbolCache *symbol_cache; SysprofSymbol *fallback_symbol; SysprofSymbol *symbol; + EggBitset *thread_ids; int pid; gint64 exit_time; } SysprofProcessInfo; @@ -42,4 +45,12 @@ SysprofProcessInfo *sysprof_process_info_new (SysprofMountNamespace *mount_nam SysprofProcessInfo *sysprof_process_info_ref (SysprofProcessInfo *self); void sysprof_process_info_unref (SysprofProcessInfo *self); +static inline void +sysprof_process_info_seen_thread (SysprofProcessInfo *self, + int thread_id) +{ + if (thread_id > 0) + egg_bitset_add (self->thread_ids, thread_id); +} + G_END_DECLS diff --git a/src/libsysprof-analyze/sysprof-process-info.c b/src/libsysprof-analyze/sysprof-process-info.c index 19dc9429..6cba4b7b 100644 --- a/src/libsysprof-analyze/sysprof-process-info.c +++ b/src/libsysprof-analyze/sysprof-process-info.c @@ -44,12 +44,16 @@ sysprof_process_info_new (SysprofMountNamespace *mount_namespace, self->address_layout = sysprof_address_layout_new (); self->symbol_cache = sysprof_symbol_cache_new (); self->mount_namespace = mount_namespace; + self->thread_ids = egg_bitset_new_empty (); self->fallback_symbol = _sysprof_symbol_new (g_ref_string_new (symname), NULL, g_ref_string_new (pidstr), 0, 0, SYSPROF_SYMBOL_KIND_PROCESS); + if (pid > 0) + egg_bitset_add (self->thread_ids, pid); + return self; } @@ -69,6 +73,7 @@ sysprof_process_info_finalize (gpointer data) g_clear_object (&self->mount_namespace); g_clear_object (&self->fallback_symbol); g_clear_object (&self->symbol); + g_clear_pointer (&self->thread_ids, egg_bitset_unref); self->pid = 0; } diff --git a/src/libsysprof-analyze/sysprof-thread-info.c b/src/libsysprof-analyze/sysprof-thread-info.c new file mode 100644 index 00000000..bf8d932c --- /dev/null +++ b/src/libsysprof-analyze/sysprof-thread-info.c @@ -0,0 +1,184 @@ +/* sysprof-thread-info.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 "sysprof-thread-info.h" + +struct _SysprofThreadInfo +{ + GObject parent_instance; + SysprofDocumentProcess *process; + int thread_id; +}; + +enum { + PROP_0, + PROP_PROCESS, + PROP_THREAD_ID, + PROP_IS_MAIN_THREAD, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofThreadInfo, sysprof_thread_info, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_thread_info_finalize (GObject *object) +{ + SysprofThreadInfo *self = (SysprofThreadInfo *)object; + + g_clear_object (&self->process); + + G_OBJECT_CLASS (sysprof_thread_info_parent_class)->finalize (object); +} + +static void +sysprof_thread_info_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofThreadInfo *self = SYSPROF_THREAD_INFO (object); + + switch (prop_id) + { + case PROP_IS_MAIN_THREAD: + g_value_set_boolean (value, sysprof_thread_info_is_main_thread (self)); + break; + + case PROP_PROCESS: + g_value_set_object (value, sysprof_thread_info_get_process (self)); + break; + + case PROP_THREAD_ID: + g_value_set_int (value, sysprof_thread_info_get_thread_id (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_thread_info_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofThreadInfo *self = SYSPROF_THREAD_INFO (object); + + switch (prop_id) + { + case PROP_PROCESS: + self->process = g_value_dup_object (value); + break; + + case PROP_THREAD_ID: + self->thread_id = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_thread_info_class_init (SysprofThreadInfoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_thread_info_finalize; + object_class->get_property = sysprof_thread_info_get_property; + object_class->set_property = sysprof_thread_info_set_property; + + properties[PROP_IS_MAIN_THREAD] = + g_param_spec_boolean ("is-main-thread", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_PROCESS] = + g_param_spec_object ("process", NULL, NULL, + SYSPROF_TYPE_DOCUMENT_PROCESS, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_THREAD_ID] = + g_param_spec_int ("thread-id", NULL, NULL, + G_MININT, G_MAXINT, 0, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_thread_info_init (SysprofThreadInfo *self) +{ +} + +/** + * sysprof_thread_info_get_process: + * @self: a #SysprofThreadInfo + * + * Gets the process that owns the thread info. + * + * Returns: (transfer none): a #SysprofDocumentProcess + */ +SysprofDocumentProcess * +sysprof_thread_info_get_process (SysprofThreadInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_THREAD_INFO (self), NULL); + + return self->process; +} + +/** + * sysprof_thread_info_get_thread_id: + * @self: a #SysprofThreadInfo + * + * Gets the thread identifier. + * + * This typically matches what `gettid()` syscall returns on Linux. + * + * Returns: an integer containing the thread-id + */ +int +sysprof_thread_info_get_thread_id (SysprofThreadInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_THREAD_INFO (self), -1); + + return self->thread_id; +} + +/** + * sysprof_thread_info_is_main_thread: + * @self: a #SysprofThreadInfo + * + * Checks if the thread is the main thread for a process. + * + * Returns: %TRUE if the thread-id is the main thread. + */ +gboolean +sysprof_thread_info_is_main_thread (SysprofThreadInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_THREAD_INFO (self), FALSE); + + return self->thread_id == sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (self->process)); +} diff --git a/src/libsysprof-analyze/sysprof-thread-info.h b/src/libsysprof-analyze/sysprof-thread-info.h new file mode 100644 index 00000000..705949c9 --- /dev/null +++ b/src/libsysprof-analyze/sysprof-thread-info.h @@ -0,0 +1,39 @@ +/* sysprof-thread-info.h + * + * 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 + */ + +#pragma once + +#include "sysprof-document-process.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_THREAD_INFO (sysprof_thread_info_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofThreadInfo, sysprof_thread_info, SYSPROF, THREAD_INFO, GObject) + +SYSPROF_AVAILABLE_IN_ALL +SysprofDocumentProcess *sysprof_thread_info_get_process (SysprofThreadInfo *self); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_thread_info_get_thread_id (SysprofThreadInfo *self); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_thread_info_is_main_thread (SysprofThreadInfo *self); + +G_END_DECLS