1 /* gbp-sysprof-workspace-addin.c
2 *
3 * Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #define G_LOG_DOMAIN "gbp-sysprof-workspace-addin"
22
23 #include "config.h"
24
25 #include <dazzle.h>
26 #include <glib/gi18n.h>
27 #include <sysprof-ui.h>
28
29 #include "gbp-sysprof-surface.h"
30 #include "gbp-sysprof-workspace-addin.h"
31
32 struct _GbpSysprofWorkspaceAddin
33 {
34 GObject parent_instance;
35
36 GSimpleActionGroup *actions;
37
38 GbpSysprofSurface *surface;
39 IdeWorkspace *workspace;
40 };
41
42 static void workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface);
43
G_DEFINE_FINAL_TYPE_WITH_CODE(GbpSysprofWorkspaceAddin,gbp_sysprof_workspace_addin,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN,workspace_addin_iface_init))44 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpSysprofWorkspaceAddin, gbp_sysprof_workspace_addin, G_TYPE_OBJECT,
45 G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN, workspace_addin_iface_init))
46
47 static void
48 profiler_child_spawned (IdeRunner *runner,
49 const gchar *identifier,
50 SysprofProfiler *profiler)
51 {
52 #ifdef G_OS_UNIX
53 GPid pid = 0;
54
55 g_assert (IDE_IS_MAIN_THREAD ());
56 g_assert (SYSPROF_IS_PROFILER (profiler));
57 g_assert (identifier != NULL);
58 g_assert (IDE_IS_RUNNER (runner));
59
60 pid = g_ascii_strtoll (identifier, NULL, 10);
61
62 if (pid == 0)
63 {
64 g_warning ("Failed to parse integer value from %s", identifier);
65 return;
66 }
67
68 IDE_TRACE_MSG ("Adding pid %s to profiler", identifier);
69
70 sysprof_profiler_add_pid (profiler, pid);
71 sysprof_profiler_start (profiler);
72 #endif
73 }
74
75 static void
runner_exited_cb(IdeRunner * runner,SysprofProfiler * profiler)76 runner_exited_cb (IdeRunner *runner,
77 SysprofProfiler *profiler)
78 {
79 g_assert (IDE_IS_MAIN_THREAD ());
80 g_assert (IDE_IS_RUNNER (runner));
81 g_assert (SYSPROF_IS_PROFILER (profiler));
82
83 if (sysprof_profiler_get_is_running (profiler))
84 sysprof_profiler_stop (profiler);
85 }
86
87 static void
foreach_fd(gint dest_fd,gint fd,gpointer user_data)88 foreach_fd (gint dest_fd,
89 gint fd,
90 gpointer user_data)
91 {
92 IdeRunner *runner = user_data;
93
94 g_assert (IDE_IS_RUNNER (runner));
95 g_assert (dest_fd >= 0);
96 g_assert (fd >= 0);
97
98 ide_runner_take_fd (runner, dup (fd), dest_fd);
99 }
100
101 static void
profiler_run_handler(IdeRunManager * run_manager,IdeRunner * runner,gpointer user_data)102 profiler_run_handler (IdeRunManager *run_manager,
103 IdeRunner *runner,
104 gpointer user_data)
105 {
106 GbpSysprofWorkspaceAddin *self = user_data;
107 g_autoptr(SysprofProfiler) profiler = NULL;
108 g_autoptr(SysprofSource) app_source = NULL;
109 g_autoptr(SysprofSpawnable) spawnable = NULL;
110 g_autoptr(GPtrArray) sources = NULL;
111 g_auto(GStrv) argv = NULL;
112 const gchar * const *env;
113 IdeEnvironment *ienv;
114
115 g_assert (IDE_IS_MAIN_THREAD ());
116 g_assert (GBP_IS_SYSPROF_WORKSPACE_ADDIN (self));
117 g_assert (IDE_IS_RUNNER (runner));
118 g_assert (IDE_IS_RUN_MANAGER (run_manager));
119
120 sources = g_ptr_array_new_with_free_func (g_object_unref);
121
122 profiler = sysprof_local_profiler_new ();
123
124 /*
125 * Currently we require whole-system because otherwise we can get a situation
126 * where we only watch the spawning process (say jhbuild, flatpak, etc).
127 * Longer term we either need a way to follow-children and/or limit to a
128 * cgroup/process-group.
129 */
130 sysprof_profiler_set_whole_system (profiler, TRUE);
131
132 #ifdef __linux__
133 {
134 g_ptr_array_add (sources, sysprof_proc_source_new ());
135 g_ptr_array_add (sources, sysprof_perf_source_new ());
136 g_ptr_array_add (sources, sysprof_hostinfo_source_new ());
137 g_ptr_array_add (sources, sysprof_memory_source_new ());
138 g_ptr_array_add (sources, sysprof_proxy_source_new (G_BUS_TYPE_SYSTEM,
139 "org.gnome.Sysprof3",
140 "/org/gnome/Sysprof3/RAPL"));
141 g_ptr_array_add (sources, sysprof_netdev_source_new ());
142 }
143 #endif
144
145 g_ptr_array_add (sources, sysprof_gjs_source_new ());
146 g_ptr_array_add (sources, sysprof_symbols_source_new ());
147
148 /* Allow the app to submit us data if it supports "SYSPROF_TRACE_FD" */
149 app_source = sysprof_tracefd_source_new ();
150 sysprof_tracefd_source_set_envvar (SYSPROF_TRACEFD_SOURCE (app_source), "SYSPROF_TRACE_FD");
151 g_ptr_array_add (sources, g_object_ref (app_source));
152
153 /*
154 * TODO:
155 *
156 * We need to synchronize the inferior with the parent here. Ideally, we would
157 * prepend the application launch (to some degree) with the application we want
158 * to execute. In this case, we might want to add a "gnome-builder-sysprof"
159 * helper that will synchronize with the parent, and then block until we start
160 * the process (with the appropriate pid) before exec() otherwise we could
161 * miss the exit of the app and race to add the pid to the profiler.
162 */
163
164 g_signal_connect_object (runner,
165 "spawned",
166 G_CALLBACK (profiler_child_spawned),
167 profiler,
168 0);
169
170 g_signal_connect_object (runner,
171 "exited",
172 G_CALLBACK (runner_exited_cb),
173 profiler,
174 0);
175
176 /*
177 * We need to allow the sources to modify the execution environment, so copy
178 * the environment into the spawnable, modify it, and the propagate back.
179 */
180 argv = ide_runner_get_argv (runner);
181 ienv = ide_runner_get_environment (runner);
182
183 spawnable = sysprof_spawnable_new ();
184 sysprof_spawnable_append_args (spawnable, (const gchar * const *)argv);
185 sysprof_spawnable_set_starting_fd (spawnable, ide_runner_get_max_fd (runner) + 1);
186
187 for (guint i = 0; i < sources->len; i++)
188 {
189 SysprofSource *source = g_ptr_array_index (sources, i);
190
191 if (source != NULL)
192 {
193 sysprof_profiler_add_source (profiler, source);
194 sysprof_source_modify_spawn (source, spawnable);
195 }
196 }
197
198 /* TODO: Propagate argv back to runner.
199 *
200 * Currently this is a non-issue because none of our sources modify argv.
201 * So doing it now is just brittle for no benefit.
202 */
203
204 if ((env = sysprof_spawnable_get_environ (spawnable)))
205 {
206 for (guint i = 0; env[i] != NULL; i++)
207 {
208 g_autofree gchar *key = NULL;
209 g_autofree gchar *value = NULL;
210
211 if (ide_environ_parse (env[i], &key, &value))
212 ide_environment_setenv (ienv, key, value);
213 }
214 }
215
216 sysprof_spawnable_foreach_fd (spawnable, foreach_fd, runner);
217
218 gbp_sysprof_surface_add_profiler (self->surface, profiler);
219
220 ide_workspace_set_visible_surface (self->workspace, IDE_SURFACE (self->surface));
221 }
222
223 static void
gbp_sysprof_workspace_addin_open(GbpSysprofWorkspaceAddin * self,GFile * file)224 gbp_sysprof_workspace_addin_open (GbpSysprofWorkspaceAddin *self,
225 GFile *file)
226 {
227 g_assert (GBP_IS_SYSPROF_WORKSPACE_ADDIN (self));
228 g_assert (G_IS_FILE (file));
229
230 if (!g_file_is_native (file))
231 g_warning ("Can only open local sysprof capture files.");
232 else
233 gbp_sysprof_surface_open (self->surface, file);
234 }
235
236 static void
open_profile_action(GSimpleAction * action,GVariant * variant,gpointer user_data)237 open_profile_action (GSimpleAction *action,
238 GVariant *variant,
239 gpointer user_data)
240 {
241 GbpSysprofWorkspaceAddin *self = user_data;
242 g_autoptr(GFile) workdir = NULL;
243 GtkFileChooserNative *native;
244 GtkFileFilter *filter;
245 IdeContext *context;
246 gint ret;
247
248 g_assert (IDE_IS_MAIN_THREAD ());
249 g_assert (GBP_IS_SYSPROF_WORKSPACE_ADDIN (self));
250 g_assert (IDE_IS_WORKSPACE (self->workspace));
251 g_assert (GBP_IS_SYSPROF_SURFACE (self->surface));
252
253 ide_workspace_set_visible_surface (self->workspace, IDE_SURFACE (self->surface));
254
255 context = ide_workspace_get_context (self->workspace);
256 workdir = ide_context_ref_workdir (context);
257
258 native = gtk_file_chooser_native_new (_("Open Sysprof Capture…"),
259 GTK_WINDOW (self->workspace),
260 GTK_FILE_CHOOSER_ACTION_OPEN,
261 _("Open"),
262 _("Cancel"));
263 gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (native), workdir, NULL);
264
265 /* Add our filter for sysprof capture files. */
266 filter = gtk_file_filter_new ();
267 gtk_file_filter_set_name (filter, _("Sysprof Capture (*.syscap)"));
268 gtk_file_filter_add_pattern (filter, "*.syscap");
269 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (native), filter);
270
271 /* And all files now */
272 filter = gtk_file_filter_new ();
273 gtk_file_filter_set_name (filter, _("All Files"));
274 gtk_file_filter_add_pattern (filter, "*");
275 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (native), filter);
276
277 /* Unlike gtk_dialog_run(), this will handle processing
278 * various I/O events and so should be safe to use.
279 */
280 ret = gtk_native_dialog_run (GTK_NATIVE_DIALOG (native));
281
282 if (ret == GTK_RESPONSE_ACCEPT)
283 {
284 g_autoptr(GFile) file = NULL;
285
286 file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (native));
287 if (G_IS_FILE (file))
288 gbp_sysprof_workspace_addin_open (self, file);
289 }
290
291 gtk_native_dialog_hide (GTK_NATIVE_DIALOG (native));
292 gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (native));
293 }
294
295 static void
run_cb(GSimpleAction * action,GVariant * param,gpointer user_data)296 run_cb (GSimpleAction *action,
297 GVariant *param,
298 gpointer user_data)
299 {
300 GbpSysprofWorkspaceAddin *self = user_data;
301
302 g_assert (GBP_IS_SYSPROF_WORKSPACE_ADDIN (self));
303
304 if (self->workspace != NULL)
305 dzl_gtk_widget_action (GTK_WIDGET (self->workspace),
306 "run-manager",
307 "run-with-handler",
308 g_variant_new_string ("profiler"));
309 }
310
311 static void
show_cb(GSimpleAction * action,GVariant * param,gpointer user_data)312 show_cb (GSimpleAction *action,
313 GVariant *param,
314 gpointer user_data)
315 {
316 GbpSysprofWorkspaceAddin *self = user_data;
317
318 g_assert (GBP_IS_SYSPROF_WORKSPACE_ADDIN (self));
319
320 if (self->workspace != NULL)
321 ide_workspace_set_visible_surface (self->workspace, IDE_SURFACE (self->surface));
322 }
323
324 static void
gbp_sysprof_workspace_addin_finalize(GObject * object)325 gbp_sysprof_workspace_addin_finalize (GObject *object)
326 {
327 GbpSysprofWorkspaceAddin *self = (GbpSysprofWorkspaceAddin *)object;
328
329 g_assert (IDE_IS_MAIN_THREAD ());
330
331 g_clear_object (&self->actions);
332
333 G_OBJECT_CLASS (gbp_sysprof_workspace_addin_parent_class)->finalize (object);
334 }
335
336 static void
gbp_sysprof_workspace_addin_class_init(GbpSysprofWorkspaceAddinClass * klass)337 gbp_sysprof_workspace_addin_class_init (GbpSysprofWorkspaceAddinClass *klass)
338 {
339 GObjectClass *object_class = G_OBJECT_CLASS (klass);
340
341 object_class->finalize = gbp_sysprof_workspace_addin_finalize;
342 }
343
344 static void
gbp_sysprof_workspace_addin_init(GbpSysprofWorkspaceAddin * self)345 gbp_sysprof_workspace_addin_init (GbpSysprofWorkspaceAddin *self)
346 {
347 static const GActionEntry entries[] = {
348 { "open-profile", open_profile_action },
349 { "run", run_cb },
350 { "show", show_cb },
351 };
352
353 g_assert (IDE_IS_MAIN_THREAD ());
354
355 self->actions = g_simple_action_group_new ();
356
357 g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
358 entries,
359 G_N_ELEMENTS (entries),
360 self);
361 }
362
363 static void
gbp_sysprof_workspace_addin_check_supported_cb(GObject * object,GAsyncResult * result,gpointer user_data)364 gbp_sysprof_workspace_addin_check_supported_cb (GObject *object,
365 GAsyncResult *result,
366 gpointer user_data)
367 {
368 g_autoptr(GbpSysprofWorkspaceAddin) self = user_data;
369 g_autoptr(GError) error = NULL;
370 IdeRunManager *run_manager;
371 IdeContext *context;
372
373 g_assert (GBP_IS_SYSPROF_WORKSPACE_ADDIN (self));
374 g_assert (G_IS_ASYNC_RESULT (result));
375
376 /* Check if we're unloaded */
377 if (self->workspace == NULL)
378 return;
379
380 if (!sysprof_check_supported_finish (result, &error))
381 {
382 g_warning ("Sysprof-3 is not supported, will not enable profiler: %s",
383 error->message);
384 return;
385 }
386
387 gtk_widget_insert_action_group (GTK_WIDGET (self->workspace),
388 "profiler",
389 G_ACTION_GROUP (self->actions));
390
391 /* Register our custom run handler to activate the profiler. */
392 context = ide_workspace_get_context (self->workspace);
393 run_manager = ide_run_manager_from_context (context);
394 ide_run_manager_add_handler (run_manager,
395 "profiler",
396 _("Run with Profiler"),
397 "builder-profiler-symbolic",
398 "<primary>F8",
399 profiler_run_handler,
400 self,
401 NULL);
402
403 /* Add the surface to the workspace. */
404 self->surface = g_object_new (GBP_TYPE_SYSPROF_SURFACE,
405 "visible", TRUE,
406 NULL);
407 g_signal_connect (self->surface,
408 "destroy",
409 G_CALLBACK (gtk_widget_destroyed),
410 &self->surface);
411 ide_workspace_add_surface (self->workspace, IDE_SURFACE (self->surface));
412 }
413
414 static void
gbp_sysprof_workspace_addin_load(IdeWorkspaceAddin * addin,IdeWorkspace * workspace)415 gbp_sysprof_workspace_addin_load (IdeWorkspaceAddin *addin,
416 IdeWorkspace *workspace)
417 {
418 GbpSysprofWorkspaceAddin *self = (GbpSysprofWorkspaceAddin *)addin;
419
420 g_assert (IDE_IS_MAIN_THREAD ());
421 g_assert (GBP_IS_SYSPROF_WORKSPACE_ADDIN (self));
422 g_assert (IDE_IS_PRIMARY_WORKSPACE (workspace));
423
424 self->workspace = workspace;
425
426 sysprof_check_supported_async (NULL,
427 gbp_sysprof_workspace_addin_check_supported_cb,
428 g_object_ref (self));
429 }
430
431 static void
gbp_sysprof_workspace_addin_unload(IdeWorkspaceAddin * addin,IdeWorkspace * workspace)432 gbp_sysprof_workspace_addin_unload (IdeWorkspaceAddin *addin,
433 IdeWorkspace *workspace)
434 {
435 GbpSysprofWorkspaceAddin *self = (GbpSysprofWorkspaceAddin *)addin;
436 IdeRunManager *run_manager;
437 IdeContext *context;
438
439 g_assert (GBP_IS_SYSPROF_WORKSPACE_ADDIN (self));
440 g_assert (IDE_IS_WORKSPACE (workspace));
441
442 context = ide_workspace_get_context (workspace);
443
444 gtk_widget_insert_action_group (GTK_WIDGET (workspace), "profiler", NULL);
445
446 run_manager = ide_run_manager_from_context (context);
447 ide_run_manager_remove_handler (run_manager, "profiler");
448
449 if (self->surface != NULL)
450 gtk_widget_destroy (GTK_WIDGET (self->surface));
451
452 self->surface = NULL;
453 self->workspace = NULL;
454 }
455
456 static void
workspace_addin_iface_init(IdeWorkspaceAddinInterface * iface)457 workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface)
458 {
459 iface->load = gbp_sysprof_workspace_addin_load;
460 iface->unload = gbp_sysprof_workspace_addin_unload;
461 }
462