mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2026-04-26 15:21:26 +00:00
tree: start on massive tree refactor
The big thing going on here is that we are going to split up the libraries a bit better, and remove GObject from the capture library. The libsysprof library will bring in the capture library statically, so we can export the symbols we want. Eventually, we will bump the version to sysprof-3, but not yet.
This commit is contained in:
53
src/libsysprof-capture/meson.build
Normal file
53
src/libsysprof-capture/meson.build
Normal file
@ -0,0 +1,53 @@
|
||||
libsysprof_capture_headers = [
|
||||
'sp-capture-condition.h',
|
||||
'sp-capture-cursor.h',
|
||||
'sp-capture-reader.h',
|
||||
'sp-capture-types.h',
|
||||
'sp-capture-writer.h',
|
||||
'sysprof-capture.h',
|
||||
]
|
||||
|
||||
libsysprof_capture_sources = [
|
||||
'sp-capture-condition.c',
|
||||
'sp-capture-cursor.c',
|
||||
'sp-capture-reader.c',
|
||||
'sp-capture-writer.c',
|
||||
]
|
||||
|
||||
configure_file(
|
||||
input: 'sysprof-version.h.in',
|
||||
output: 'sysprof-version.h',
|
||||
configuration: sysprof_version_conf,
|
||||
install_dir: join_paths(get_option('includedir'), sysprof_header_subdir),
|
||||
)
|
||||
|
||||
libsysprof_capture_deps = [
|
||||
dependency('glib-2.0', version: glib_req_version),
|
||||
libshared_dep,
|
||||
]
|
||||
|
||||
install_headers(libsysprof_capture_headers, subdir: sysprof_header_subdir)
|
||||
|
||||
libsysprof_capture = static_library('sysprof-capture-@0@'.format(libsysprof_api_version),
|
||||
libsysprof_capture_sources,
|
||||
dependencies: libsysprof_capture_deps,
|
||||
c_args: [ '-DSYSPROF_CAPTURE_COMPILATION' ],
|
||||
install_dir: get_option('libdir'),
|
||||
install: true,
|
||||
)
|
||||
|
||||
libsysprof_capture_dep = declare_dependency(
|
||||
link_whole: libsysprof_capture,
|
||||
dependencies: libsysprof_capture_deps,
|
||||
include_directories: include_directories('.'),
|
||||
)
|
||||
|
||||
pkgconfig.generate(
|
||||
subdirs: [ sysprof_header_subdir ],
|
||||
version: meson.project_version(),
|
||||
name: 'sysprof-capture-@0@'.format(libsysprof_api_version),
|
||||
filebase: 'sysprof-capture-@0@'.format(libsysprof_api_version),
|
||||
description: 'The static capture library for tools that generate profiling capture data',
|
||||
install_dir: join_paths(get_option('libdir'), 'pkgconfig'),
|
||||
requires: [ 'glib-2.0' ],
|
||||
)
|
||||
357
src/libsysprof-capture/sp-capture-condition.c
Normal file
357
src/libsysprof-capture/sp-capture-condition.c
Normal file
@ -0,0 +1,357 @@
|
||||
/* sp-capture-condition.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
|
||||
#define G_LOG_DOMAIN "sp-capture-condition"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-capture-condition.h"
|
||||
|
||||
/**
|
||||
* SECTION:sp-capture-condition
|
||||
* @title: SpCaptureCondition
|
||||
*
|
||||
* The #SpCaptureCondition type is an abstraction on an operation
|
||||
* for a sort of AST to the #SpCaptureCursor. The goal is that if
|
||||
* we abstract the types of fields we want to match in the cursor
|
||||
* that we can opportunistically add indexes to speed up the operation
|
||||
* later on without changing the implementation of cursor consumers.
|
||||
*/
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SP_CAPTURE_CONDITION_AND,
|
||||
SP_CAPTURE_CONDITION_WHERE_TYPE_IN,
|
||||
SP_CAPTURE_CONDITION_WHERE_TIME_BETWEEN,
|
||||
SP_CAPTURE_CONDITION_WHERE_PID_IN,
|
||||
SP_CAPTURE_CONDITION_WHERE_COUNTER_IN,
|
||||
} SpCaptureConditionType;
|
||||
|
||||
struct _SpCaptureCondition
|
||||
{
|
||||
volatile gint ref_count;
|
||||
SpCaptureConditionType type;
|
||||
union {
|
||||
GArray *where_type_in;
|
||||
struct {
|
||||
gint64 begin;
|
||||
gint64 end;
|
||||
} where_time_between;
|
||||
GArray *where_pid_in;
|
||||
GArray *where_counter_in;
|
||||
struct {
|
||||
SpCaptureCondition *left;
|
||||
SpCaptureCondition *right;
|
||||
} and;
|
||||
} u;
|
||||
};
|
||||
|
||||
gboolean
|
||||
sp_capture_condition_match (const SpCaptureCondition *self,
|
||||
const SpCaptureFrame *frame)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (frame != NULL);
|
||||
|
||||
switch (self->type)
|
||||
{
|
||||
case SP_CAPTURE_CONDITION_AND:
|
||||
return sp_capture_condition_match (self->u.and.left, frame) &&
|
||||
sp_capture_condition_match (self->u.and.right, frame);
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_TYPE_IN:
|
||||
for (guint i = 0; i < self->u.where_type_in->len; i++)
|
||||
{
|
||||
if (frame->type == g_array_index (self->u.where_type_in, SpCaptureFrameType, i))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_TIME_BETWEEN:
|
||||
return (frame->time >= self->u.where_time_between.begin && frame->time <= self->u.where_time_between.end);
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_PID_IN:
|
||||
for (guint i = 0; i < self->u.where_pid_in->len; i++)
|
||||
{
|
||||
if (frame->pid == g_array_index (self->u.where_pid_in, gint32, i))
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_COUNTER_IN:
|
||||
if (frame->type == SP_CAPTURE_FRAME_CTRSET)
|
||||
{
|
||||
const SpCaptureFrameCounterSet *set = (SpCaptureFrameCounterSet *)frame;
|
||||
|
||||
for (guint i = 0; i < self->u.where_counter_in->len; i++)
|
||||
{
|
||||
guint counter = g_array_index (self->u.where_counter_in, guint, i);
|
||||
|
||||
for (guint j = 0; j < set->n_values; j++)
|
||||
{
|
||||
if (counter == set->values[j].ids[0] ||
|
||||
counter == set->values[j].ids[1] ||
|
||||
counter == set->values[j].ids[2] ||
|
||||
counter == set->values[j].ids[3] ||
|
||||
counter == set->values[j].ids[4] ||
|
||||
counter == set->values[j].ids[5] ||
|
||||
counter == set->values[j].ids[6] ||
|
||||
counter == set->values[j].ids[7])
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (frame->type == SP_CAPTURE_FRAME_CTRDEF)
|
||||
{
|
||||
const SpCaptureFrameCounterDefine *def = (SpCaptureFrameCounterDefine *)frame;
|
||||
|
||||
for (guint i = 0; i < self->u.where_counter_in->len; i++)
|
||||
{
|
||||
guint counter = g_array_index (self->u.where_counter_in, guint, i);
|
||||
|
||||
for (guint j = 0; j < def->n_counters; j++)
|
||||
{
|
||||
if (def->counters[j].id == counter)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
g_assert_not_reached ();
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static SpCaptureCondition *
|
||||
sp_capture_condition_init (void)
|
||||
{
|
||||
SpCaptureCondition *self;
|
||||
|
||||
self = g_slice_new0 (SpCaptureCondition);
|
||||
self->ref_count = 1;
|
||||
|
||||
return g_steal_pointer (&self);
|
||||
}
|
||||
|
||||
SpCaptureCondition *
|
||||
sp_capture_condition_copy (const SpCaptureCondition *self)
|
||||
{
|
||||
SpCaptureCondition *copy;
|
||||
|
||||
copy = sp_capture_condition_init ();
|
||||
copy->type = self->type;
|
||||
|
||||
switch (self->type)
|
||||
{
|
||||
case SP_CAPTURE_CONDITION_AND:
|
||||
return sp_capture_condition_new_and (
|
||||
sp_capture_condition_copy (self->u.and.left),
|
||||
sp_capture_condition_copy (self->u.and.right));
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_TYPE_IN:
|
||||
return sp_capture_condition_new_where_type_in (
|
||||
self->u.where_type_in->len,
|
||||
(const SpCaptureFrameType *)(gpointer)self->u.where_type_in->data);
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_TIME_BETWEEN:
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_PID_IN:
|
||||
return sp_capture_condition_new_where_pid_in (
|
||||
self->u.where_pid_in->len,
|
||||
(const gint32 *)(gpointer)self->u.where_pid_in->data);
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_COUNTER_IN:
|
||||
return sp_capture_condition_new_where_counter_in (
|
||||
self->u.where_counter_in->len,
|
||||
(const guint *)(gpointer)self->u.where_counter_in->data);
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_capture_condition_finalize (SpCaptureCondition *self)
|
||||
{
|
||||
switch (self->type)
|
||||
{
|
||||
case SP_CAPTURE_CONDITION_AND:
|
||||
sp_capture_condition_unref (self->u.and.left);
|
||||
sp_capture_condition_unref (self->u.and.right);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_TYPE_IN:
|
||||
g_array_free (self->u.where_type_in, TRUE);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_TIME_BETWEEN:
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_PID_IN:
|
||||
g_array_free (self->u.where_pid_in, TRUE);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_CONDITION_WHERE_COUNTER_IN:
|
||||
g_array_free (self->u.where_counter_in, TRUE);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
g_slice_free (SpCaptureCondition, self);
|
||||
}
|
||||
|
||||
SpCaptureCondition *
|
||||
sp_capture_condition_ref (SpCaptureCondition *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
g_return_val_if_fail (self->ref_count > 0, NULL);
|
||||
|
||||
g_atomic_int_inc (&self->ref_count);
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
sp_capture_condition_unref (SpCaptureCondition *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->ref_count > 0);
|
||||
|
||||
if (g_atomic_int_dec_and_test (&self->ref_count))
|
||||
sp_capture_condition_finalize (self);
|
||||
}
|
||||
|
||||
SpCaptureCondition *
|
||||
sp_capture_condition_new_where_type_in (guint n_types,
|
||||
const SpCaptureFrameType *types)
|
||||
{
|
||||
SpCaptureCondition *self;
|
||||
|
||||
g_return_val_if_fail (types != NULL, NULL);
|
||||
|
||||
self = sp_capture_condition_init ();
|
||||
self->type = SP_CAPTURE_CONDITION_WHERE_TYPE_IN;
|
||||
self->u.where_type_in = g_array_sized_new (FALSE, FALSE, sizeof (SpCaptureFrameType), n_types);
|
||||
g_array_set_size (self->u.where_type_in, n_types);
|
||||
memcpy (self->u.where_type_in->data, types, sizeof (SpCaptureFrameType) * n_types);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
SpCaptureCondition *
|
||||
sp_capture_condition_new_where_time_between (gint64 begin_time,
|
||||
gint64 end_time)
|
||||
{
|
||||
SpCaptureCondition *self;
|
||||
|
||||
if G_UNLIKELY (begin_time > end_time)
|
||||
{
|
||||
gint64 tmp = begin_time;
|
||||
begin_time = end_time;
|
||||
end_time = tmp;
|
||||
}
|
||||
|
||||
self = sp_capture_condition_init ();
|
||||
self->type = SP_CAPTURE_CONDITION_WHERE_TIME_BETWEEN;
|
||||
self->u.where_time_between.begin = begin_time;
|
||||
self->u.where_time_between.end = end_time;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
SpCaptureCondition *
|
||||
sp_capture_condition_new_where_pid_in (guint n_pids,
|
||||
const gint32 *pids)
|
||||
{
|
||||
SpCaptureCondition *self;
|
||||
|
||||
g_return_val_if_fail (pids != NULL, NULL);
|
||||
|
||||
self = sp_capture_condition_init ();
|
||||
self->type = SP_CAPTURE_CONDITION_WHERE_PID_IN;
|
||||
self->u.where_pid_in = g_array_sized_new (FALSE, FALSE, sizeof (gint32), n_pids);
|
||||
g_array_set_size (self->u.where_pid_in, n_pids);
|
||||
memcpy (self->u.where_pid_in->data, pids, sizeof (gint32) * n_pids);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
SpCaptureCondition *
|
||||
sp_capture_condition_new_where_counter_in (guint n_counters,
|
||||
const guint *counters)
|
||||
{
|
||||
SpCaptureCondition *self;
|
||||
|
||||
g_return_val_if_fail (counters != NULL || n_counters == 0, NULL);
|
||||
|
||||
self = sp_capture_condition_init ();
|
||||
self->type = SP_CAPTURE_CONDITION_WHERE_COUNTER_IN;
|
||||
self->u.where_counter_in = g_array_sized_new (FALSE, FALSE, sizeof (guint), n_counters);
|
||||
|
||||
if (n_counters > 0)
|
||||
{
|
||||
g_array_set_size (self->u.where_counter_in, n_counters);
|
||||
memcpy (self->u.where_counter_in->data, counters, sizeof (guint) * n_counters);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_condition_new_and:
|
||||
* @left: (transfer full): An #SpCaptureCondition
|
||||
* @right: (transfer full): An #SpCaptureCondition
|
||||
*
|
||||
* Creates a new #SpCaptureCondition that requires both left and right
|
||||
* to evaluate to %TRUE.
|
||||
*
|
||||
* Returns: (transfer full): A new #SpCaptureCondition.
|
||||
*/
|
||||
SpCaptureCondition *
|
||||
sp_capture_condition_new_and (SpCaptureCondition *left,
|
||||
SpCaptureCondition *right)
|
||||
{
|
||||
SpCaptureCondition *self;
|
||||
|
||||
g_return_val_if_fail (left != NULL, NULL);
|
||||
g_return_val_if_fail (right != NULL, NULL);
|
||||
|
||||
self = sp_capture_condition_init ();
|
||||
self->type = SP_CAPTURE_CONDITION_AND;
|
||||
self->u.and.left = left;
|
||||
self->u.and.right = right;
|
||||
|
||||
return self;
|
||||
}
|
||||
45
src/libsysprof-capture/sp-capture-condition.h
Normal file
45
src/libsysprof-capture/sp-capture-condition.h
Normal file
@ -0,0 +1,45 @@
|
||||
/* sp-capture-condition.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sp-capture-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
SpCaptureCondition *sp_capture_condition_copy (const SpCaptureCondition *self);
|
||||
void sp_capture_condition_unref (SpCaptureCondition *self);
|
||||
SpCaptureCondition *sp_capture_condition_ref (SpCaptureCondition *self);
|
||||
SpCaptureCondition *sp_capture_condition_new_and (SpCaptureCondition *left,
|
||||
SpCaptureCondition *right);
|
||||
SpCaptureCondition *sp_capture_condition_new_where_type_in (guint n_types,
|
||||
const SpCaptureFrameType *types);
|
||||
SpCaptureCondition *sp_capture_condition_new_where_time_between (gint64 begin_time,
|
||||
gint64 end_time);
|
||||
SpCaptureCondition *sp_capture_condition_new_where_pid_in (guint n_pids,
|
||||
const gint32 *pids);
|
||||
SpCaptureCondition *sp_capture_condition_new_where_counter_in (guint n_counters,
|
||||
const guint *counters);
|
||||
gboolean sp_capture_condition_match (const SpCaptureCondition *self,
|
||||
const SpCaptureFrame *frame);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpCaptureCondition, sp_capture_condition_unref)
|
||||
|
||||
G_END_DECLS
|
||||
265
src/libsysprof-capture/sp-capture-cursor.c
Normal file
265
src/libsysprof-capture/sp-capture-cursor.c
Normal file
@ -0,0 +1,265 @@
|
||||
/* sp-capture-cursor.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-capture-cursor"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "sp-capture-condition.h"
|
||||
#include "sp-capture-cursor.h"
|
||||
#include "sp-capture-reader.h"
|
||||
|
||||
#define READ_DELEGATE(f) ((ReadDelegate)(f))
|
||||
|
||||
typedef const SpCaptureFrame *(*ReadDelegate) (SpCaptureReader *);
|
||||
|
||||
struct _SpCaptureCursor
|
||||
{
|
||||
volatile gint ref_count;
|
||||
GPtrArray *conditions;
|
||||
SpCaptureReader *reader;
|
||||
guint reversed : 1;
|
||||
};
|
||||
|
||||
static void
|
||||
sp_capture_cursor_finalize (SpCaptureCursor *self)
|
||||
{
|
||||
g_clear_pointer (&self->conditions, g_ptr_array_unref);
|
||||
g_clear_pointer (&self->reader, sp_capture_reader_unref);
|
||||
g_slice_free (SpCaptureCursor, self);
|
||||
}
|
||||
|
||||
static SpCaptureCursor *
|
||||
sp_capture_cursor_init (void)
|
||||
{
|
||||
SpCaptureCursor *self;
|
||||
|
||||
self = g_slice_new0 (SpCaptureCursor);
|
||||
self->conditions = g_ptr_array_new_with_free_func ((GDestroyNotify) sp_capture_condition_unref);
|
||||
self->ref_count = 1;
|
||||
|
||||
return g_steal_pointer (&self);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_cursor_ref:
|
||||
* @self: a #SpCaptureCursor
|
||||
*
|
||||
* Returns: (transfer full): @self
|
||||
*
|
||||
* Since: 3.34
|
||||
*/
|
||||
SpCaptureCursor *
|
||||
sp_capture_cursor_ref (SpCaptureCursor *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
g_return_val_if_fail (self->ref_count > 0, NULL);
|
||||
|
||||
g_atomic_int_inc (&self->ref_count);
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_cursor_unref:
|
||||
* @self: a #SpCaptureCursor
|
||||
*
|
||||
* Since: 3.34
|
||||
*/
|
||||
void
|
||||
sp_capture_cursor_unref (SpCaptureCursor *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->ref_count > 0);
|
||||
|
||||
if (g_atomic_int_dec_and_test (&self->ref_count))
|
||||
sp_capture_cursor_finalize (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_cursor_foreach:
|
||||
* @self: a #SpCaptureCursor
|
||||
* @callback: (scope call): a closure to execute
|
||||
* @user_data: user data for @callback
|
||||
*
|
||||
*/
|
||||
void
|
||||
sp_capture_cursor_foreach (SpCaptureCursor *self,
|
||||
SpCaptureCursorCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->reader != NULL);
|
||||
g_return_if_fail (callback != NULL);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const SpCaptureFrame *frame;
|
||||
SpCaptureFrameType type = 0;
|
||||
ReadDelegate delegate = NULL;
|
||||
|
||||
if (!sp_capture_reader_peek_type (self->reader, &type))
|
||||
return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case SP_CAPTURE_FRAME_TIMESTAMP:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_timestamp);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_SAMPLE:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_sample);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_MAP:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_map);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_MARK:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_mark);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_PROCESS:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_process);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_FORK:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_fork);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_EXIT:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_exit);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_JITMAP:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_jitmap);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_CTRDEF:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_counter_define);
|
||||
break;
|
||||
|
||||
case SP_CAPTURE_FRAME_CTRSET:
|
||||
delegate = READ_DELEGATE (sp_capture_reader_read_counter_set);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!sp_capture_reader_skip (self->reader))
|
||||
return;
|
||||
delegate = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (delegate == NULL)
|
||||
continue;
|
||||
|
||||
if (NULL == (frame = delegate (self->reader)))
|
||||
return;
|
||||
|
||||
if (self->conditions->len == 0)
|
||||
{
|
||||
if (!callback (frame, user_data))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (guint i = 0; i < self->conditions->len; i++)
|
||||
{
|
||||
const SpCaptureCondition *condition = g_ptr_array_index (self->conditions, i);
|
||||
|
||||
if (sp_capture_condition_match (condition, frame))
|
||||
{
|
||||
if (!callback (frame, user_data))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_capture_cursor_reset (SpCaptureCursor *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->reader != NULL);
|
||||
|
||||
sp_capture_reader_reset (self->reader);
|
||||
}
|
||||
|
||||
void
|
||||
sp_capture_cursor_reverse (SpCaptureCursor *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
self->reversed = !self->reversed;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_cursor_add_condition:
|
||||
* @self: An #SpCaptureCursor
|
||||
* @condition: (transfer full): An #SpCaptureCondition
|
||||
*
|
||||
* Adds the condition to the cursor. This condition must evaluate to
|
||||
* true or a given #SpCapureFrame will not be matched.
|
||||
*/
|
||||
void
|
||||
sp_capture_cursor_add_condition (SpCaptureCursor *self,
|
||||
SpCaptureCondition *condition)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (condition != NULL);
|
||||
|
||||
g_ptr_array_add (self->conditions, condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_cursor_new:
|
||||
* @self: a #SpCaptureCursor
|
||||
*
|
||||
* Returns: (transfer full): a new cursor for @reader
|
||||
*/
|
||||
SpCaptureCursor *
|
||||
sp_capture_cursor_new (SpCaptureReader *reader)
|
||||
{
|
||||
SpCaptureCursor *self;
|
||||
|
||||
g_return_val_if_fail (reader != NULL, NULL);
|
||||
|
||||
self = sp_capture_cursor_init ();
|
||||
self->reader = sp_capture_reader_copy (reader);
|
||||
sp_capture_reader_reset (self->reader);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_cursor_get_reader:
|
||||
*
|
||||
* Gets the underlying reader that is used by the cursor.
|
||||
*
|
||||
* Returns: (transfer none): An #SpCaptureReader
|
||||
*/
|
||||
SpCaptureReader *
|
||||
sp_capture_cursor_get_reader (SpCaptureCursor *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
|
||||
return self->reader;
|
||||
}
|
||||
57
src/libsysprof-capture/sp-capture-cursor.h
Normal file
57
src/libsysprof-capture/sp-capture-cursor.h
Normal file
@ -0,0 +1,57 @@
|
||||
/* sp-capture-cursor.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sp-capture-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _SpCaptureCursor SpCaptureCursor;
|
||||
|
||||
/**
|
||||
* SpCaptureCursorCallback:
|
||||
*
|
||||
* This is the prototype for callbacks that are called for each frame
|
||||
* matching the cursor query.
|
||||
*
|
||||
* Functions matching this typedef should return %TRUE if they want the
|
||||
* the caller to stop iteration of cursor.
|
||||
*
|
||||
* Returns: %TRUE if iteration should stop, otherwise %FALSE.
|
||||
*/
|
||||
typedef gboolean (*SpCaptureCursorCallback) (const SpCaptureFrame *frame,
|
||||
gpointer user_data);
|
||||
|
||||
SpCaptureCursor *sp_capture_cursor_new (SpCaptureReader *reader);
|
||||
void sp_capture_cursor_unref (SpCaptureCursor *self);
|
||||
SpCaptureCursor *sp_capture_cursor_ref (SpCaptureCursor *self);
|
||||
SpCaptureReader *sp_capture_cursor_get_reader (SpCaptureCursor *self);
|
||||
void sp_capture_cursor_foreach (SpCaptureCursor *self,
|
||||
SpCaptureCursorCallback callback,
|
||||
gpointer user_data);
|
||||
void sp_capture_cursor_reset (SpCaptureCursor *self);
|
||||
void sp_capture_cursor_reverse (SpCaptureCursor *self);
|
||||
void sp_capture_cursor_add_condition (SpCaptureCursor *self,
|
||||
SpCaptureCondition *condition);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpCaptureCursor, sp_capture_cursor_unref)
|
||||
|
||||
G_END_DECLS
|
||||
950
src/libsysprof-capture/sp-capture-reader.c
Normal file
950
src/libsysprof-capture/sp-capture-reader.c
Normal file
@ -0,0 +1,950 @@
|
||||
/* sp-capture-reader.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-capture-reader.h"
|
||||
#include "sp-capture-util-private.h"
|
||||
#include "sp-capture-writer.h"
|
||||
|
||||
struct _SpCaptureReader
|
||||
{
|
||||
volatile gint ref_count;
|
||||
gchar *filename;
|
||||
guint8 *buf;
|
||||
gsize bufsz;
|
||||
gsize len;
|
||||
gsize pos;
|
||||
gsize fd_off;
|
||||
int fd;
|
||||
gint endian;
|
||||
SpCaptureFileHeader header;
|
||||
gint64 end_time;
|
||||
};
|
||||
|
||||
#ifdef SP_ENABLE_GOBJECT
|
||||
G_DEFINE_BOXED_TYPE (SpCaptureReader, sp_capture_reader,
|
||||
sp_capture_reader_ref, sp_capture_reader_unref)
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
sp_capture_reader_read_file_header (SpCaptureReader *self,
|
||||
SpCaptureFileHeader *header,
|
||||
GError **error)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (header != NULL);
|
||||
|
||||
if (sizeof *header != _sp_pread (self->fd, header, sizeof *header, 0L))
|
||||
{
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR,
|
||||
g_file_error_from_errno (errno),
|
||||
"%s", g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (header->magic != SP_CAPTURE_MAGIC)
|
||||
{
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR,
|
||||
G_FILE_ERROR_FAILED,
|
||||
"Capture file magic does not match");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
header->capture_time[sizeof header->capture_time - 1] = '\0';
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_capture_reader_finalize (SpCaptureReader *self)
|
||||
{
|
||||
if (self != NULL)
|
||||
{
|
||||
close (self->fd);
|
||||
g_free (self->buf);
|
||||
g_free (self->filename);
|
||||
g_free (self);
|
||||
}
|
||||
}
|
||||
|
||||
const gchar *
|
||||
sp_capture_reader_get_time (SpCaptureReader *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
|
||||
return self->header.capture_time;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
sp_capture_reader_get_filename (SpCaptureReader *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
|
||||
return self->filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_reader_new_from_fd:
|
||||
* @fd: an fd to take ownership from
|
||||
* @error: a location for a #GError or %NULL
|
||||
*
|
||||
* Creates a new reader using the file-descriptor.
|
||||
*
|
||||
* This is useful if you don't necessarily have access to the filename itself.
|
||||
*
|
||||
* Returns: (transfer full): an #SpCaptureReader or %NULL upon failure.
|
||||
*/
|
||||
SpCaptureReader *
|
||||
sp_capture_reader_new_from_fd (int fd,
|
||||
GError **error)
|
||||
{
|
||||
SpCaptureReader *self;
|
||||
|
||||
g_assert (fd > -1);
|
||||
|
||||
self = g_new0 (SpCaptureReader, 1);
|
||||
self->ref_count = 1;
|
||||
self->bufsz = G_MAXUSHORT * 2;
|
||||
self->buf = g_malloc (self->bufsz);
|
||||
self->len = 0;
|
||||
self->pos = 0;
|
||||
self->fd = fd;
|
||||
self->fd_off = sizeof (SpCaptureFileHeader);
|
||||
|
||||
if (!sp_capture_reader_read_file_header (self, &self->header, error))
|
||||
{
|
||||
sp_capture_reader_finalize (self);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (self->header.little_endian)
|
||||
self->endian = G_LITTLE_ENDIAN;
|
||||
else
|
||||
self->endian = G_BIG_ENDIAN;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
SpCaptureReader *
|
||||
sp_capture_reader_new (const gchar *filename,
|
||||
GError **error)
|
||||
{
|
||||
SpCaptureReader *self;
|
||||
int fd;
|
||||
|
||||
g_assert (filename != NULL);
|
||||
|
||||
if (-1 == (fd = open (filename, O_RDONLY, 0)))
|
||||
{
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR,
|
||||
g_file_error_from_errno (errno),
|
||||
"%s", g_strerror (errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (NULL == (self = sp_capture_reader_new_from_fd (fd, error)))
|
||||
{
|
||||
close (fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->filename = g_strdup (filename);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sp_capture_reader_bswap_frame (SpCaptureReader *self,
|
||||
SpCaptureFrame *frame)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (frame!= NULL);
|
||||
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
{
|
||||
frame->len = GUINT16_SWAP_LE_BE (frame->len);
|
||||
frame->cpu = GUINT16_SWAP_LE_BE (frame->cpu);
|
||||
frame->pid = GUINT32_SWAP_LE_BE (frame->pid);
|
||||
frame->time = GUINT64_SWAP_LE_BE (frame->time);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
sp_capture_reader_bswap_map (SpCaptureReader *self,
|
||||
SpCaptureMap *map)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (map != NULL);
|
||||
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
{
|
||||
map->start = GUINT64_SWAP_LE_BE (map->start);
|
||||
map->end = GUINT64_SWAP_LE_BE (map->end);
|
||||
map->offset = GUINT64_SWAP_LE_BE (map->offset);
|
||||
map->inode = GUINT64_SWAP_LE_BE (map->inode);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
sp_capture_reader_bswap_mark (SpCaptureReader *self,
|
||||
SpCaptureMark *mark)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (mark != NULL);
|
||||
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
mark->duration = GUINT64_SWAP_LE_BE (mark->duration);
|
||||
}
|
||||
|
||||
static inline void
|
||||
sp_capture_reader_bswap_jitmap (SpCaptureReader *self,
|
||||
SpCaptureJitmap *jitmap)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (jitmap != NULL);
|
||||
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
jitmap->n_jitmaps = GUINT64_SWAP_LE_BE (jitmap->n_jitmaps);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_capture_reader_ensure_space_for (SpCaptureReader *self,
|
||||
gsize len)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->pos <= self->len);
|
||||
g_assert (len > 0);
|
||||
|
||||
if ((self->len - self->pos) < len)
|
||||
{
|
||||
gssize r;
|
||||
|
||||
if (self->len > self->pos)
|
||||
memmove (self->buf, &self->buf[self->pos], self->len - self->pos);
|
||||
self->len -= self->pos;
|
||||
self->pos = 0;
|
||||
|
||||
while (self->len < len)
|
||||
{
|
||||
g_assert ((self->pos + self->len) < self->bufsz);
|
||||
g_assert (self->len < self->bufsz);
|
||||
|
||||
/* Read into our buffer after our current read position */
|
||||
r = _sp_pread (self->fd,
|
||||
&self->buf[self->len],
|
||||
self->bufsz - self->len,
|
||||
self->fd_off);
|
||||
|
||||
if (r <= 0)
|
||||
break;
|
||||
|
||||
self->fd_off += r;
|
||||
self->len += r;
|
||||
}
|
||||
}
|
||||
|
||||
return (self->len - self->pos) >= len;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_capture_reader_skip (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureFrame *frame;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof (SpCaptureFrame)))
|
||||
return FALSE;
|
||||
|
||||
frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
|
||||
sp_capture_reader_bswap_frame (self, frame);
|
||||
|
||||
if (frame->len < sizeof (SpCaptureFrame))
|
||||
return FALSE;
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, frame->len))
|
||||
return FALSE;
|
||||
|
||||
frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
self->pos += frame->len;
|
||||
|
||||
if ((self->pos % SP_CAPTURE_ALIGN) != 0)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_capture_reader_peek_frame (SpCaptureReader *self,
|
||||
SpCaptureFrame *frame)
|
||||
{
|
||||
SpCaptureFrame *real_frame;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->len);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof *real_frame))
|
||||
return FALSE;
|
||||
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
|
||||
real_frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
*frame = *real_frame;
|
||||
|
||||
sp_capture_reader_bswap_frame (self, frame);
|
||||
|
||||
if (frame->time > self->end_time)
|
||||
self->end_time = frame->time;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_capture_reader_peek_type (SpCaptureReader *self,
|
||||
SpCaptureFrameType *type)
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (type != NULL);
|
||||
|
||||
if (!sp_capture_reader_peek_frame (self, &frame))
|
||||
return FALSE;
|
||||
|
||||
*type = frame.type;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static const SpCaptureFrame *
|
||||
sp_capture_reader_read_basic (SpCaptureReader *self,
|
||||
SpCaptureFrameType type,
|
||||
gsize extra)
|
||||
{
|
||||
SpCaptureFrame *frame;
|
||||
gsize len = sizeof *frame + extra;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, len))
|
||||
return NULL;
|
||||
|
||||
frame = (SpCaptureFrame *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
sp_capture_reader_bswap_frame (self, frame);
|
||||
|
||||
if (frame->len < len)
|
||||
return NULL;
|
||||
|
||||
if (frame->type != type)
|
||||
return NULL;
|
||||
|
||||
self->pos += frame->len;
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
const SpCaptureTimestamp *
|
||||
sp_capture_reader_read_timestamp (SpCaptureReader *self)
|
||||
{
|
||||
return (SpCaptureTimestamp *)
|
||||
sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_TIMESTAMP, 0);
|
||||
}
|
||||
|
||||
const SpCaptureExit *
|
||||
sp_capture_reader_read_exit (SpCaptureReader *self)
|
||||
{
|
||||
return (SpCaptureExit *)
|
||||
sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_EXIT, 0);
|
||||
}
|
||||
|
||||
const SpCaptureFork *
|
||||
sp_capture_reader_read_fork (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureFork *fk;
|
||||
|
||||
g_assert (self != NULL);
|
||||
|
||||
fk = (SpCaptureFork *)
|
||||
sp_capture_reader_read_basic (self, SP_CAPTURE_FRAME_FORK, sizeof(guint32));
|
||||
|
||||
if (fk != NULL)
|
||||
{
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
fk->child_pid = GUINT32_SWAP_LE_BE (fk->child_pid);
|
||||
}
|
||||
|
||||
return fk;
|
||||
}
|
||||
|
||||
const SpCaptureMap *
|
||||
sp_capture_reader_read_map (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureMap *map;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof *map))
|
||||
return NULL;
|
||||
|
||||
map = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
sp_capture_reader_bswap_frame (self, &map->frame);
|
||||
|
||||
if (map->frame.type != SP_CAPTURE_FRAME_MAP)
|
||||
return NULL;
|
||||
|
||||
if (map->frame.len < (sizeof *map + 1))
|
||||
return NULL;
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, map->frame.len))
|
||||
return NULL;
|
||||
|
||||
map = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
if (self->buf[self->pos + map->frame.len - 1] != '\0')
|
||||
return NULL;
|
||||
|
||||
sp_capture_reader_bswap_map (self, map);
|
||||
|
||||
self->pos += map->frame.len;
|
||||
|
||||
if ((self->pos % SP_CAPTURE_ALIGN) != 0)
|
||||
return NULL;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
const SpCaptureMark *
|
||||
sp_capture_reader_read_mark (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureMark *mark;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof *mark))
|
||||
return NULL;
|
||||
|
||||
mark = (SpCaptureMark *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
sp_capture_reader_bswap_frame (self, &mark->frame);
|
||||
|
||||
if (mark->frame.type != SP_CAPTURE_FRAME_MARK)
|
||||
return NULL;
|
||||
|
||||
if (mark->frame.len < (sizeof *mark + 1))
|
||||
return NULL;
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, mark->frame.len))
|
||||
return NULL;
|
||||
|
||||
mark = (SpCaptureMark *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
sp_capture_reader_bswap_mark (self, mark);
|
||||
|
||||
self->pos += mark->frame.len;
|
||||
|
||||
if ((self->pos % SP_CAPTURE_ALIGN) != 0)
|
||||
return NULL;
|
||||
|
||||
/* Ensure trailing \0 in name and message */
|
||||
mark->name[sizeof mark->name - 1] = 0;
|
||||
self->buf[self->pos + mark->frame.len - 1] = 0;
|
||||
|
||||
return mark;
|
||||
}
|
||||
|
||||
const SpCaptureProcess *
|
||||
sp_capture_reader_read_process (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureProcess *process;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof *process))
|
||||
return NULL;
|
||||
|
||||
process = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
sp_capture_reader_bswap_frame (self, &process->frame);
|
||||
|
||||
if (process->frame.type != SP_CAPTURE_FRAME_PROCESS)
|
||||
return NULL;
|
||||
|
||||
if (process->frame.len < (sizeof *process + 1))
|
||||
return NULL;
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, process->frame.len))
|
||||
return NULL;
|
||||
|
||||
process = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
if (self->buf[self->pos + process->frame.len - 1] != '\0')
|
||||
return NULL;
|
||||
|
||||
self->pos += process->frame.len;
|
||||
|
||||
if ((self->pos % SP_CAPTURE_ALIGN) != 0)
|
||||
return NULL;
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
GHashTable *
|
||||
sp_capture_reader_read_jitmap (SpCaptureReader *self)
|
||||
{
|
||||
g_autoptr(GHashTable) ret = NULL;
|
||||
SpCaptureJitmap *jitmap;
|
||||
guint8 *buf;
|
||||
guint8 *endptr;
|
||||
guint i;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof *jitmap))
|
||||
return NULL;
|
||||
|
||||
jitmap = (SpCaptureJitmap *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
sp_capture_reader_bswap_frame (self, &jitmap->frame);
|
||||
|
||||
if (jitmap->frame.type != SP_CAPTURE_FRAME_JITMAP)
|
||||
return NULL;
|
||||
|
||||
if (jitmap->frame.len < sizeof *jitmap)
|
||||
return NULL;
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, jitmap->frame.len))
|
||||
return NULL;
|
||||
|
||||
jitmap = (SpCaptureJitmap *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
ret = g_hash_table_new_full (NULL, NULL, NULL, g_free);
|
||||
|
||||
buf = jitmap->data;
|
||||
endptr = &self->buf[self->pos + jitmap->frame.len];
|
||||
|
||||
for (i = 0; i < jitmap->n_jitmaps; i++)
|
||||
{
|
||||
SpCaptureAddress addr;
|
||||
const gchar *str;
|
||||
|
||||
if (buf + sizeof addr >= endptr)
|
||||
return NULL;
|
||||
|
||||
memcpy (&addr, buf, sizeof addr);
|
||||
buf += sizeof addr;
|
||||
|
||||
str = (gchar *)buf;
|
||||
|
||||
buf = memchr (buf, '\0', (endptr - buf));
|
||||
|
||||
if (buf == NULL)
|
||||
return NULL;
|
||||
|
||||
buf++;
|
||||
|
||||
g_hash_table_insert (ret, GSIZE_TO_POINTER (addr), g_strdup (str));
|
||||
}
|
||||
|
||||
sp_capture_reader_bswap_jitmap (self, jitmap);
|
||||
|
||||
self->pos += jitmap->frame.len;
|
||||
|
||||
return g_steal_pointer (&ret);
|
||||
}
|
||||
|
||||
const SpCaptureSample *
|
||||
sp_capture_reader_read_sample (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureSample *sample;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof *sample))
|
||||
return NULL;
|
||||
|
||||
sample = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
sp_capture_reader_bswap_frame (self, &sample->frame);
|
||||
|
||||
if (sample->frame.type != SP_CAPTURE_FRAME_SAMPLE)
|
||||
return NULL;
|
||||
|
||||
if (sample->frame.len < sizeof *sample)
|
||||
return NULL;
|
||||
|
||||
if (self->endian != G_BYTE_ORDER)
|
||||
sample->n_addrs = GUINT16_SWAP_LE_BE (sample->n_addrs);
|
||||
|
||||
if (sample->frame.len < (sizeof *sample + (sizeof(SpCaptureAddress) * sample->n_addrs)))
|
||||
return NULL;
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sample->frame.len))
|
||||
return NULL;
|
||||
|
||||
sample = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < sample->n_addrs; i++)
|
||||
sample->addrs[i] = GUINT64_SWAP_LE_BE (sample->addrs[i]);
|
||||
}
|
||||
|
||||
self->pos += sample->frame.len;
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
const SpCaptureFrameCounterDefine *
|
||||
sp_capture_reader_read_counter_define (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureFrameCounterDefine *def;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof *def))
|
||||
return NULL;
|
||||
|
||||
def = (SpCaptureFrameCounterDefine *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
if (def->frame.type != SP_CAPTURE_FRAME_CTRDEF)
|
||||
return NULL;
|
||||
|
||||
if (def->frame.len < sizeof *def)
|
||||
return NULL;
|
||||
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
def->n_counters = GUINT16_SWAP_LE_BE (def->n_counters);
|
||||
|
||||
if (def->frame.len < (sizeof *def + (sizeof (SpCaptureFrameCounterDefine) * def->n_counters)))
|
||||
return NULL;
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, def->frame.len))
|
||||
return NULL;
|
||||
|
||||
def = (SpCaptureFrameCounterDefine *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < def->n_counters; i++)
|
||||
{
|
||||
def->counters[i].id = GUINT32_SWAP_LE_BE (def->counters[i].id);
|
||||
def->counters[i].value.v64 = GUINT64_SWAP_LE_BE (def->counters[i].value.v64);
|
||||
}
|
||||
}
|
||||
|
||||
self->pos += def->frame.len;
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
const SpCaptureFrameCounterSet *
|
||||
sp_capture_reader_read_counter_set (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureFrameCounterSet *set;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
||||
g_assert (self->pos <= self->bufsz);
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, sizeof *set))
|
||||
return NULL;
|
||||
|
||||
set = (SpCaptureFrameCounterSet *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
if (set->frame.type != SP_CAPTURE_FRAME_CTRSET)
|
||||
return NULL;
|
||||
|
||||
if (set->frame.len < sizeof *set)
|
||||
return NULL;
|
||||
|
||||
if (self->endian != G_BYTE_ORDER)
|
||||
set->n_values = GUINT16_SWAP_LE_BE (set->n_values);
|
||||
|
||||
if (set->frame.len < (sizeof *set + (sizeof (SpCaptureCounterValues) * set->n_values)))
|
||||
return NULL;
|
||||
|
||||
if (!sp_capture_reader_ensure_space_for (self, set->frame.len))
|
||||
return NULL;
|
||||
|
||||
set = (SpCaptureFrameCounterSet *)(gpointer)&self->buf[self->pos];
|
||||
|
||||
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < set->n_values; i++)
|
||||
{
|
||||
guint j;
|
||||
|
||||
for (j = 0; j < G_N_ELEMENTS (set->values[0].values); j++)
|
||||
{
|
||||
set->values[i].ids[j] = GUINT32_SWAP_LE_BE (set->values[i].ids[j]);
|
||||
set->values[i].values[j].v64 = GUINT64_SWAP_LE_BE (set->values[i].values[j].v64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self->pos += set->frame.len;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_capture_reader_reset (SpCaptureReader *self)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
|
||||
self->fd_off = sizeof (SpCaptureFileHeader);
|
||||
self->pos = 0;
|
||||
self->len = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
SpCaptureReader *
|
||||
sp_capture_reader_ref (SpCaptureReader *self)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->ref_count > 0);
|
||||
|
||||
g_atomic_int_inc (&self->ref_count);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
sp_capture_reader_unref (SpCaptureReader *self)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->ref_count > 0);
|
||||
|
||||
if (g_atomic_int_dec_and_test (&self->ref_count))
|
||||
sp_capture_reader_finalize (self);
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_capture_reader_splice (SpCaptureReader *self,
|
||||
SpCaptureWriter *dest,
|
||||
GError **error)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->fd != -1);
|
||||
g_assert (dest != NULL);
|
||||
|
||||
/* Flush before writing anything to ensure consistency */
|
||||
if (!sp_capture_writer_flush (dest))
|
||||
{
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR,
|
||||
g_file_error_from_errno (errno),
|
||||
"%s", g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't need to track position because writer will
|
||||
* track the current position to avoid reseting it.
|
||||
*/
|
||||
|
||||
/* Perform the splice */
|
||||
return _sp_capture_writer_splice_from_fd (dest, self->fd, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_reader_save_as:
|
||||
* @self: An #SpCaptureReader
|
||||
* @filename: the file to save the capture as
|
||||
* @error: a location for a #GError or %NULL.
|
||||
*
|
||||
* This is a convenience function for copying a capture file for which
|
||||
* you may have already discarded the writer for.
|
||||
*
|
||||
* Returns: %TRUE on success; otherwise %FALSE and @error is set.
|
||||
*/
|
||||
gboolean
|
||||
sp_capture_reader_save_as (SpCaptureReader *self,
|
||||
const gchar *filename,
|
||||
GError **error)
|
||||
{
|
||||
struct stat stbuf;
|
||||
off_t in_off;
|
||||
gsize to_write;
|
||||
int fd = -1;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (filename != NULL);
|
||||
|
||||
if (-1 == (fd = open (filename, O_CREAT | O_WRONLY, 0640)))
|
||||
goto handle_errno;
|
||||
|
||||
if (-1 == fstat (self->fd, &stbuf))
|
||||
goto handle_errno;
|
||||
|
||||
if (-1 == ftruncate (fd, stbuf.st_size))
|
||||
goto handle_errno;
|
||||
|
||||
if ((off_t)-1 == lseek (fd, 0L, SEEK_SET))
|
||||
goto handle_errno;
|
||||
|
||||
in_off = 0;
|
||||
to_write = stbuf.st_size;
|
||||
|
||||
while (to_write > 0)
|
||||
{
|
||||
gssize written;
|
||||
|
||||
written = _sp_sendfile (fd, self->fd, &in_off, to_write);
|
||||
|
||||
if (written < 0)
|
||||
goto handle_errno;
|
||||
|
||||
if (written == 0 && errno != EAGAIN)
|
||||
goto handle_errno;
|
||||
|
||||
g_assert (written <= (gssize)to_write);
|
||||
|
||||
to_write -= written;
|
||||
}
|
||||
|
||||
close (fd);
|
||||
|
||||
return TRUE;
|
||||
|
||||
handle_errno:
|
||||
if (fd != -1)
|
||||
close (fd);
|
||||
|
||||
g_set_error (error,
|
||||
G_FILE_ERROR,
|
||||
g_file_error_from_errno (errno),
|
||||
"%s", g_strerror (errno));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gint64
|
||||
sp_capture_reader_get_start_time (SpCaptureReader *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0);
|
||||
|
||||
if (self->endian != G_BYTE_ORDER)
|
||||
return GUINT64_SWAP_LE_BE (self->header.time);
|
||||
|
||||
return self->header.time;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_reader_get_end_time:
|
||||
*
|
||||
* This function will return the end time for the capture, which is the
|
||||
* same as the last event added to the capture.
|
||||
*
|
||||
* If the end time has been stored in the capture header, that will be used.
|
||||
* Otherwise, the time is discovered from the last capture frame and therefore
|
||||
* the caller must have read all frames to ensure this value is accurate.
|
||||
*
|
||||
* Captures created by sysprof versions containing this API will have the
|
||||
* end time set if the output capture file-descriptor supports seeking.
|
||||
*
|
||||
* Since: 3.22.1
|
||||
*/
|
||||
gint64
|
||||
sp_capture_reader_get_end_time (SpCaptureReader *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0);
|
||||
|
||||
if (self->header.end_time != 0)
|
||||
{
|
||||
if (self->endian != G_BYTE_ORDER)
|
||||
return GUINT64_SWAP_LE_BE (self->header.end_time);
|
||||
return self->header.end_time;
|
||||
}
|
||||
|
||||
return self->end_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_capture_reader_copy:
|
||||
*
|
||||
* This function makes a copy of the reader. Since readers use
|
||||
* positioned reads with pread(), this allows you to have multiple
|
||||
* readers with the shared file descriptor. This uses dup() to create
|
||||
* another file descriptor.
|
||||
*
|
||||
* Returns: (transfer full): A copy of @self with a new file-descriptor.
|
||||
*/
|
||||
SpCaptureReader *
|
||||
sp_capture_reader_copy (SpCaptureReader *self)
|
||||
{
|
||||
SpCaptureReader *copy;
|
||||
int fd;
|
||||
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
|
||||
if (-1 == (fd = dup (self->fd)))
|
||||
return NULL;
|
||||
|
||||
copy = g_new0 (SpCaptureReader, 1);
|
||||
|
||||
*copy = *self;
|
||||
|
||||
copy->ref_count = 1;
|
||||
copy->filename = g_strdup (self->filename);
|
||||
copy->fd = fd;
|
||||
|
||||
copy->buf = g_malloc (self->bufsz);
|
||||
memcpy (copy->buf, self->buf, self->bufsz);
|
||||
|
||||
return copy;
|
||||
}
|
||||
72
src/libsysprof-capture/sp-capture-reader.h
Normal file
72
src/libsysprof-capture/sp-capture-reader.h
Normal file
@ -0,0 +1,72 @@
|
||||
/* sp-capture-reader.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sp-capture-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _SpCaptureReader SpCaptureReader;
|
||||
|
||||
SpCaptureReader *sp_capture_reader_new (const gchar *filename,
|
||||
GError **error);
|
||||
SpCaptureReader *sp_capture_reader_new_from_fd (int fd,
|
||||
GError **error);
|
||||
SpCaptureReader *sp_capture_reader_copy (SpCaptureReader *self);
|
||||
SpCaptureReader *sp_capture_reader_ref (SpCaptureReader *self);
|
||||
void sp_capture_reader_unref (SpCaptureReader *self);
|
||||
const gchar *sp_capture_reader_get_filename (SpCaptureReader *self);
|
||||
const gchar *sp_capture_reader_get_time (SpCaptureReader *self);
|
||||
gint64 sp_capture_reader_get_start_time (SpCaptureReader *self);
|
||||
gint64 sp_capture_reader_get_end_time (SpCaptureReader *self);
|
||||
gboolean sp_capture_reader_skip (SpCaptureReader *self);
|
||||
gboolean sp_capture_reader_peek_type (SpCaptureReader *self,
|
||||
SpCaptureFrameType *type);
|
||||
gboolean sp_capture_reader_peek_frame (SpCaptureReader *self,
|
||||
SpCaptureFrame *frame);
|
||||
const SpCaptureMap *sp_capture_reader_read_map (SpCaptureReader *self);
|
||||
const SpCaptureMark *sp_capture_reader_read_mark (SpCaptureReader *self);
|
||||
const SpCaptureExit *sp_capture_reader_read_exit (SpCaptureReader *self);
|
||||
const SpCaptureFork *sp_capture_reader_read_fork (SpCaptureReader *self);
|
||||
const SpCaptureTimestamp *sp_capture_reader_read_timestamp (SpCaptureReader *self);
|
||||
const SpCaptureProcess *sp_capture_reader_read_process (SpCaptureReader *self);
|
||||
const SpCaptureSample *sp_capture_reader_read_sample (SpCaptureReader *self);
|
||||
GHashTable *sp_capture_reader_read_jitmap (SpCaptureReader *self);
|
||||
const SpCaptureFrameCounterDefine *sp_capture_reader_read_counter_define (SpCaptureReader *self);
|
||||
const SpCaptureFrameCounterSet *sp_capture_reader_read_counter_set (SpCaptureReader *self);
|
||||
gboolean sp_capture_reader_reset (SpCaptureReader *self);
|
||||
gboolean sp_capture_reader_splice (SpCaptureReader *self,
|
||||
SpCaptureWriter *dest,
|
||||
GError **error);
|
||||
gboolean sp_capture_reader_save_as (SpCaptureReader *self,
|
||||
const gchar *filename,
|
||||
GError **error);
|
||||
|
||||
#ifdef SP_ENABLE_GOBJECT
|
||||
# define SP_TYPE_CAPTURE_READER (sp_capture_reader_get_type())
|
||||
GType sp_capture_reader_get_type (void);
|
||||
#endif
|
||||
|
||||
#if GLIB_CHECK_VERSION(2, 44, 0)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpCaptureReader, sp_capture_reader_unref)
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
256
src/libsysprof-capture/sp-capture-types.h
Normal file
256
src/libsysprof-capture/sp-capture-types.h
Normal file
@ -0,0 +1,256 @@
|
||||
/* sp-capture-types.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "sp-clock.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_CAPTURE_MAGIC (GUINT32_TO_LE(0xFDCA975E))
|
||||
#define SP_CAPTURE_ALIGN (sizeof(SpCaptureAddress))
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
# define SP_ALIGNED_BEGIN(_N) __declspec(align (_N))
|
||||
# define SP_ALIGNED_END(_N)
|
||||
#else
|
||||
# define SP_ALIGNED_BEGIN(_N)
|
||||
# define SP_ALIGNED_END(_N) __attribute__((aligned ((_N))))
|
||||
#endif
|
||||
|
||||
#if GLIB_SIZEOF_VOID_P == 8
|
||||
# define SP_CAPTURE_JITMAP_MARK G_GUINT64_CONSTANT(0xE000000000000000)
|
||||
# define SP_CAPTURE_ADDRESS_FORMAT "0x%016lx"
|
||||
#elif GLIB_SIZEOF_VOID_P == 4
|
||||
# define SP_CAPTURE_JITMAP_MARK G_GUINT64_CONSTANT(0xE0000000)
|
||||
# define SP_CAPTURE_ADDRESS_FORMAT "0x%016llx"
|
||||
#else
|
||||
#error Unknown GLIB_SIZEOF_VOID_P
|
||||
#endif
|
||||
|
||||
#define SP_CAPTURE_CURRENT_TIME (sp_clock_get_current_time())
|
||||
#define SP_CAPTURE_COUNTER_INT64 0
|
||||
#define SP_CAPTURE_COUNTER_DOUBLE 1
|
||||
|
||||
typedef struct _SpCaptureReader SpCaptureReader;
|
||||
typedef struct _SpCaptureWriter SpCaptureWriter;
|
||||
typedef struct _SpCaptureCursor SpCaptureCursor;
|
||||
typedef struct _SpCaptureCondition SpCaptureCondition;
|
||||
|
||||
typedef guint64 SpCaptureAddress;
|
||||
|
||||
typedef union
|
||||
{
|
||||
gint64 v64;
|
||||
gdouble vdbl;
|
||||
} SpCaptureCounterValue;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SP_CAPTURE_FRAME_TIMESTAMP = 1,
|
||||
SP_CAPTURE_FRAME_SAMPLE = 2,
|
||||
SP_CAPTURE_FRAME_MAP = 3,
|
||||
SP_CAPTURE_FRAME_PROCESS = 4,
|
||||
SP_CAPTURE_FRAME_FORK = 5,
|
||||
SP_CAPTURE_FRAME_EXIT = 6,
|
||||
SP_CAPTURE_FRAME_JITMAP = 7,
|
||||
SP_CAPTURE_FRAME_CTRDEF = 8,
|
||||
SP_CAPTURE_FRAME_CTRSET = 9,
|
||||
SP_CAPTURE_FRAME_MARK = 10,
|
||||
} SpCaptureFrameType;
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
guint32 magic;
|
||||
guint32 version : 8;
|
||||
guint32 little_endian : 1;
|
||||
guint32 padding : 23;
|
||||
gchar capture_time[64];
|
||||
gint64 time;
|
||||
gint64 end_time;
|
||||
gchar suffix[168];
|
||||
} SpCaptureFileHeader
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
guint16 len;
|
||||
gint16 cpu;
|
||||
gint32 pid;
|
||||
gint64 time;
|
||||
guint32 type : 8;
|
||||
guint32 padding1 : 24;
|
||||
guint32 padding2;
|
||||
guint8 data[0];
|
||||
} SpCaptureFrame
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
guint64 start;
|
||||
guint64 end;
|
||||
guint64 offset;
|
||||
guint64 inode;
|
||||
gchar filename[0];
|
||||
} SpCaptureMap
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
guint32 n_jitmaps;
|
||||
guint8 data[0];
|
||||
} SpCaptureJitmap
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
gchar cmdline[0];
|
||||
} SpCaptureProcess
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
guint32 n_addrs : 16;
|
||||
guint32 padding1 : 16;
|
||||
guint32 padding2;
|
||||
SpCaptureAddress addrs[0];
|
||||
} SpCaptureSample
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
gint32 child_pid;
|
||||
} SpCaptureFork
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
} SpCaptureExit
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
} SpCaptureTimestamp
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
gchar category[32];
|
||||
gchar name[32];
|
||||
gchar description[52];
|
||||
guint32 id : 24;
|
||||
guint32 type : 8;
|
||||
SpCaptureCounterValue value;
|
||||
} SpCaptureCounter
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
guint32 n_counters : 16;
|
||||
guint32 padding1 : 16;
|
||||
guint32 padding2;
|
||||
SpCaptureCounter counters[0];
|
||||
} SpCaptureFrameCounterDefine
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
/*
|
||||
* 96 bytes might seem a bit odd, but the counter frame header is 32
|
||||
* bytes. So this makes a nice 2-cacheline aligned size which is
|
||||
* useful when the number of counters is rather small.
|
||||
*/
|
||||
guint32 ids[8];
|
||||
SpCaptureCounterValue values[8];
|
||||
} SpCaptureCounterValues
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
guint32 n_values : 16;
|
||||
guint32 padding1 : 16;
|
||||
guint32 padding2;
|
||||
SpCaptureCounterValues values[0];
|
||||
} SpCaptureFrameCounterSet
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
SP_ALIGNED_BEGIN(1)
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureFrame frame;
|
||||
gint64 duration;
|
||||
gchar group[24];
|
||||
gchar name[40];
|
||||
gchar message[0];
|
||||
} SpCaptureMark
|
||||
SP_ALIGNED_END(1);
|
||||
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureFileHeader) == 256);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureFrame) == 24);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureMap) == 56);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureJitmap) == 28);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureProcess) == 24);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureSample) == 32);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureFork) == 28);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureExit) == 24);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureTimestamp) == 24);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureCounter) == 128);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureCounterValues) == 96);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureFrameCounterDefine) == 32);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureFrameCounterSet) == 32);
|
||||
G_STATIC_ASSERT (sizeof (SpCaptureMark) == 96);
|
||||
|
||||
static inline gint
|
||||
sp_capture_address_compare (SpCaptureAddress a,
|
||||
SpCaptureAddress b)
|
||||
{
|
||||
if (a < b)
|
||||
return -1;
|
||||
if (a > b)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
G_END_DECLS
|
||||
56
src/libsysprof-capture/sp-capture-util-private.h
Normal file
56
src/libsysprof-capture/sp-capture-util-private.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* sp-capture-util-private.h
|
||||
*
|
||||
* Copyright 2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef __linux__
|
||||
# include <sys/sendfile.h>
|
||||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __linux__
|
||||
# define _sp_getpagesize getpagesize
|
||||
# define _sp_pread pread
|
||||
# define _sp_pwrite pwrite
|
||||
# define _sp_write write
|
||||
# define _sp_getpid getpid
|
||||
# define _sp_sendfile sendfile
|
||||
#else
|
||||
size_t _sp_getpagesize (void);
|
||||
ssize_t _sp_pread (int fd,
|
||||
void *buf,
|
||||
size_t count,
|
||||
off_t offset);
|
||||
ssize_t _sp_pwrite (int fd,
|
||||
const void *buf,
|
||||
size_t count,
|
||||
off_t offset);
|
||||
ssize_t _sp_write (int fd,
|
||||
const void *buf,
|
||||
size_t count);
|
||||
gint32 _sp_getpid (void);
|
||||
ssize_t _sp_sendfile (int out_fd,
|
||||
int in_fd,
|
||||
off_t *offset,
|
||||
size_t count);
|
||||
#endif
|
||||
195
src/libsysprof-capture/sp-capture-util.c
Normal file
195
src/libsysprof-capture/sp-capture-util.c
Normal file
@ -0,0 +1,195 @@
|
||||
/* sp-capture-util.c
|
||||
*
|
||||
* Copyright 2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
# include <process.h>
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "sp-capture-util-private.h"
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
static G_LOCK_DEFINE (_sp_io_sync);
|
||||
#endif
|
||||
|
||||
size_t
|
||||
_sp_getpagesize (void)
|
||||
{
|
||||
static size_t pgsz = 0;
|
||||
|
||||
if G_UNLIKELY (pgsz == 0)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
SYSTEM_INFO system_info;
|
||||
GetSystemInfo (&system_info);
|
||||
pgsz = system_info.dwPageSize;
|
||||
#else
|
||||
pgsz = getpagesize ();
|
||||
#endif
|
||||
}
|
||||
|
||||
return pgsz;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
_sp_pread (int fd,
|
||||
void *buf,
|
||||
size_t count,
|
||||
off_t offset)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
ssize_t ret = -1;
|
||||
|
||||
G_LOCK (_sp_io_sync);
|
||||
errno = 0;
|
||||
if (lseek (fd, offset, SEEK_SET) != -1)
|
||||
ret = read (fd, buf, count);
|
||||
G_UNLOCK (_sp_io_sync);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
errno = 0;
|
||||
return pread (fd, buf, count, offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
ssize_t
|
||||
_sp_pwrite (int fd,
|
||||
const void *buf,
|
||||
size_t count,
|
||||
off_t offset)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
ssize_t ret = -1;
|
||||
|
||||
G_LOCK (_sp_io_sync);
|
||||
errno = 0;
|
||||
if (lseek (fd, offset, SEEK_SET) != -1)
|
||||
ret = write (fd, buf, count);
|
||||
G_UNLOCK (_sp_io_sync);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
errno = 0;
|
||||
return pwrite (fd, buf, count, offset);
|
||||
#endif
|
||||
}
|
||||
|
||||
ssize_t
|
||||
_sp_write (int fd,
|
||||
const void *buf,
|
||||
size_t count)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
ssize_t ret = -1;
|
||||
|
||||
G_LOCK (_sp_io_sync);
|
||||
errno = 0;
|
||||
ret = write (fd, buf, count);
|
||||
G_UNLOCK (_sp_io_sync);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
errno = 0;
|
||||
return write (fd, buf, count);
|
||||
#endif
|
||||
}
|
||||
|
||||
gint32
|
||||
_sp_getpid (void)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
return _getpid ();
|
||||
#else
|
||||
return getpid ();
|
||||
#endif
|
||||
}
|
||||
|
||||
ssize_t
|
||||
_sp_sendfile (int out_fd,
|
||||
int in_fd,
|
||||
off_t *offset,
|
||||
size_t count)
|
||||
{
|
||||
ssize_t total = 0;
|
||||
off_t wpos = 0;
|
||||
off_t rpos = 0;
|
||||
|
||||
errno = 0;
|
||||
|
||||
if (offset != NULL && *offset > 0)
|
||||
wpos = rpos = *offset;
|
||||
|
||||
while (count > 0)
|
||||
{
|
||||
unsigned char buf[4096*4];
|
||||
ssize_t n_written = 0;
|
||||
ssize_t n_read;
|
||||
off_t off = 0;
|
||||
size_t to_read;
|
||||
|
||||
/* Try to page align */
|
||||
if ((rpos % 4096) != 0)
|
||||
to_read = 4096 - rpos;
|
||||
else
|
||||
to_read = sizeof buf;
|
||||
|
||||
if (to_read > count)
|
||||
to_read = count;
|
||||
|
||||
errno = 0;
|
||||
n_read = _sp_pread (in_fd, buf, to_read, rpos);
|
||||
|
||||
if (n_read <= 0)
|
||||
return -1;
|
||||
|
||||
g_assert (count >= n_read);
|
||||
|
||||
count -= n_read;
|
||||
rpos += n_read;
|
||||
|
||||
while (wpos < rpos)
|
||||
{
|
||||
g_assert (off < sizeof buf);
|
||||
|
||||
errno = 0;
|
||||
n_written = write (out_fd, &buf[off], rpos - wpos);
|
||||
|
||||
if (n_written <= 0)
|
||||
return -1;
|
||||
|
||||
wpos += n_written;
|
||||
off += n_written;
|
||||
total += n_written;
|
||||
}
|
||||
}
|
||||
|
||||
g_assert (count == 0);
|
||||
|
||||
if (offset != NULL)
|
||||
*offset = rpos;
|
||||
|
||||
errno = 0;
|
||||
return total;
|
||||
}
|
||||
1248
src/libsysprof-capture/sp-capture-writer.c
Normal file
1248
src/libsysprof-capture/sp-capture-writer.c
Normal file
File diff suppressed because it is too large
Load Diff
134
src/libsysprof-capture/sp-capture-writer.h
Normal file
134
src/libsysprof-capture/sp-capture-writer.h
Normal file
@ -0,0 +1,134 @@
|
||||
/* sp-capture-writer.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sp-capture-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _SpCaptureWriter SpCaptureWriter;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/*
|
||||
* The number of frames indexed by SpCaptureFrameType
|
||||
*/
|
||||
gsize frame_count[16];
|
||||
|
||||
/*
|
||||
* Padding for future expansion.
|
||||
*/
|
||||
gsize padding[48];
|
||||
} SpCaptureStat;
|
||||
|
||||
SpCaptureWriter *sp_capture_writer_new_from_env (gsize buffer_size);
|
||||
SpCaptureWriter *sp_capture_writer_new (const gchar *filename,
|
||||
gsize buffer_size);
|
||||
SpCaptureWriter *sp_capture_writer_new_from_fd (int fd,
|
||||
gsize buffer_size);
|
||||
SpCaptureWriter *sp_capture_writer_ref (SpCaptureWriter *self);
|
||||
void sp_capture_writer_unref (SpCaptureWriter *self);
|
||||
void sp_capture_writer_stat (SpCaptureWriter *self,
|
||||
SpCaptureStat *stat);
|
||||
gboolean sp_capture_writer_add_map (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid,
|
||||
guint64 start,
|
||||
guint64 end,
|
||||
guint64 offset,
|
||||
guint64 inode,
|
||||
const gchar *filename);
|
||||
gboolean sp_capture_writer_add_mark (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid,
|
||||
guint64 duration,
|
||||
const gchar *group,
|
||||
const gchar *name,
|
||||
const gchar *message);
|
||||
guint64 sp_capture_writer_add_jitmap (SpCaptureWriter *self,
|
||||
const gchar *name);
|
||||
gboolean sp_capture_writer_add_process (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid,
|
||||
const gchar *cmdline);
|
||||
gboolean sp_capture_writer_add_sample (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid,
|
||||
const SpCaptureAddress *addrs,
|
||||
guint n_addrs);
|
||||
gboolean sp_capture_writer_add_fork (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid,
|
||||
gint32 child_pid);
|
||||
gboolean sp_capture_writer_add_exit (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid);
|
||||
gboolean sp_capture_writer_add_timestamp (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid);
|
||||
gboolean sp_capture_writer_define_counters (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid,
|
||||
const SpCaptureCounter *counters,
|
||||
guint n_counters);
|
||||
gboolean sp_capture_writer_set_counters (SpCaptureWriter *self,
|
||||
gint64 time,
|
||||
gint cpu,
|
||||
gint32 pid,
|
||||
const guint *counters_ids,
|
||||
const SpCaptureCounterValue *values,
|
||||
guint n_counters);
|
||||
gboolean sp_capture_writer_flush (SpCaptureWriter *self);
|
||||
gboolean sp_capture_writer_save_as (SpCaptureWriter *self,
|
||||
const gchar *filename,
|
||||
GError **error);
|
||||
guint sp_capture_writer_request_counter (SpCaptureWriter *self,
|
||||
guint n_counters);
|
||||
SpCaptureReader *sp_capture_writer_create_reader (SpCaptureWriter *self,
|
||||
GError **error);
|
||||
gboolean sp_capture_writer_splice (SpCaptureWriter *self,
|
||||
SpCaptureWriter *dest,
|
||||
GError **error);
|
||||
gboolean _sp_capture_writer_splice_from_fd (SpCaptureWriter *self,
|
||||
int fd,
|
||||
GError **error) G_GNUC_INTERNAL;
|
||||
gboolean _sp_capture_writer_set_time_range (SpCaptureWriter *self,
|
||||
gint64 start_time,
|
||||
gint64 end_time) G_GNUC_INTERNAL;
|
||||
|
||||
#ifdef SP_ENABLE_GOBJECT
|
||||
# define SP_TYPE_CAPTURE_WRITER (sp_capture_writer_get_type())
|
||||
GType sp_capture_writer_get_type (void);
|
||||
#endif
|
||||
|
||||
#if GLIB_CHECK_VERSION(2, 44, 0)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpCaptureWriter, sp_capture_writer_unref)
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
40
src/libsysprof-capture/sysprof-capture.h
Normal file
40
src/libsysprof-capture/sysprof-capture.h
Normal file
@ -0,0 +1,40 @@
|
||||
/* sysprof-capture.h
|
||||
*
|
||||
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SYSPROF_CAPTURE_INSIDE
|
||||
|
||||
# include "sp-address.h"
|
||||
# include "sp-capture-condition.h"
|
||||
# include "sp-capture-cursor.h"
|
||||
# include "sp-capture-reader.h"
|
||||
# include "sp-capture-writer.h"
|
||||
# include "sp-clock.h"
|
||||
# include "sp-error.h"
|
||||
# include "sysprof-version.h"
|
||||
|
||||
#undef SYSPROF_CAPTURE_INSIDE
|
||||
|
||||
G_END_DECLS
|
||||
97
src/libsysprof-capture/sysprof-version.h.in
Normal file
97
src/libsysprof-capture/sysprof-version.h.in
Normal file
@ -0,0 +1,97 @@
|
||||
/* sysprof-version.h.in
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Lesser General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This file 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 Lesser 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(SYSPROF_CAPTURE_INSIDE) && !defined(SYSPROF_CAPTURE_COMPILATION)
|
||||
# error "Only <sysprof-capture.h> can be included directly."
|
||||
#endif
|
||||
|
||||
/**
|
||||
* SECTION:sysprof-version
|
||||
* @short_description: sysprof version checking
|
||||
*
|
||||
* sysprof provides macros to check the version of the library
|
||||
* at compile-time
|
||||
*/
|
||||
|
||||
/**
|
||||
* SYSPROF_MAJOR_VERSION:
|
||||
*
|
||||
* sysprof major version component (e.g. 1 if %SYSPROF_VERSION is 1.2.3)
|
||||
*/
|
||||
#define SYSPROF_MAJOR_VERSION (@MAJOR_VERSION@)
|
||||
|
||||
/**
|
||||
* SYSPROF_MINOR_VERSION:
|
||||
*
|
||||
* sysprof minor version component (e.g. 2 if %SYSPROF_VERSION is 1.2.3)
|
||||
*/
|
||||
#define SYSPROF_MINOR_VERSION (@MINOR_VERSION@)
|
||||
|
||||
/**
|
||||
* SYSPROF_MICRO_VERSION:
|
||||
*
|
||||
* sysprof micro version component (e.g. 3 if %SYSPROF_VERSION is 1.2.3)
|
||||
*/
|
||||
#define SYSPROF_MICRO_VERSION (@MICRO_VERSION@)
|
||||
|
||||
/**
|
||||
* SYSPROF_VERSION
|
||||
*
|
||||
* sysprof version.
|
||||
*/
|
||||
#define SYSPROF_VERSION (@VERSION@)
|
||||
|
||||
/**
|
||||
* SYSPROF_VERSION_S:
|
||||
*
|
||||
* sysprof version, encoded as a string, useful for printing and
|
||||
* concatenation.
|
||||
*/
|
||||
#define SYSPROF_VERSION_S "@VERSION@"
|
||||
|
||||
#define SYSPROF_ENCODE_VERSION(major,minor,micro) \
|
||||
((major) << 24 | (minor) << 16 | (micro) << 8)
|
||||
|
||||
/**
|
||||
* SYSPROF_VERSION_HEX:
|
||||
*
|
||||
* sysprof version, encoded as an hexadecimal number, useful for
|
||||
* integer comparisons.
|
||||
*/
|
||||
#define SYSPROF_VERSION_HEX \
|
||||
(SYSPROF_ENCODE_VERSION (SYSPROF_MAJOR_VERSION, SYSPROF_MINOR_VERSION, SYSPROF_MICRO_VERSION))
|
||||
|
||||
/**
|
||||
* SYSPROF_CHECK_VERSION:
|
||||
* @major: required major version
|
||||
* @minor: required minor version
|
||||
* @micro: required micro version
|
||||
*
|
||||
* Compile-time version checking. Evaluates to %TRUE if the version
|
||||
* of sysprof is greater than the required one.
|
||||
*/
|
||||
#define SYSPROF_CHECK_VERSION(major,minor,micro) \
|
||||
(SYSPROF_MAJOR_VERSION > (major) || \
|
||||
(SYSPROF_MAJOR_VERSION == (major) && SYSPROF_MINOR_VERSION > (minor)) || \
|
||||
(SYSPROF_MAJOR_VERSION == (major) && SYSPROF_MINOR_VERSION == (minor) && \
|
||||
SYSPROF_MICRO_VERSION >= (micro)))
|
||||
|
||||
6
src/libsysprof-ui/css/SpVisualizerView-Adwaita-dark.css
Normal file
6
src/libsysprof-ui/css/SpVisualizerView-Adwaita-dark.css
Normal file
@ -0,0 +1,6 @@
|
||||
visualizers list row {
|
||||
background-color: #201f21;
|
||||
background-size: 8px 8px;
|
||||
background-image: repeating-linear-gradient(0deg, #232224, #232224 1px, transparent 1px, transparent 8px),
|
||||
repeating-linear-gradient(-90deg, #232224, #232224 1px, transparent 1px, transparent 8px);
|
||||
}
|
||||
7
src/libsysprof-ui/css/SpVisualizerView-Adwaita.css
Normal file
7
src/libsysprof-ui/css/SpVisualizerView-Adwaita.css
Normal file
@ -0,0 +1,7 @@
|
||||
visualizers list row {
|
||||
background-color: #f6f7f8;
|
||||
background-size: 8px 8px;
|
||||
background-image: repeating-linear-gradient(0deg, #f0f1f2, #f0f1f2 1px, transparent 1px, transparent 8px),
|
||||
repeating-linear-gradient(-90deg, #f0f1f2, #f0f1f2 1px, transparent 1px, transparent 8px);
|
||||
}
|
||||
|
||||
13
src/libsysprof-ui/css/SpVisualizerView-shared.css
Normal file
13
src/libsysprof-ui/css/SpVisualizerView-shared.css
Normal file
@ -0,0 +1,13 @@
|
||||
visualizers.selection {
|
||||
background: none;
|
||||
background-color: alpha(@theme_selected_bg_color, 0.35);
|
||||
border: 1px solid @theme_selected_bg_color;
|
||||
}
|
||||
visualizers.selection:backdrop {
|
||||
background: none;
|
||||
background-color: alpha(@theme_selected_bg_color, 0.15);
|
||||
border: none;
|
||||
}
|
||||
visualizers list row:not(:first-child) {
|
||||
border-top: 1px solid alpha(@borders, 0.4);
|
||||
}
|
||||
20
src/libsysprof-ui/libsysprof-ui.gresource.xml
Normal file
20
src/libsysprof-ui/libsysprof-ui.gresource.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/gnome/sysprof">
|
||||
<file compressed="true">css/SpVisualizerView-shared.css</file>
|
||||
<file compressed="true">css/SpVisualizerView-Adwaita.css</file>
|
||||
<file compressed="true">css/SpVisualizerView-Adwaita-dark.css</file>
|
||||
|
||||
<!-- Application icons -->
|
||||
<file alias="icons/scalable/apps/org.gnome.Sysprof.svg">../../data/icons/scalable/apps/org.gnome.Sysprof.svg</file>
|
||||
<file alias="icons/symbolic/apps/org.gnome.Sysprof-symbolic.svg">../../data/icons/symbolic/apps/org.gnome.Sysprof-symbolic.svg</file>
|
||||
|
||||
<file compressed="true">ui/sp-callgraph-view.ui</file>
|
||||
<file compressed="true">ui/sp-empty-state-view.ui</file>
|
||||
<file compressed="true">ui/sp-failed-state-view.ui</file>
|
||||
<file compressed="true">ui/sp-process-model-row.ui</file>
|
||||
<file compressed="true">ui/sp-profiler-menu-button.ui</file>
|
||||
<file compressed="true">ui/sp-recording-state-view.ui</file>
|
||||
<file compressed="true">ui/sp-visualizer-view.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
85
src/libsysprof-ui/meson.build
Normal file
85
src/libsysprof-ui/meson.build
Normal file
@ -0,0 +1,85 @@
|
||||
libsysprof_ui_public_sources = [
|
||||
'sp-callgraph-view.c',
|
||||
'sp-color-cycle.c',
|
||||
'sp-cpu-visualizer-row.c',
|
||||
'sp-empty-state-view.c',
|
||||
'sp-failed-state-view.c',
|
||||
'sp-line-visualizer-row.c',
|
||||
'sp-mark-visualizer-row.c',
|
||||
'sp-model-filter.c',
|
||||
'sp-multi-paned.c',
|
||||
'sp-process-model-row.c',
|
||||
'sp-profiler-menu-button.c',
|
||||
'sp-recording-state-view.c',
|
||||
'sp-visualizer-list.c',
|
||||
'sp-visualizer-row.c',
|
||||
'sp-visualizer-ticks.c',
|
||||
'sp-visualizer-view.c',
|
||||
'sp-zoom-manager.c',
|
||||
]
|
||||
|
||||
libsysprof_ui_private_sources = [
|
||||
'pointcache.c',
|
||||
'rectangles.c',
|
||||
'sp-cell-renderer-percent.c',
|
||||
'sp-theme-manager.c',
|
||||
]
|
||||
|
||||
libsysprof_ui_public_headers = [
|
||||
'sp-callgraph-view.h',
|
||||
'sp-cell-renderer-percent.h',
|
||||
'sp-cpu-visualizer-row.h',
|
||||
'sp-empty-state-view.h',
|
||||
'sp-failed-state-view.h',
|
||||
'sp-line-visualizer-row.h',
|
||||
'sp-mark-visualizer-row.h',
|
||||
'sp-model-filter.h',
|
||||
'sp-multi-paned.h',
|
||||
'sp-process-model-row.h',
|
||||
'sp-profiler-menu-button.h',
|
||||
'sp-recording-state-view.h',
|
||||
'sp-visualizer-list.h',
|
||||
'sp-visualizer-row.h',
|
||||
'sp-visualizer-ticks.h',
|
||||
'sp-visualizer-view.h',
|
||||
'sp-zoom-manager.h',
|
||||
'sysprof-ui.h',
|
||||
]
|
||||
|
||||
libsysprof_ui_resources = gnome.compile_resources(
|
||||
'libsysprof-ui-resources',
|
||||
'libsysprof-ui.gresource.xml',
|
||||
c_name: 'lisysprof_ui',
|
||||
)
|
||||
|
||||
libsysprof_ui_deps = [
|
||||
dependency('gio-2.0', version: glib_req_version),
|
||||
dependency('gtk+-3.0', version: gtk_req_version),
|
||||
libsysprof_dep,
|
||||
libshared_dep,
|
||||
]
|
||||
|
||||
libsysprof_ui = shared_library('sysprof-ui-@0@'.format(libsysprof_api_version),
|
||||
libsysprof_ui_public_sources + libsysprof_ui_private_sources + libsysprof_ui_resources,
|
||||
dependencies: libsysprof_ui_deps,
|
||||
install_dir: get_option('libdir'),
|
||||
install: true,
|
||||
)
|
||||
|
||||
libsysprof_ui_dep = declare_dependency(
|
||||
link_with: libsysprof_ui,
|
||||
dependencies: libsysprof_ui_deps,
|
||||
include_directories: include_directories('.'),
|
||||
)
|
||||
|
||||
pkgconfig.generate(
|
||||
subdirs: [ sysprof_header_subdir ],
|
||||
version: meson.project_version(),
|
||||
name: 'sysprof-ui-@0@'.format(libsysprof_api_version),
|
||||
filebase: 'sysprof-ui-@0@'.format(libsysprof_api_version),
|
||||
description: 'The UI library for GTK applications embedding sysprof',
|
||||
install_dir: join_paths(get_option('libdir'), 'pkgconfig'),
|
||||
requires: [ 'gio-2.0', 'gtk+-3.0' ],
|
||||
)
|
||||
|
||||
install_headers(libsysprof_ui_public_headers, subdir: sysprof_header_subdir)
|
||||
105
src/libsysprof-ui/pointcache.c
Normal file
105
src/libsysprof-ui/pointcache.c
Normal file
@ -0,0 +1,105 @@
|
||||
/* pointcache.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "pointcache"
|
||||
|
||||
#include "pointcache.h"
|
||||
|
||||
struct _PointCache
|
||||
{
|
||||
volatile gint ref_count;
|
||||
GHashTable *sets;
|
||||
};
|
||||
|
||||
static void
|
||||
point_cache_destroy (PointCache *self)
|
||||
{
|
||||
g_clear_pointer (&self->sets, g_hash_table_unref);
|
||||
g_slice_free (PointCache, self);
|
||||
}
|
||||
|
||||
PointCache *
|
||||
point_cache_ref (PointCache *self)
|
||||
{
|
||||
g_return_val_if_fail (self->ref_count > 0, NULL);
|
||||
g_atomic_int_inc (&self->ref_count);
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
point_cache_unref (PointCache *self)
|
||||
{
|
||||
g_return_if_fail (self->ref_count > 0);
|
||||
if (g_atomic_int_dec_and_test (&self->ref_count))
|
||||
point_cache_destroy (self);
|
||||
}
|
||||
|
||||
PointCache *
|
||||
point_cache_new (void)
|
||||
{
|
||||
PointCache *self;
|
||||
|
||||
self = g_slice_new0 (PointCache);
|
||||
self->ref_count = 1;
|
||||
self->sets = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_array_unref);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
point_cache_add_set (PointCache *self,
|
||||
guint set_id)
|
||||
{
|
||||
g_hash_table_insert (self->sets,
|
||||
GUINT_TO_POINTER (set_id),
|
||||
g_array_new (FALSE, FALSE, sizeof (Point)));
|
||||
}
|
||||
|
||||
gboolean
|
||||
point_cache_contains_set (PointCache *self,
|
||||
guint set_id)
|
||||
{
|
||||
return g_hash_table_contains (self->sets, GUINT_TO_POINTER (set_id));
|
||||
}
|
||||
|
||||
void
|
||||
point_cache_add_point_to_set (PointCache *self,
|
||||
guint set_id,
|
||||
gdouble x,
|
||||
gdouble y)
|
||||
{
|
||||
GArray *ar;
|
||||
Point point = { x, y };
|
||||
|
||||
ar = g_hash_table_lookup (self->sets, GUINT_TO_POINTER (set_id));
|
||||
g_array_append_val (ar, point);
|
||||
}
|
||||
|
||||
const Point *
|
||||
point_cache_get_points (PointCache *self,
|
||||
guint set_id,
|
||||
guint *n_points)
|
||||
{
|
||||
GArray *ar;
|
||||
|
||||
ar = g_hash_table_lookup (self->sets, GUINT_TO_POINTER (set_id));
|
||||
*n_points = ar->len;
|
||||
return &g_array_index (ar, const Point, 0);
|
||||
}
|
||||
55
src/libsysprof-ui/pointcache.h
Normal file
55
src/libsysprof-ui/pointcache.h
Normal file
@ -0,0 +1,55 @@
|
||||
/* pointcache.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef POINT_CACHE_H
|
||||
#define POINT_CACHE_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _PointCache PointCache;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gdouble x;
|
||||
gdouble y;
|
||||
} Point;
|
||||
|
||||
PointCache *point_cache_new (void);
|
||||
PointCache *point_cache_ref (PointCache *self);
|
||||
void point_cache_unref (PointCache *self);
|
||||
void point_cache_add_set (PointCache *self,
|
||||
guint set_id);
|
||||
gboolean point_cache_contains_set (PointCache *self,
|
||||
guint set_id);
|
||||
void point_cache_add_point_to_set (PointCache *self,
|
||||
guint set_id,
|
||||
gdouble x,
|
||||
gdouble y);
|
||||
const Point *point_cache_get_points (PointCache *self,
|
||||
guint set_id,
|
||||
guint *n_points);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (PointCache, point_cache_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* POINT_CACHE_H */
|
||||
248
src/libsysprof-ui/rectangles.c
Normal file
248
src/libsysprof-ui/rectangles.c
Normal file
@ -0,0 +1,248 @@
|
||||
/* rectangles.c
|
||||
*
|
||||
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "rectangles.h"
|
||||
#include "sp-color-cycle.h"
|
||||
#include "sp-visualizer-row.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const gchar *name;
|
||||
const gchar *message;
|
||||
gint64 begin;
|
||||
gint64 end;
|
||||
GdkRectangle area;
|
||||
} Rectangle;
|
||||
|
||||
struct _Rectangles
|
||||
{
|
||||
GStringChunk *strings;
|
||||
GArray *rectangles;
|
||||
GHashTable *y_indexes;
|
||||
GHashTable *colors;
|
||||
SpColorCycle *cycle;
|
||||
gint64 begin_time;
|
||||
gint64 end_time;
|
||||
guint sorted : 1;
|
||||
};
|
||||
|
||||
Rectangles *
|
||||
rectangles_new (gint64 begin_time,
|
||||
gint64 end_time)
|
||||
{
|
||||
Rectangles *self;
|
||||
|
||||
self = g_slice_new0 (Rectangles);
|
||||
self->strings = g_string_chunk_new (4096);
|
||||
self->rectangles = g_array_new (FALSE, FALSE, sizeof (Rectangle));
|
||||
self->y_indexes = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
self->colors = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
|
||||
self->cycle = sp_color_cycle_new ();
|
||||
self->begin_time = begin_time;
|
||||
self->end_time = end_time;
|
||||
self->sorted = FALSE;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
rectangles_add (Rectangles *self,
|
||||
gint64 begin_time,
|
||||
gint64 end_time,
|
||||
const gchar *name,
|
||||
const gchar *message)
|
||||
{
|
||||
Rectangle rect = {0};
|
||||
|
||||
g_assert (self != NULL);
|
||||
|
||||
if (message != NULL)
|
||||
rect.message = g_string_chunk_insert_const (self->strings, message);
|
||||
|
||||
if (name != NULL)
|
||||
rect.name = g_string_chunk_insert_const (self->strings, name);
|
||||
|
||||
rect.begin = begin_time;
|
||||
rect.end = end_time;
|
||||
|
||||
if (rect.end == rect.begin)
|
||||
rect.area.width = 1;
|
||||
|
||||
g_array_append_val (self->rectangles, rect);
|
||||
|
||||
self->sorted = FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
rectangles_free (Rectangles *self)
|
||||
{
|
||||
g_string_chunk_free (self->strings);
|
||||
g_array_unref (self->rectangles);
|
||||
g_hash_table_unref (self->colors);
|
||||
g_hash_table_unref (self->y_indexes);
|
||||
sp_color_cycle_unref (self->cycle);
|
||||
g_slice_free (Rectangles, self);
|
||||
}
|
||||
|
||||
static gint
|
||||
sort_rectangles (gconstpointer a,
|
||||
gconstpointer b)
|
||||
{
|
||||
const Rectangle *r1 = a;
|
||||
const Rectangle *r2 = b;
|
||||
gint64 r = r1->begin - r2->begin;
|
||||
if (r == 0)
|
||||
r = r1->end - r2->end;
|
||||
if (r > 0) return 1;
|
||||
else if (r < 0) return -1;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
rectangles_sort (Rectangles *self)
|
||||
{
|
||||
guint sequence = 0;
|
||||
|
||||
g_assert (self != NULL);
|
||||
|
||||
if (self->sorted)
|
||||
return;
|
||||
|
||||
g_array_sort (self->rectangles, sort_rectangles);
|
||||
|
||||
g_hash_table_remove_all (self->y_indexes);
|
||||
|
||||
for (guint i = 0; i < self->rectangles->len; i++)
|
||||
{
|
||||
const Rectangle *rect = &g_array_index (self->rectangles, Rectangle, i);
|
||||
|
||||
if (!g_hash_table_contains (self->y_indexes, rect->name))
|
||||
{
|
||||
GdkRGBA rgba;
|
||||
|
||||
sp_color_cycle_next (self->cycle, &rgba);
|
||||
g_hash_table_insert (self->y_indexes, (gchar *)rect->name, GUINT_TO_POINTER (++sequence));
|
||||
g_hash_table_insert (self->colors, (gchar *)rect->name, g_memdup (&rgba, sizeof rgba));
|
||||
}
|
||||
}
|
||||
|
||||
self->sorted = TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
rectangles_draw (Rectangles *self,
|
||||
GtkWidget *row,
|
||||
cairo_t *cr)
|
||||
{
|
||||
GtkAllocation alloc;
|
||||
gdouble range;
|
||||
guint n_rows;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (SP_IS_VISUALIZER_ROW (row));
|
||||
g_assert (cr != NULL);
|
||||
|
||||
if (!self->sorted)
|
||||
rectangles_sort (self);
|
||||
|
||||
gtk_widget_get_allocation (row, &alloc);
|
||||
n_rows = g_hash_table_size (self->y_indexes);
|
||||
if (n_rows == 0 || alloc.height == 0)
|
||||
return;
|
||||
|
||||
range = self->end_time - self->begin_time;
|
||||
|
||||
for (guint i = 0; i < self->rectangles->len; i++)
|
||||
{
|
||||
Rectangle *rect = &g_array_index (self->rectangles, Rectangle, i);
|
||||
guint y_index = GPOINTER_TO_UINT (g_hash_table_lookup (self->y_indexes, rect->name));
|
||||
SpVisualizerRowRelativePoint in_points[2];
|
||||
SpVisualizerRowAbsolutePoint out_points[2];
|
||||
GdkRectangle r;
|
||||
GdkRGBA *rgba;
|
||||
|
||||
g_assert (y_index > 0);
|
||||
g_assert (y_index <= n_rows);
|
||||
|
||||
in_points[0].x = (rect->begin - self->begin_time) / range;
|
||||
in_points[0].y = (y_index - 1) / (gdouble)n_rows;
|
||||
in_points[1].x = (rect->end - self->begin_time) / range;
|
||||
in_points[1].y = 0;
|
||||
|
||||
sp_visualizer_row_translate_points (SP_VISUALIZER_ROW (row),
|
||||
in_points, G_N_ELEMENTS (in_points),
|
||||
out_points, G_N_ELEMENTS (out_points));
|
||||
|
||||
r.height = alloc.height / (gdouble)n_rows;
|
||||
r.x = out_points[0].x;
|
||||
r.y = out_points[0].y - r.height;
|
||||
|
||||
if (rect->end <= rect->begin)
|
||||
r.width = 1;
|
||||
else
|
||||
r.width = MAX (1, out_points[1].x - out_points[0].x);
|
||||
|
||||
rect->area = r;
|
||||
|
||||
rgba = g_hash_table_lookup (self->colors, rect->name);
|
||||
|
||||
gdk_cairo_rectangle (cr, &r);
|
||||
gdk_cairo_set_source_rgba (cr, rgba);
|
||||
cairo_fill (cr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
rectangles_set_end_time (Rectangles *self,
|
||||
gint64 end_time)
|
||||
{
|
||||
/* We might not know the real end time until we've exhausted the stream */
|
||||
self->end_time = end_time;
|
||||
}
|
||||
|
||||
gboolean
|
||||
rectangles_query_tooltip (Rectangles *self,
|
||||
GtkTooltip *tooltip,
|
||||
const gchar *group,
|
||||
gint x,
|
||||
gint y)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (GTK_IS_TOOLTIP (tooltip));
|
||||
|
||||
/* TODO: Sort, binary search, etc. */
|
||||
|
||||
for (guint i = 0; i < self->rectangles->len; i++)
|
||||
{
|
||||
const Rectangle *r = &g_array_index (self->rectangles, Rectangle, i);
|
||||
|
||||
if (r->area.x <= x &&
|
||||
r->area.y <= y &&
|
||||
r->area.x + r->area.width >= x &&
|
||||
r->area.y + r->area.height >= y)
|
||||
{
|
||||
g_autofree gchar *text = g_strdup_printf ("%s:%s: %s", group, r->name, r->message);
|
||||
gtk_tooltip_set_text (tooltip, text);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
48
src/libsysprof-ui/rectangles.h
Normal file
48
src/libsysprof-ui/rectangles.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* rectangles.h
|
||||
*
|
||||
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _Rectangles Rectangles;
|
||||
|
||||
Rectangles *rectangles_new (gint64 begin_time,
|
||||
gint64 end_time);
|
||||
void rectangles_free (Rectangles *self);
|
||||
void rectangles_draw (Rectangles *self,
|
||||
GtkWidget *widget,
|
||||
cairo_t *cr);
|
||||
void rectangles_add (Rectangles *self,
|
||||
gint64 begin_time,
|
||||
gint64 end_time,
|
||||
const gchar *name,
|
||||
const gchar *message);
|
||||
void rectangles_set_end_time (Rectangles *self,
|
||||
gint64 end_time);
|
||||
gboolean rectangles_query_tooltip (Rectangles *self,
|
||||
GtkTooltip *tooltip,
|
||||
const gchar *group,
|
||||
gint x,
|
||||
gint y);
|
||||
|
||||
G_END_DECLS
|
||||
1085
src/libsysprof-ui/sp-callgraph-view.c
Normal file
1085
src/libsysprof-ui/sp-callgraph-view.c
Normal file
File diff suppressed because it is too large
Load Diff
52
src/libsysprof-ui/sp-callgraph-view.h
Normal file
52
src/libsysprof-ui/sp-callgraph-view.h
Normal file
@ -0,0 +1,52 @@
|
||||
/* sp-callgraph-view.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_CALLGRAPH_VIEW_H
|
||||
#define SP_CALLGRAPH_VIEW_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "sp-callgraph-profile.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_CALLGRAPH_VIEW (sp_callgraph_view_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpCallgraphView, sp_callgraph_view, SP, CALLGRAPH_VIEW, GtkBin)
|
||||
|
||||
struct _SpCallgraphViewClass
|
||||
{
|
||||
GtkBinClass parent_class;
|
||||
|
||||
void (*go_previous) (SpCallgraphView *self);
|
||||
|
||||
gpointer padding[8];
|
||||
};
|
||||
|
||||
GtkWidget *sp_callgraph_view_new (void);
|
||||
SpCallgraphProfile *sp_callgraph_view_get_profile (SpCallgraphView *self);
|
||||
void sp_callgraph_view_set_profile (SpCallgraphView *self,
|
||||
SpCallgraphProfile *profile);
|
||||
gchar *sp_callgraph_view_screenshot (SpCallgraphView *self);
|
||||
guint sp_callgraph_view_get_n_functions (SpCallgraphView *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_CALLGRAPH_VIEW_H */
|
||||
137
src/libsysprof-ui/sp-cell-renderer-percent.c
Normal file
137
src/libsysprof-ui/sp-cell-renderer-percent.c
Normal file
@ -0,0 +1,137 @@
|
||||
/* sp-cell-renderer-percent.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include "sp-cell-renderer-percent.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gdouble percent;
|
||||
} SpCellRendererPercentPrivate;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PERCENT,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (SpCellRendererPercent, sp_cell_renderer_percent, GTK_TYPE_CELL_RENDERER_TEXT)
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
static void
|
||||
sp_cell_renderer_percent_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpCellRendererPercent *self = SP_CELL_RENDERER_PERCENT (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PERCENT:
|
||||
g_value_set_double (value, sp_cell_renderer_percent_get_percent (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_cell_renderer_percent_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpCellRendererPercent *self = SP_CELL_RENDERER_PERCENT (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PERCENT:
|
||||
sp_cell_renderer_percent_set_percent (self, g_value_get_double (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_cell_renderer_percent_class_init (SpCellRendererPercentClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->get_property = sp_cell_renderer_percent_get_property;
|
||||
object_class->set_property = sp_cell_renderer_percent_set_property;
|
||||
|
||||
properties [PROP_PERCENT] =
|
||||
g_param_spec_double ("percent",
|
||||
"Percent",
|
||||
"Percent",
|
||||
0.0,
|
||||
100.0,
|
||||
0.0,
|
||||
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_cell_renderer_percent_init (SpCellRendererPercent *self)
|
||||
{
|
||||
g_object_set (self, "xalign", 1.0f, NULL);
|
||||
}
|
||||
|
||||
gdouble
|
||||
sp_cell_renderer_percent_get_percent (SpCellRendererPercent *self)
|
||||
{
|
||||
SpCellRendererPercentPrivate *priv = sp_cell_renderer_percent_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_CELL_RENDERER_PERCENT (self), 0.0);
|
||||
|
||||
return priv->percent;
|
||||
}
|
||||
|
||||
void
|
||||
sp_cell_renderer_percent_set_percent (SpCellRendererPercent *self,
|
||||
gdouble percent)
|
||||
{
|
||||
SpCellRendererPercentPrivate *priv = sp_cell_renderer_percent_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_CELL_RENDERER_PERCENT (self));
|
||||
g_return_if_fail (percent >= 0.0);
|
||||
g_return_if_fail (percent <= 100.0);
|
||||
|
||||
if (percent != priv->percent)
|
||||
{
|
||||
gchar text[128];
|
||||
|
||||
priv->percent = percent;
|
||||
|
||||
g_snprintf (text, sizeof text, "%.2lf<span size='smaller'><span size='smaller'> </span>%%</span>", percent);
|
||||
text [sizeof text - 1] = '\0';
|
||||
|
||||
g_object_set (self, "markup", text, NULL);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PERCENT]);
|
||||
}
|
||||
}
|
||||
59
src/libsysprof-ui/sp-cell-renderer-percent.h
Normal file
59
src/libsysprof-ui/sp-cell-renderer-percent.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* sp-cell-renderer-percent.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_CELL_RENDERER_PERCENT_H
|
||||
#define SP_CELL_RENDERER_PERCENT_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_CELL_RENDERER_PERCENT (sp_cell_renderer_percent_get_type())
|
||||
#define SP_CELL_RENDERER_PERCENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercent))
|
||||
#define SP_CELL_RENDERER_PERCENT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercent const))
|
||||
#define SP_CELL_RENDERER_PERCENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercentClass))
|
||||
#define SP_IS_CELL_RENDERER_PERCENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_CELL_RENDERER_PERCENT))
|
||||
#define SP_IS_CELL_RENDERER_PERCENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SP_TYPE_CELL_RENDERER_PERCENT))
|
||||
#define SP_CELL_RENDERER_PERCENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SP_TYPE_CELL_RENDERER_PERCENT, SpCellRendererPercentClass))
|
||||
|
||||
typedef struct _SpCellRendererPercent SpCellRendererPercent;
|
||||
typedef struct _SpCellRendererPercentClass SpCellRendererPercentClass;
|
||||
|
||||
struct _SpCellRendererPercent
|
||||
{
|
||||
GtkCellRendererText parent;
|
||||
};
|
||||
|
||||
struct _SpCellRendererPercentClass
|
||||
{
|
||||
GtkCellRendererTextClass parent_class;
|
||||
|
||||
gpointer padding[4];
|
||||
};
|
||||
|
||||
GType sp_cell_renderer_percent_get_type (void);
|
||||
GtkCellRenderer *sp_cell_renderer_percent_new (void);
|
||||
gdouble sp_cell_renderer_percent_get_percent (SpCellRendererPercent *self);
|
||||
void sp_cell_renderer_percent_set_percent (SpCellRendererPercent *self,
|
||||
gdouble percent);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_CELL_RENDERER_PERCENT_H */
|
||||
138
src/libsysprof-ui/sp-color-cycle.c
Normal file
138
src/libsysprof-ui/sp-color-cycle.c
Normal file
@ -0,0 +1,138 @@
|
||||
/* sp-color-cycle.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-color-cycle"
|
||||
|
||||
#include "sp-color-cycle.h"
|
||||
|
||||
G_DEFINE_BOXED_TYPE (SpColorCycle, sp_color_cycle, sp_color_cycle_ref, sp_color_cycle_unref)
|
||||
|
||||
static const gchar *default_colors[] = {
|
||||
"#73d216",
|
||||
"#f57900",
|
||||
"#3465a4",
|
||||
"#ef2929",
|
||||
"#75507b",
|
||||
"#ce5c00",
|
||||
"#c17d11",
|
||||
"#cc0000",
|
||||
"#edd400",
|
||||
"#555753",
|
||||
"#4e9a06",
|
||||
"#204a87",
|
||||
"#5c3566",
|
||||
"#a40000",
|
||||
"#c4a000",
|
||||
"#8f5902",
|
||||
"#2e3436",
|
||||
"#8ae234",
|
||||
"#729fcf",
|
||||
"#ad7fa8",
|
||||
"#fce94f",
|
||||
"#fcaf3e",
|
||||
"#e9b96e",
|
||||
"#888a85",
|
||||
NULL
|
||||
};
|
||||
|
||||
struct _SpColorCycle
|
||||
{
|
||||
volatile gint ref_count;
|
||||
GdkRGBA *colors;
|
||||
gsize n_colors;
|
||||
guint position;
|
||||
};
|
||||
|
||||
static void
|
||||
sp_color_cycle_destroy (SpColorCycle *self)
|
||||
{
|
||||
g_free (self->colors);
|
||||
g_slice_free (SpColorCycle, self);
|
||||
}
|
||||
|
||||
SpColorCycle *
|
||||
sp_color_cycle_new (void)
|
||||
{
|
||||
SpColorCycle *self;
|
||||
|
||||
self = g_slice_new0 (SpColorCycle);
|
||||
self->ref_count = 1;
|
||||
self->n_colors = g_strv_length ((gchar **)default_colors);
|
||||
self->colors = g_new0 (GdkRGBA, self->n_colors);
|
||||
|
||||
for (guint i = 0; default_colors[i]; i++)
|
||||
{
|
||||
if G_UNLIKELY (!gdk_rgba_parse (&self->colors[i], default_colors[i]))
|
||||
g_warning ("Failed to parse color %s into an RGBA", default_colors[i]);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
SpColorCycle *
|
||||
sp_color_cycle_ref (SpColorCycle *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
g_return_val_if_fail (self->ref_count > 0, NULL);
|
||||
g_atomic_int_inc (&self->ref_count);
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
sp_color_cycle_unref (SpColorCycle *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->ref_count > 0);
|
||||
if (g_atomic_int_dec_and_test (&self->ref_count))
|
||||
sp_color_cycle_destroy (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_color_cycle_next (SpColorCycle *self,
|
||||
GdkRGBA *rgba)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (self->position < self->n_colors);
|
||||
|
||||
*rgba = self->colors[self->position];
|
||||
|
||||
/*
|
||||
* TODO: Adjust color HSV/etc
|
||||
*
|
||||
* We could simply adjust the brightness/etc after we dispatch
|
||||
* a color so that we get darker as we go.
|
||||
*/
|
||||
|
||||
self->position = (self->position + 1) % self->n_colors;
|
||||
}
|
||||
|
||||
void
|
||||
sp_color_cycle_reset (SpColorCycle *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
for (guint i = 0; default_colors[i]; i++)
|
||||
{
|
||||
if G_UNLIKELY (!gdk_rgba_parse (&self->colors[i], default_colors[i]))
|
||||
g_warning ("Failed to parse color %s into an RGBA", default_colors[i]);
|
||||
}
|
||||
|
||||
self->position = 0;
|
||||
}
|
||||
42
src/libsysprof-ui/sp-color-cycle.h
Normal file
42
src/libsysprof-ui/sp-color-cycle.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* sp-color-cycle.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_COLOR_CYCLE_H
|
||||
#define SP_COLOR_CYCLE_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_COLOR_CYCLE (sp_color_cycle_get_type())
|
||||
|
||||
typedef struct _SpColorCycle SpColorCycle;
|
||||
|
||||
GType sp_color_cycle_get_type (void);
|
||||
SpColorCycle *sp_color_cycle_ref (SpColorCycle *self);
|
||||
void sp_color_cycle_unref (SpColorCycle *self);
|
||||
SpColorCycle *sp_color_cycle_new (void);
|
||||
void sp_color_cycle_reset (SpColorCycle *self);
|
||||
void sp_color_cycle_next (SpColorCycle *self,
|
||||
GdkRGBA *rgba);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_COLOR_CYCLE_H */
|
||||
171
src/libsysprof-ui/sp-cpu-visualizer-row.c
Normal file
171
src/libsysprof-ui/sp-cpu-visualizer-row.c
Normal file
@ -0,0 +1,171 @@
|
||||
/* sp-cpu-visualizer-row.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-cpu-visualizer-row"
|
||||
|
||||
#include "sp-capture-condition.h"
|
||||
#include "sp-capture-cursor.h"
|
||||
#include "sp-color-cycle.h"
|
||||
#include "sp-cpu-visualizer-row.h"
|
||||
|
||||
struct _SpCpuVisualizerRow
|
||||
{
|
||||
SpLineVisualizerRow parent_instance;
|
||||
SpColorCycle *colors;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (SpCpuVisualizerRow, sp_cpu_visualizer_row, SP_TYPE_LINE_VISUALIZER_ROW)
|
||||
|
||||
static gboolean
|
||||
sp_cpu_visualizer_counter_found (const SpCaptureFrame *frame,
|
||||
gpointer user_data)
|
||||
{
|
||||
const SpCaptureFrameCounterDefine *def = (SpCaptureFrameCounterDefine *)frame;
|
||||
GArray *counters = user_data;
|
||||
gboolean found = FALSE;
|
||||
|
||||
g_assert (frame->type == SP_CAPTURE_FRAME_CTRDEF);
|
||||
|
||||
/*
|
||||
* In practice, all the CPU counters are defined at once, so we can avoid
|
||||
* walking the rest of the capture by returning after we find our CTRDEF.
|
||||
*/
|
||||
|
||||
for (guint i = 0; i < def->n_counters; i++)
|
||||
{
|
||||
if (g_str_equal (def->counters[i].category, "CPU Percent"))
|
||||
{
|
||||
guint id = def->counters[i].id;
|
||||
g_array_append_val (counters, id);
|
||||
found = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return !found;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_cpu_visualizer_row_discover_counters (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *canellable)
|
||||
{
|
||||
const SpCaptureFrameType types[] = { SP_CAPTURE_FRAME_CTRDEF };
|
||||
SpCaptureReader *reader = task_data;
|
||||
g_autoptr(SpCaptureCursor) cursor = NULL;
|
||||
g_autoptr(GArray) counters = NULL;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
g_assert (SP_IS_CPU_VISUALIZER_ROW (source_object));
|
||||
g_assert (reader != NULL);
|
||||
|
||||
counters = g_array_new (FALSE, FALSE, sizeof (guint));
|
||||
cursor = sp_capture_cursor_new (reader);
|
||||
sp_capture_cursor_add_condition (cursor, sp_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types));
|
||||
sp_capture_cursor_foreach (cursor, sp_cpu_visualizer_counter_found, counters);
|
||||
g_task_return_pointer (task, g_steal_pointer (&counters), (GDestroyNotify)g_array_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
complete_counters (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpCpuVisualizerRow *self = (SpCpuVisualizerRow *)object;
|
||||
g_autoptr(GArray) counters = NULL;
|
||||
|
||||
g_assert (SP_IS_CPU_VISUALIZER_ROW (self));
|
||||
g_assert (G_IS_TASK (result));
|
||||
|
||||
counters = g_task_propagate_pointer (G_TASK (result), NULL);
|
||||
|
||||
if (counters != NULL)
|
||||
{
|
||||
for (guint i = 0; i < counters->len; i++)
|
||||
{
|
||||
guint counter_id = g_array_index (counters, guint, i);
|
||||
GdkRGBA color;
|
||||
|
||||
sp_color_cycle_next (self->colors, &color);
|
||||
sp_line_visualizer_row_add_counter (SP_LINE_VISUALIZER_ROW (self), counter_id, &color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide ourself if we failed to locate counters */
|
||||
gtk_widget_set_visible (GTK_WIDGET (self), counters != NULL && counters->len > 0);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_cpu_visualizer_row_set_reader (SpVisualizerRow *row,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpCpuVisualizerRow *self = (SpCpuVisualizerRow *)row;
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
g_assert (SP_IS_CPU_VISUALIZER_ROW (row));
|
||||
|
||||
sp_color_cycle_reset (self->colors);
|
||||
|
||||
sp_line_visualizer_row_clear (SP_LINE_VISUALIZER_ROW (row));
|
||||
|
||||
SP_VISUALIZER_ROW_CLASS (sp_cpu_visualizer_row_parent_class)->set_reader (row, reader);
|
||||
|
||||
if (reader != NULL)
|
||||
{
|
||||
task = g_task_new (self, NULL, complete_counters, NULL);
|
||||
g_task_set_source_tag (task, sp_cpu_visualizer_row_set_reader);
|
||||
g_task_set_task_data (task, sp_capture_reader_copy (reader),
|
||||
(GDestroyNotify)sp_capture_reader_unref);
|
||||
g_task_run_in_thread (task, sp_cpu_visualizer_row_discover_counters);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_cpu_visualizer_row_finalize (GObject *object)
|
||||
{
|
||||
SpCpuVisualizerRow *self = (SpCpuVisualizerRow *)object;
|
||||
|
||||
g_clear_pointer (&self->colors, sp_color_cycle_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_cpu_visualizer_row_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_cpu_visualizer_row_class_init (SpCpuVisualizerRowClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
SpVisualizerRowClass *row_class = SP_VISUALIZER_ROW_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_cpu_visualizer_row_finalize;
|
||||
|
||||
row_class->set_reader = sp_cpu_visualizer_row_set_reader;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_cpu_visualizer_row_init (SpCpuVisualizerRow *self)
|
||||
{
|
||||
self->colors = sp_color_cycle_new ();
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
sp_cpu_visualizer_row_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_CPU_VISUALIZER_ROW, NULL);
|
||||
}
|
||||
36
src/libsysprof-ui/sp-cpu-visualizer-row.h
Normal file
36
src/libsysprof-ui/sp-cpu-visualizer-row.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-cpu-visualizer-row.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_CPU_VISUALIZER_ROW_H
|
||||
#define SP_CPU_VISUALIZER_ROW_H
|
||||
|
||||
#include "sp-line-visualizer-row.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_CPU_VISUALIZER_ROW (sp_cpu_visualizer_row_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpCpuVisualizerRow, sp_cpu_visualizer_row, SP, CPU_VISUALIZER_ROW, SpLineVisualizerRow)
|
||||
|
||||
GtkWidget *sp_cpu_visualizer_row_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_CPU_VISUALIZER_ROW_H */
|
||||
207
src/libsysprof-ui/sp-empty-state-view.c
Normal file
207
src/libsysprof-ui/sp-empty-state-view.c
Normal file
@ -0,0 +1,207 @@
|
||||
/* sp-empty-state-view.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-empty-state-view"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-empty-state-view.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkLabel *title;
|
||||
GtkLabel *subtitle;
|
||||
} SpEmptyStateViewPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (SpEmptyStateView, sp_empty_state_view, GTK_TYPE_BIN)
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_TITLE,
|
||||
PROP_SUBTITLE,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
GtkWidget *
|
||||
sp_empty_state_view_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_EMPTY_STATE_VIEW, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_empty_state_view_action (GtkWidget *widget,
|
||||
const gchar *prefix,
|
||||
const gchar *action_name,
|
||||
GVariant *parameter)
|
||||
{
|
||||
GtkWidget *toplevel;
|
||||
GApplication *app;
|
||||
GActionGroup *group = NULL;
|
||||
|
||||
g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
|
||||
g_return_val_if_fail (prefix, FALSE);
|
||||
g_return_val_if_fail (action_name, FALSE);
|
||||
|
||||
app = g_application_get_default ();
|
||||
toplevel = gtk_widget_get_toplevel (widget);
|
||||
|
||||
while ((group == NULL) && (widget != NULL))
|
||||
{
|
||||
group = gtk_widget_get_action_group (widget, prefix);
|
||||
widget = gtk_widget_get_parent (widget);
|
||||
}
|
||||
|
||||
if (!group && g_str_equal (prefix, "win") && G_IS_ACTION_GROUP (toplevel))
|
||||
group = G_ACTION_GROUP (toplevel);
|
||||
|
||||
if (!group && g_str_equal (prefix, "app") && G_IS_ACTION_GROUP (app))
|
||||
group = G_ACTION_GROUP (app);
|
||||
|
||||
if (group && g_action_group_has_action (group, action_name))
|
||||
{
|
||||
g_action_group_activate_action (group, action_name, parameter);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (parameter && g_variant_is_floating (parameter))
|
||||
{
|
||||
parameter = g_variant_ref_sink (parameter);
|
||||
g_variant_unref (parameter);
|
||||
}
|
||||
|
||||
g_warning ("Failed to locate action %s.%s", prefix, action_name);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_empty_state_view_activate_link (SpEmptyStateView *self,
|
||||
const gchar *uri,
|
||||
GtkLabel *label)
|
||||
{
|
||||
g_assert (SP_IS_EMPTY_STATE_VIEW (self));
|
||||
g_assert (uri != NULL);
|
||||
g_assert (GTK_IS_LABEL (label));
|
||||
|
||||
if (g_str_has_prefix (uri, "action://"))
|
||||
{
|
||||
g_autofree gchar *full_name = NULL;
|
||||
g_autofree gchar *action_name = NULL;
|
||||
g_autofree gchar *group_name = NULL;
|
||||
g_autoptr(GVariant) param = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
uri += strlen ("action://");
|
||||
|
||||
if (g_action_parse_detailed_name (uri, &full_name, ¶m, &error))
|
||||
{
|
||||
const gchar *dot = strchr (full_name, '.');
|
||||
|
||||
if (param != NULL && g_variant_is_floating (param))
|
||||
param = g_variant_ref_sink (param);
|
||||
|
||||
if (dot == NULL)
|
||||
return FALSE;
|
||||
|
||||
group_name = g_strndup (full_name, dot - full_name);
|
||||
action_name = g_strdup (++dot);
|
||||
|
||||
sp_empty_state_view_action (GTK_WIDGET (self),
|
||||
group_name,
|
||||
action_name,
|
||||
param);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
g_warning ("%s", error->message);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_empty_state_view_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpEmptyStateView *self = SP_EMPTY_STATE_VIEW (object);
|
||||
SpEmptyStateViewPrivate *priv = sp_empty_state_view_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_TITLE:
|
||||
gtk_label_set_label (priv->title, g_value_get_string (value));
|
||||
break;
|
||||
|
||||
case PROP_SUBTITLE:
|
||||
gtk_label_set_label (priv->subtitle, g_value_get_string (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_empty_state_view_class_init (SpEmptyStateViewClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->set_property = sp_empty_state_view_set_property;
|
||||
|
||||
properties [PROP_TITLE] =
|
||||
g_param_spec_string ("title",
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
(G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_SUBTITLE] =
|
||||
g_param_spec_string ("subtitle",
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
(G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sp-empty-state-view.ui");
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpEmptyStateView, subtitle);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpEmptyStateView, title);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_empty_state_view_init (SpEmptyStateView *self)
|
||||
{
|
||||
SpEmptyStateViewPrivate *priv = sp_empty_state_view_get_instance_private (self);
|
||||
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
|
||||
g_signal_connect_object (priv->subtitle,
|
||||
"activate-link",
|
||||
G_CALLBACK (sp_empty_state_view_activate_link),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
}
|
||||
44
src/libsysprof-ui/sp-empty-state-view.h
Normal file
44
src/libsysprof-ui/sp-empty-state-view.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* sp-empty-state-view.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_EMPTY_STATE_VIEW_H
|
||||
#define SP_EMPTY_STATE_VIEW_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_EMPTY_STATE_VIEW (sp_empty_state_view_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpEmptyStateView, sp_empty_state_view, SP, EMPTY_STATE_VIEW, GtkBin)
|
||||
|
||||
struct _SpEmptyStateViewClass
|
||||
{
|
||||
GtkBinClass parent;
|
||||
|
||||
gpointer padding[4];
|
||||
};
|
||||
|
||||
GtkWidget *sp_empty_state_view_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_EMPTY_STATE_VIEW_H */
|
||||
|
||||
44
src/libsysprof-ui/sp-failed-state-view.c
Normal file
44
src/libsysprof-ui/sp-failed-state-view.c
Normal file
@ -0,0 +1,44 @@
|
||||
/* sp-failed-state-view.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "sp-failed-state-view.h"
|
||||
|
||||
G_DEFINE_TYPE (SpFailedStateView, sp_failed_state_view, GTK_TYPE_BIN)
|
||||
|
||||
GtkWidget *
|
||||
sp_failed_state_view_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_FAILED_STATE_VIEW, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_failed_state_view_class_init (SpFailedStateViewClass *klass)
|
||||
{
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class,
|
||||
"/org/gnome/sysprof/ui/sp-failed-state-view.ui");
|
||||
}
|
||||
|
||||
static void
|
||||
sp_failed_state_view_init (SpFailedStateView *self)
|
||||
{
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
}
|
||||
47
src/libsysprof-ui/sp-failed-state-view.h
Normal file
47
src/libsysprof-ui/sp-failed-state-view.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* sp-failed-state-view.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_FAILED_STATE_VIEW_H
|
||||
#define SP_FAILED_STATE_VIEW_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "sp-profiler.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_FAILED_STATE_VIEW (sp_failed_state_view_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpFailedStateView, sp_failed_state_view, SP, FAILED_STATE_VIEW, GtkBin)
|
||||
|
||||
struct _SpFailedStateViewClass
|
||||
{
|
||||
GtkBinClass parent;
|
||||
|
||||
gpointer padding[4];
|
||||
};
|
||||
|
||||
GtkWidget *sp_failed_state_view_new (void);
|
||||
void sp_failed_state_view_set_profiler (SpFailedStateView *self,
|
||||
SpProfiler *profiler);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_FAILED_STATE_VIEW_H */
|
||||
819
src/libsysprof-ui/sp-line-visualizer-row.c
Normal file
819
src/libsysprof-ui/sp-line-visualizer-row.c
Normal file
@ -0,0 +1,819 @@
|
||||
/* sp-line-visualizer-row.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-line-visualizer-row"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pointcache.h"
|
||||
#include "sp-capture-condition.h"
|
||||
#include "sp-capture-cursor.h"
|
||||
#include "sp-line-visualizer-row.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/*
|
||||
* Our reader as assigned by the visualizer system.
|
||||
*/
|
||||
SpCaptureReader *reader;
|
||||
|
||||
/*
|
||||
* An array of LineInfo which contains information about the counters
|
||||
* we need to render.
|
||||
*/
|
||||
GArray *lines;
|
||||
|
||||
/*
|
||||
* This is our set of cached points to render. Once it is assigned here,
|
||||
* it is immutable (and therefore may be shared with worker processes
|
||||
* that are rendering the points).
|
||||
*/
|
||||
PointCache *cache;
|
||||
|
||||
/*
|
||||
* Child widget to display the label in the upper corner.
|
||||
*/
|
||||
GtkLabel *label;
|
||||
|
||||
/*
|
||||
* Range of the scale for lower and upper.
|
||||
*/
|
||||
gdouble y_lower;
|
||||
gdouble y_upper;
|
||||
|
||||
/*
|
||||
* If we have a new counter discovered or the reader is set, we might
|
||||
* want to delay loading until we return to the main loop. This can
|
||||
* help us avoid doing duplicate work.
|
||||
*/
|
||||
guint queued_load;
|
||||
|
||||
guint y_lower_set : 1;
|
||||
guint y_upper_set : 1;
|
||||
} SpLineVisualizerRowPrivate;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint id;
|
||||
gdouble line_width;
|
||||
GdkRGBA foreground;
|
||||
GdkRGBA background;
|
||||
guint use_default_style : 1;
|
||||
guint fill : 1;
|
||||
} LineInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureCursor *cursor;
|
||||
GArray *lines;
|
||||
PointCache *cache;
|
||||
gint64 begin_time;
|
||||
gint64 end_time;
|
||||
gdouble y_lower;
|
||||
gdouble y_upper;
|
||||
guint y_lower_set : 1;
|
||||
guint y_upper_set : 1;
|
||||
} LoadData;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (SpLineVisualizerRow, sp_line_visualizer_row, SP_TYPE_VISUALIZER_ROW)
|
||||
|
||||
static void sp_line_visualizer_row_load_data_async (SpLineVisualizerRow *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
static PointCache *sp_line_visualizer_row_load_data_finish (SpLineVisualizerRow *self,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_TITLE,
|
||||
PROP_Y_LOWER,
|
||||
PROP_Y_UPPER,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
static void
|
||||
load_data_free (gpointer data)
|
||||
{
|
||||
LoadData *load = data;
|
||||
|
||||
if (load != NULL)
|
||||
{
|
||||
g_clear_pointer (&load->lines, g_array_unref);
|
||||
g_clear_pointer (&load->cursor, sp_capture_cursor_unref);
|
||||
g_clear_pointer (&load->cache, point_cache_unref);
|
||||
g_slice_free (LoadData, load);
|
||||
}
|
||||
}
|
||||
|
||||
static GArray *
|
||||
copy_array (GArray *ar)
|
||||
{
|
||||
GArray *ret;
|
||||
|
||||
ret = g_array_sized_new (FALSE, FALSE, g_array_get_element_size (ar), ar->len);
|
||||
g_array_set_size (ret, ar->len);
|
||||
memcpy (ret->data, ar->data, ar->len * g_array_get_element_size (ret));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_line_visualizer_row_draw (GtkWidget *widget,
|
||||
cairo_t *cr)
|
||||
{
|
||||
SpLineVisualizerRow *self = (SpLineVisualizerRow *)widget;
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
GtkStyleContext *style_context;
|
||||
GtkStateFlags flags;
|
||||
GtkAllocation alloc;
|
||||
GdkRGBA foreground;
|
||||
gboolean ret;
|
||||
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (widget));
|
||||
g_assert (cr != NULL);
|
||||
|
||||
gtk_widget_get_allocation (widget, &alloc);
|
||||
|
||||
ret = GTK_WIDGET_CLASS (sp_line_visualizer_row_parent_class)->draw (widget, cr);
|
||||
|
||||
if (priv->cache == NULL)
|
||||
return ret;
|
||||
|
||||
style_context = gtk_widget_get_style_context (widget);
|
||||
flags = gtk_widget_get_state_flags (widget);
|
||||
gtk_style_context_get_color (style_context, flags, &foreground);
|
||||
|
||||
for (guint line = 0; line < priv->lines->len; line++)
|
||||
{
|
||||
g_autofree SpVisualizerRowAbsolutePoint *points = NULL;
|
||||
const LineInfo *line_info = &g_array_index (priv->lines, LineInfo, line);
|
||||
const Point *fpoints;
|
||||
guint n_fpoints = 0;
|
||||
GdkRGBA color;
|
||||
|
||||
fpoints = point_cache_get_points (priv->cache, line_info->id, &n_fpoints);
|
||||
|
||||
if (n_fpoints > 0)
|
||||
{
|
||||
gdouble last_x;
|
||||
gdouble last_y;
|
||||
|
||||
points = g_new0 (SpVisualizerRowAbsolutePoint, n_fpoints);
|
||||
|
||||
sp_visualizer_row_translate_points (SP_VISUALIZER_ROW (self),
|
||||
(const SpVisualizerRowRelativePoint *)fpoints,
|
||||
n_fpoints,
|
||||
points,
|
||||
n_fpoints);
|
||||
|
||||
last_x = points[0].x;
|
||||
last_y = points[0].y;
|
||||
|
||||
if (line_info->fill)
|
||||
{
|
||||
cairo_move_to (cr, last_x, alloc.height);
|
||||
cairo_line_to (cr, last_x, last_y);
|
||||
}
|
||||
else
|
||||
{
|
||||
cairo_move_to (cr, last_x, last_y);
|
||||
}
|
||||
|
||||
for (guint i = 1; i < n_fpoints; i++)
|
||||
{
|
||||
cairo_curve_to (cr,
|
||||
last_x + ((points[i].x - last_x) / 2),
|
||||
last_y,
|
||||
last_x + ((points[i].x - last_x) / 2),
|
||||
points[i].y,
|
||||
points[i].x,
|
||||
points[i].y);
|
||||
last_x = points[i].x;
|
||||
last_y = points[i].y;
|
||||
}
|
||||
|
||||
if (line_info->fill)
|
||||
{
|
||||
cairo_line_to (cr, last_x, alloc.height);
|
||||
cairo_close_path (cr);
|
||||
}
|
||||
|
||||
cairo_set_line_width (cr, line_info->line_width);
|
||||
|
||||
if (line_info->fill)
|
||||
{
|
||||
gdk_cairo_set_source_rgba (cr, &line_info->background);
|
||||
cairo_fill_preserve (cr);
|
||||
}
|
||||
|
||||
if (line_info->use_default_style)
|
||||
color = foreground;
|
||||
else
|
||||
color = line_info->foreground;
|
||||
|
||||
gdk_cairo_set_source_rgba (cr, &color);
|
||||
cairo_stroke (cr);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_load_data_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpLineVisualizerRow *self = (SpLineVisualizerRow *)object;
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(PointCache) cache = NULL;
|
||||
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
|
||||
cache = sp_line_visualizer_row_load_data_finish (self, result, &error);
|
||||
|
||||
if (cache == NULL)
|
||||
{
|
||||
g_warning ("%s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
g_clear_pointer (&priv->cache, point_cache_unref);
|
||||
priv->cache = g_steal_pointer (&cache);
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_line_visualizer_row_do_reload (gpointer data)
|
||||
{
|
||||
SpLineVisualizerRow *self = data;
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
|
||||
priv->queued_load = 0;
|
||||
|
||||
if (priv->reader != NULL)
|
||||
{
|
||||
sp_line_visualizer_row_load_data_async (self,
|
||||
NULL,
|
||||
sp_line_visualizer_row_load_data_cb,
|
||||
NULL);
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_queue_reload (SpLineVisualizerRow *self)
|
||||
{
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
|
||||
if (priv->queued_load == 0)
|
||||
{
|
||||
priv->queued_load = gdk_threads_add_idle_full (G_PRIORITY_LOW,
|
||||
sp_line_visualizer_row_do_reload,
|
||||
self,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_set_reader (SpVisualizerRow *row,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpLineVisualizerRow *self = (SpLineVisualizerRow *)row;
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
|
||||
if (priv->reader != reader)
|
||||
{
|
||||
if (priv->reader != NULL)
|
||||
{
|
||||
sp_capture_reader_unref (priv->reader);
|
||||
priv->reader = NULL;
|
||||
}
|
||||
|
||||
if (reader != NULL)
|
||||
priv->reader = sp_capture_reader_ref (reader);
|
||||
|
||||
sp_line_visualizer_row_queue_reload (self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_finalize (GObject *object)
|
||||
{
|
||||
SpLineVisualizerRow *self = (SpLineVisualizerRow *)object;
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->lines, g_array_unref);
|
||||
g_clear_pointer (&priv->cache, point_cache_unref);
|
||||
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
|
||||
|
||||
if (priv->queued_load != 0)
|
||||
{
|
||||
g_source_remove (priv->queued_load);
|
||||
priv->queued_load = 0;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (sp_line_visualizer_row_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpLineVisualizerRow *self = SP_LINE_VISUALIZER_ROW (object);
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_TITLE:
|
||||
g_object_get_property (G_OBJECT (priv->label), "label", value);
|
||||
break;
|
||||
|
||||
case PROP_Y_LOWER:
|
||||
g_value_set_double (value, priv->y_lower);
|
||||
break;
|
||||
|
||||
case PROP_Y_UPPER:
|
||||
g_value_set_double (value, priv->y_upper);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpLineVisualizerRow *self = SP_LINE_VISUALIZER_ROW (object);
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_TITLE:
|
||||
g_object_set_property (G_OBJECT (priv->label), "label", value);
|
||||
break;
|
||||
|
||||
case PROP_Y_LOWER:
|
||||
priv->y_lower = g_value_get_double (value);
|
||||
priv->y_lower_set = TRUE;
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
break;
|
||||
|
||||
case PROP_Y_UPPER:
|
||||
priv->y_upper = g_value_get_double (value);
|
||||
priv->y_upper_set = TRUE;
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_class_init (SpLineVisualizerRowClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
SpVisualizerRowClass *visualizer_class = SP_VISUALIZER_ROW_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_line_visualizer_row_finalize;
|
||||
object_class->get_property = sp_line_visualizer_row_get_property;
|
||||
object_class->set_property = sp_line_visualizer_row_set_property;
|
||||
|
||||
widget_class->draw = sp_line_visualizer_row_draw;
|
||||
|
||||
visualizer_class->set_reader = sp_line_visualizer_row_set_reader;
|
||||
|
||||
properties [PROP_TITLE] =
|
||||
g_param_spec_string ("title",
|
||||
"Title",
|
||||
"The title of the row",
|
||||
NULL,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_Y_LOWER] =
|
||||
g_param_spec_double ("y-lower",
|
||||
"Y Lower",
|
||||
"The lowest Y value for the visualizer",
|
||||
-G_MAXDOUBLE,
|
||||
G_MAXDOUBLE,
|
||||
0.0,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_Y_UPPER] =
|
||||
g_param_spec_double ("y-upper",
|
||||
"Y Upper",
|
||||
"The highest Y value for the visualizer",
|
||||
-G_MAXDOUBLE,
|
||||
G_MAXDOUBLE,
|
||||
100.0,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_init (SpLineVisualizerRow *self)
|
||||
{
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
PangoAttrList *attrs = pango_attr_list_new ();
|
||||
|
||||
priv->lines = g_array_new (FALSE, FALSE, sizeof (LineInfo));
|
||||
|
||||
pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL * PANGO_SCALE_SMALL));
|
||||
|
||||
priv->label = g_object_new (GTK_TYPE_LABEL,
|
||||
"attributes", attrs,
|
||||
"visible", TRUE,
|
||||
"xalign", 0.0f,
|
||||
"yalign", 0.0f,
|
||||
NULL);
|
||||
gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (priv->label));
|
||||
|
||||
pango_attr_list_unref (attrs);
|
||||
}
|
||||
|
||||
void
|
||||
sp_line_visualizer_row_add_counter (SpLineVisualizerRow *self,
|
||||
guint counter_id,
|
||||
const GdkRGBA *color)
|
||||
{
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
LineInfo line_info = { 0 };
|
||||
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
g_assert (priv->lines != NULL);
|
||||
|
||||
line_info.id = counter_id;
|
||||
line_info.line_width = 1.0;
|
||||
|
||||
if (color != NULL)
|
||||
{
|
||||
line_info.foreground = *color;
|
||||
line_info.use_default_style = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
gdk_rgba_parse (&line_info.foreground, "#000");
|
||||
line_info.use_default_style = TRUE;
|
||||
}
|
||||
|
||||
g_array_append_val (priv->lines, line_info);
|
||||
|
||||
if (SP_LINE_VISUALIZER_ROW_GET_CLASS (self)->counter_added)
|
||||
SP_LINE_VISUALIZER_ROW_GET_CLASS (self)->counter_added (self, counter_id);
|
||||
|
||||
sp_line_visualizer_row_queue_reload (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_line_visualizer_row_clear (SpLineVisualizerRow *self)
|
||||
{
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
|
||||
if (priv->lines->len > 0)
|
||||
g_array_remove_range (priv->lines, 0, priv->lines->len);
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
contains_id (GArray *ar,
|
||||
guint id)
|
||||
{
|
||||
for (guint i = 0; i < ar->len; i++)
|
||||
{
|
||||
const LineInfo *info = &g_array_index (ar, LineInfo, i);
|
||||
if (info->id == id)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static inline guint8
|
||||
counter_type (LoadData *load,
|
||||
guint counter_id)
|
||||
{
|
||||
/* TODO: Support other counter types
|
||||
*
|
||||
* We need to keep some information on the counter (by id) so that we
|
||||
* can track the counters type (which is a 1-byte type id).
|
||||
*/
|
||||
return SP_CAPTURE_COUNTER_DOUBLE;
|
||||
}
|
||||
|
||||
static inline gdouble
|
||||
calc_x (gint64 lower,
|
||||
gint64 upper,
|
||||
gint64 value)
|
||||
{
|
||||
return (gdouble)(value - lower) / (gdouble)(upper - lower);
|
||||
}
|
||||
|
||||
static inline gdouble
|
||||
calc_y_double (gdouble lower,
|
||||
gdouble upper,
|
||||
gdouble value)
|
||||
{
|
||||
return (value - lower) / (upper - lower);
|
||||
}
|
||||
|
||||
static inline gdouble
|
||||
calc_y_int64 (gint64 lower,
|
||||
gint64 upper,
|
||||
gint64 value)
|
||||
{
|
||||
return (gdouble)(value - lower) / (gdouble)(upper - lower);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_line_visualizer_row_load_data_frame_cb (const SpCaptureFrame *frame,
|
||||
gpointer user_data)
|
||||
{
|
||||
LoadData *load = user_data;
|
||||
|
||||
g_assert (frame != NULL);
|
||||
g_assert (frame->type == SP_CAPTURE_FRAME_CTRSET ||
|
||||
frame->type == SP_CAPTURE_FRAME_CTRDEF);
|
||||
g_assert (load != NULL);
|
||||
|
||||
if (frame->type == SP_CAPTURE_FRAME_CTRSET)
|
||||
{
|
||||
const SpCaptureFrameCounterSet *set = (SpCaptureFrameCounterSet *)frame;
|
||||
gdouble x = calc_x (load->begin_time, load->end_time, frame->time);
|
||||
|
||||
for (guint i = 0; i < set->n_values; i++)
|
||||
{
|
||||
const SpCaptureCounterValues *group = &set->values[i];
|
||||
|
||||
for (guint j = 0; j < G_N_ELEMENTS (group->ids); j++)
|
||||
{
|
||||
guint counter_id = group->ids[j];
|
||||
|
||||
if (counter_id != 0 && contains_id (load->lines, counter_id))
|
||||
{
|
||||
gdouble y;
|
||||
|
||||
if (counter_type (load, counter_id) == SP_CAPTURE_COUNTER_DOUBLE)
|
||||
y = calc_y_double (load->y_lower, load->y_upper, group->values[j].vdbl);
|
||||
else
|
||||
y = calc_y_int64 (load->y_lower, load->y_upper, group->values[j].v64);
|
||||
|
||||
point_cache_add_point_to_set (load->cache, counter_id, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_line_visualizer_row_load_data_range_cb (const SpCaptureFrame *frame,
|
||||
gpointer user_data)
|
||||
{
|
||||
LoadData *load = user_data;
|
||||
|
||||
g_assert (frame != NULL);
|
||||
g_assert (frame->type == SP_CAPTURE_FRAME_CTRSET ||
|
||||
frame->type == SP_CAPTURE_FRAME_CTRDEF);
|
||||
g_assert (load != NULL);
|
||||
g_assert (load->y_upper_set == FALSE ||
|
||||
load->y_lower_set == FALSE);
|
||||
|
||||
if (frame->type == SP_CAPTURE_FRAME_CTRSET)
|
||||
{
|
||||
const SpCaptureFrameCounterSet *set = (SpCaptureFrameCounterSet *)frame;
|
||||
|
||||
for (guint i = 0; i < set->n_values; i++)
|
||||
{
|
||||
const SpCaptureCounterValues *group = &set->values[i];
|
||||
|
||||
for (guint j = 0; j < G_N_ELEMENTS (group->ids); j++)
|
||||
{
|
||||
guint counter_id = group->ids[j];
|
||||
|
||||
if (counter_id != 0 && contains_id (load->lines, counter_id))
|
||||
{
|
||||
gdouble y;
|
||||
|
||||
if (counter_type (load, counter_id) == SP_CAPTURE_COUNTER_DOUBLE)
|
||||
y = group->values[j].vdbl;
|
||||
else
|
||||
y = group->values[j].v64;
|
||||
|
||||
if (!load->y_upper_set)
|
||||
load->y_upper = MAX (load->y_upper, y);
|
||||
|
||||
if (!load->y_lower_set)
|
||||
load->y_lower = MIN (load->y_lower, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_load_data_worker (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
LoadData *load = task_data;
|
||||
g_autoptr(GArray) counter_ids = NULL;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (source_object));
|
||||
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
counter_ids = g_array_new (FALSE, FALSE, sizeof (guint));
|
||||
|
||||
for (guint i = 0; i < load->lines->len; i++)
|
||||
{
|
||||
const LineInfo *line_info = &g_array_index (load->lines, LineInfo, i);
|
||||
g_array_append_val (counter_ids, line_info->id);
|
||||
}
|
||||
|
||||
sp_capture_cursor_add_condition (load->cursor,
|
||||
sp_capture_condition_new_where_counter_in (counter_ids->len,
|
||||
(guint *)(gpointer)counter_ids->data));
|
||||
|
||||
/* If y boundaries are not set, we need to discover them by scaning the data. */
|
||||
if (!load->y_lower_set || !load->y_upper_set)
|
||||
{
|
||||
sp_capture_cursor_foreach (load->cursor, sp_line_visualizer_row_load_data_range_cb, load);
|
||||
sp_capture_cursor_reset (load->cursor);
|
||||
|
||||
/* Add extra boundary for some space above the graph line */
|
||||
if (G_MAXDOUBLE - load->y_upper > (load->y_upper * .25))
|
||||
load->y_upper *= 1.25;
|
||||
}
|
||||
|
||||
sp_capture_cursor_foreach (load->cursor, sp_line_visualizer_row_load_data_frame_cb, load);
|
||||
g_task_return_pointer (task, g_steal_pointer (&load->cache), (GDestroyNotify)point_cache_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_line_visualizer_row_load_data_async (SpLineVisualizerRow *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
g_autoptr(GTask) task = NULL;
|
||||
LoadData *load;
|
||||
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
g_task_set_priority (task, G_PRIORITY_LOW);
|
||||
g_task_set_source_tag (task, sp_line_visualizer_row_load_data_async);
|
||||
|
||||
if (priv->reader == NULL)
|
||||
{
|
||||
g_task_return_new_error (task,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"No data loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
load = g_slice_new0 (LoadData);
|
||||
load->cache = point_cache_new ();
|
||||
load->y_lower = priv->y_lower;
|
||||
load->y_upper = priv->y_upper;
|
||||
load->y_lower_set = priv->y_lower_set;
|
||||
load->y_upper_set = priv->y_upper_set;
|
||||
load->begin_time = sp_capture_reader_get_start_time (priv->reader);
|
||||
load->end_time = sp_capture_reader_get_end_time (priv->reader);
|
||||
load->cursor = sp_capture_cursor_new (priv->reader);
|
||||
load->lines = copy_array (priv->lines);
|
||||
|
||||
for (guint i = 0; i < load->lines->len; i++)
|
||||
{
|
||||
const LineInfo *line_info = &g_array_index (load->lines, LineInfo, i);
|
||||
|
||||
point_cache_add_set (load->cache, line_info->id);
|
||||
}
|
||||
|
||||
g_task_set_task_data (task, load, load_data_free);
|
||||
g_task_run_in_thread (task, sp_line_visualizer_row_load_data_worker);
|
||||
}
|
||||
|
||||
static PointCache *
|
||||
sp_line_visualizer_row_load_data_finish (SpLineVisualizerRow *self,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
LoadData *state;
|
||||
|
||||
g_assert (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
g_assert (G_IS_TASK (result));
|
||||
|
||||
state = g_task_get_task_data (G_TASK (result));
|
||||
|
||||
if (!priv->y_lower_set && priv->y_lower != state->y_lower)
|
||||
{
|
||||
priv->y_lower = state->y_lower;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_Y_LOWER]);
|
||||
}
|
||||
|
||||
if (!priv->y_upper_set && priv->y_upper != state->y_upper)
|
||||
{
|
||||
priv->y_upper = state->y_upper;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_Y_UPPER]);
|
||||
}
|
||||
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
void
|
||||
sp_line_visualizer_row_set_line_width (SpLineVisualizerRow *self,
|
||||
guint counter_id,
|
||||
gdouble width)
|
||||
{
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
|
||||
for (guint i = 0; i < priv->lines->len; i++)
|
||||
{
|
||||
LineInfo *info = &g_array_index (priv->lines, LineInfo, i);
|
||||
|
||||
if (info->id == counter_id)
|
||||
{
|
||||
info->line_width = width;
|
||||
sp_line_visualizer_row_queue_reload (self);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_line_visualizer_row_set_fill (SpLineVisualizerRow *self,
|
||||
guint counter_id,
|
||||
const GdkRGBA *color)
|
||||
{
|
||||
SpLineVisualizerRowPrivate *priv = sp_line_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LINE_VISUALIZER_ROW (self));
|
||||
|
||||
for (guint i = 0; i < priv->lines->len; i++)
|
||||
{
|
||||
LineInfo *info = &g_array_index (priv->lines, LineInfo, i);
|
||||
|
||||
if (info->id == counter_id)
|
||||
{
|
||||
info->fill = !!color;
|
||||
if (color != NULL)
|
||||
info->background = *color;
|
||||
sp_line_visualizer_row_queue_reload (self);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
src/libsysprof-ui/sp-line-visualizer-row.h
Normal file
57
src/libsysprof-ui/sp-line-visualizer-row.h
Normal file
@ -0,0 +1,57 @@
|
||||
/* sp-line-visualizer-row.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_LINE_VISUALIZER_ROW_H
|
||||
#define SP_LINE_VISUALIZER_ROW_H
|
||||
|
||||
#include "sp-visualizer-row.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_LINE_VISUALIZER_ROW (sp_line_visualizer_row_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpLineVisualizerRow, sp_line_visualizer_row, SP, LINE_VISUALIZER_ROW, SpVisualizerRow)
|
||||
|
||||
struct _SpLineVisualizerRowClass
|
||||
{
|
||||
SpVisualizerRowClass parent_class;
|
||||
|
||||
void (*counter_added) (SpLineVisualizerRow *self,
|
||||
guint counter_id);
|
||||
|
||||
/*< private >*/
|
||||
gpointer _reserved[16];
|
||||
};
|
||||
|
||||
GtkWidget *sp_line_visualizer_row_new (void);
|
||||
void sp_line_visualizer_row_clear (SpLineVisualizerRow *self);
|
||||
void sp_line_visualizer_row_add_counter (SpLineVisualizerRow *self,
|
||||
guint counter_id,
|
||||
const GdkRGBA *color);
|
||||
void sp_line_visualizer_row_set_line_width (SpLineVisualizerRow *self,
|
||||
guint counter_id,
|
||||
gdouble width);
|
||||
void sp_line_visualizer_row_set_fill (SpLineVisualizerRow *self,
|
||||
guint counter_id,
|
||||
const GdkRGBA *color);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_LINE_VISUALIZER_ROW_H */
|
||||
488
src/libsysprof-ui/sp-mark-visualizer-row.c
Normal file
488
src/libsysprof-ui/sp-mark-visualizer-row.c
Normal file
@ -0,0 +1,488 @@
|
||||
/* sp-mark-visualizer-row.c
|
||||
*
|
||||
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-mark-visualizer-row"
|
||||
|
||||
#include "sp-capture-condition.h"
|
||||
#include "sp-capture-cursor.h"
|
||||
#include "rectangles.h"
|
||||
#include "sp-mark-visualizer-row.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/*
|
||||
* Our reader as assigned by the visualizer system.
|
||||
*/
|
||||
SpCaptureReader *reader;
|
||||
|
||||
/*
|
||||
* The group we care about for displaying marks. The idea is that we only
|
||||
* show one group per-row, so tooling from separate systems can either be
|
||||
* displayed together or not, based on usefulness.
|
||||
*/
|
||||
gchar *group;
|
||||
|
||||
/*
|
||||
* Rectangle information we have built from the marks that belong to this
|
||||
* row of information.
|
||||
*/
|
||||
Rectangles *rectangles;
|
||||
|
||||
/*
|
||||
* Child widget to display the label in the upper corner.
|
||||
*/
|
||||
GtkLabel *label;
|
||||
} SpMarkVisualizerRowPrivate;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gchar *group;
|
||||
SpCaptureCursor *cursor;
|
||||
Rectangles *rects;
|
||||
GHashTable *inferred_rects;
|
||||
} BuildState;
|
||||
|
||||
typedef struct {
|
||||
gint64 time;
|
||||
gchar *name;
|
||||
gchar *message;
|
||||
} InferredRect;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_GROUP,
|
||||
PROP_TITLE,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (SpMarkVisualizerRow, sp_mark_visualizer_row, SP_TYPE_VISUALIZER_ROW)
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
static void
|
||||
free_inferred_rect (InferredRect *rect)
|
||||
{
|
||||
g_free (rect->name);
|
||||
g_free (rect->message);
|
||||
g_slice_free (InferredRect, rect);
|
||||
}
|
||||
|
||||
static void
|
||||
add_inferred_rect_point (BuildState *state,
|
||||
InferredRect *rect)
|
||||
{
|
||||
rectangles_add (state->rects,
|
||||
rect->time,
|
||||
rect->time,
|
||||
rect->name,
|
||||
rect->message);
|
||||
}
|
||||
|
||||
static void
|
||||
build_state_free (BuildState *state)
|
||||
{
|
||||
g_hash_table_remove_all (state->inferred_rects);
|
||||
g_clear_pointer (&state->inferred_rects, g_hash_table_unref);
|
||||
g_free (state->group);
|
||||
g_object_unref (state->cursor);
|
||||
g_slice_free (BuildState, state);
|
||||
}
|
||||
|
||||
/* Creates rectangles for GPU marks.
|
||||
*
|
||||
* GPU marks come in as a begin and an end, but since those things are
|
||||
* processessed on potentially different CPUs, perf doesn't record
|
||||
* them in sequence order in the mmap ringbuffer. Thus, we have to
|
||||
* shuffle things back around at visualization time.
|
||||
*/
|
||||
static gboolean
|
||||
process_gpu_mark (BuildState *state,
|
||||
const SpCaptureMark *mark)
|
||||
{
|
||||
InferredRect *rect = g_hash_table_lookup (state->inferred_rects,
|
||||
mark->message);
|
||||
|
||||
if (rect)
|
||||
{
|
||||
gboolean ours_begins = strstr (mark->name, "begin") != NULL;
|
||||
gboolean theirs_begins = strstr (rect->name, "begin") != NULL;
|
||||
|
||||
if (ours_begins != theirs_begins)
|
||||
{
|
||||
rectangles_add (state->rects,
|
||||
ours_begins ? mark->frame.time : rect->time,
|
||||
ours_begins ? rect->time : mark->frame.time,
|
||||
ours_begins ? mark->name : rect->name,
|
||||
rect->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Something went weird with the tracking (GPU hang caused
|
||||
* two starts?), so just put up both time points as vertical
|
||||
* bars for now.
|
||||
*/
|
||||
rectangles_add (state->rects,
|
||||
mark->frame.time,
|
||||
mark->frame.time,
|
||||
mark->name,
|
||||
mark->message);
|
||||
|
||||
add_inferred_rect_point (state, rect);
|
||||
}
|
||||
|
||||
g_hash_table_remove (state->inferred_rects,
|
||||
rect->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
rect = g_slice_new0 (InferredRect);
|
||||
rect->name = g_strdup (mark->name);
|
||||
rect->message = g_strdup (mark->message);
|
||||
rect->time = mark->frame.time;
|
||||
|
||||
g_hash_table_insert (state->inferred_rects, rect->message, rect);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
sp_mark_visualizer_row_add_rect (const SpCaptureFrame *frame,
|
||||
gpointer user_data)
|
||||
{
|
||||
BuildState *state = user_data;
|
||||
const SpCaptureMark *mark = (const SpCaptureMark *)frame;
|
||||
|
||||
g_assert (frame != NULL);
|
||||
g_assert (frame->type == SP_CAPTURE_FRAME_MARK);
|
||||
g_assert (state != NULL);
|
||||
g_assert (state->rects != NULL);
|
||||
|
||||
if (g_strcmp0 (mark->group, state->group) == 0)
|
||||
{
|
||||
if (strstr (mark->name, "gpu begin") != NULL ||
|
||||
strstr (mark->name, "gpu end") != NULL)
|
||||
process_gpu_mark (state, mark);
|
||||
else
|
||||
rectangles_add (state->rects,
|
||||
frame->time,
|
||||
frame->time + mark->duration,
|
||||
mark->name,
|
||||
mark->message);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_mark_visualizer_row_worker (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
BuildState *state = task_data;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
gint64 end_time;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
g_assert (SP_IS_MARK_VISUALIZER_ROW (source_object));
|
||||
g_assert (state != NULL);
|
||||
g_assert (state->cursor != NULL);
|
||||
|
||||
sp_capture_cursor_foreach (state->cursor, sp_mark_visualizer_row_add_rect, state);
|
||||
|
||||
/* If any inferred rects are left incomplete, just drop them in as
|
||||
* point events for now.
|
||||
*/
|
||||
g_hash_table_iter_init (&iter, state->inferred_rects);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||||
{
|
||||
InferredRect *rect = value;
|
||||
|
||||
add_inferred_rect_point (state, rect);
|
||||
}
|
||||
g_hash_table_remove_all (state->inferred_rects);
|
||||
|
||||
end_time = sp_capture_reader_get_end_time (sp_capture_cursor_get_reader (state->cursor));
|
||||
rectangles_set_end_time (state->rects, end_time);
|
||||
g_task_return_pointer (task, g_steal_pointer (&state->rects), (GDestroyNotify)rectangles_free);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_mark_visualizer_row_query_tooltip (GtkWidget *widget,
|
||||
gint x,
|
||||
gint y,
|
||||
gboolean keyboard_mode,
|
||||
GtkTooltip *tooltip)
|
||||
{
|
||||
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)widget;
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_MARK_VISUALIZER_ROW (self));
|
||||
|
||||
if (priv->rectangles == NULL)
|
||||
return FALSE;
|
||||
|
||||
return rectangles_query_tooltip (priv->rectangles, tooltip, priv->group, x, y);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_mark_visualizer_row_draw (GtkWidget *widget,
|
||||
cairo_t *cr)
|
||||
{
|
||||
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)widget;
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
GtkStyleContext *style_context;
|
||||
GtkStateFlags flags;
|
||||
GdkRGBA foreground;
|
||||
GtkAllocation alloc;
|
||||
gboolean ret;
|
||||
|
||||
g_assert (SP_IS_MARK_VISUALIZER_ROW (widget));
|
||||
g_assert (cr != NULL);
|
||||
|
||||
gtk_widget_get_allocation (widget, &alloc);
|
||||
|
||||
ret = GTK_WIDGET_CLASS (sp_mark_visualizer_row_parent_class)->draw (widget, cr);
|
||||
|
||||
if (priv->rectangles == NULL)
|
||||
return ret;
|
||||
|
||||
style_context = gtk_widget_get_style_context (widget);
|
||||
flags = gtk_widget_get_state_flags (widget);
|
||||
gtk_style_context_get_color (style_context, flags, &foreground);
|
||||
|
||||
rectangles_draw (priv->rectangles, GTK_WIDGET (self), cr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
data_load_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)object;
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_MARK_VISUALIZER_ROW (self));
|
||||
g_assert (G_IS_TASK (result));
|
||||
|
||||
g_clear_pointer (&priv->rectangles, rectangles_free);
|
||||
priv->rectangles = g_task_propagate_pointer (G_TASK (result), NULL);
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_mark_visualizer_row_reload (SpMarkVisualizerRow *self)
|
||||
{
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
g_autoptr(SpCaptureCursor) cursor = NULL;
|
||||
g_autoptr(GTask) task = NULL;
|
||||
SpCaptureCondition *condition;
|
||||
BuildState *state;
|
||||
|
||||
g_assert (SP_IS_MARK_VISUALIZER_ROW (self));
|
||||
|
||||
g_clear_pointer (&priv->rectangles, rectangles_free);
|
||||
|
||||
condition = sp_capture_condition_new_where_type_in (1, (SpCaptureFrameType[]) { SP_CAPTURE_FRAME_MARK });
|
||||
cursor = sp_capture_cursor_new (priv->reader);
|
||||
sp_capture_cursor_add_condition (cursor, g_steal_pointer (&condition));
|
||||
|
||||
state = g_slice_new0 (BuildState);
|
||||
state->inferred_rects = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
NULL,
|
||||
(GDestroyNotify)free_inferred_rect);
|
||||
state->group = g_strdup (priv->group);
|
||||
state->cursor = g_steal_pointer (&cursor);
|
||||
state->rects = rectangles_new (sp_capture_reader_get_start_time (priv->reader),
|
||||
sp_capture_reader_get_end_time (priv->reader));
|
||||
|
||||
task = g_task_new (self, NULL, data_load_cb, NULL);
|
||||
g_task_set_task_data (task, state, (GDestroyNotify)build_state_free);
|
||||
g_task_run_in_thread (task, sp_mark_visualizer_row_worker);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_mark_visualizer_row_set_reader (SpVisualizerRow *row,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)row;
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_MARK_VISUALIZER_ROW (self));
|
||||
|
||||
if (reader != priv->reader)
|
||||
{
|
||||
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
|
||||
if (reader != NULL)
|
||||
priv->reader = sp_capture_reader_ref (reader);
|
||||
sp_mark_visualizer_row_reload (self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_mark_visualizer_row_finalize (GObject *object)
|
||||
{
|
||||
SpMarkVisualizerRow *self = (SpMarkVisualizerRow *)object;
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->group, g_free);
|
||||
g_clear_pointer (&priv->rectangles, rectangles_free);
|
||||
|
||||
G_OBJECT_CLASS (sp_mark_visualizer_row_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_mark_visualizer_row_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpMarkVisualizerRow *self = SP_MARK_VISUALIZER_ROW (object);
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_GROUP:
|
||||
g_value_set_string (value, sp_mark_visualizer_row_get_group (self));
|
||||
break;
|
||||
|
||||
case PROP_TITLE:
|
||||
g_value_set_string (value, gtk_label_get_label (priv->label));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_mark_visualizer_row_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpMarkVisualizerRow *self = SP_MARK_VISUALIZER_ROW (object);
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_GROUP:
|
||||
sp_mark_visualizer_row_set_group (self, g_value_get_string (value));
|
||||
break;
|
||||
|
||||
case PROP_TITLE:
|
||||
gtk_label_set_label (priv->label, g_value_get_string (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_mark_visualizer_row_class_init (SpMarkVisualizerRowClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
SpVisualizerRowClass *visualizer_class = SP_VISUALIZER_ROW_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_mark_visualizer_row_finalize;
|
||||
object_class->get_property = sp_mark_visualizer_row_get_property;
|
||||
object_class->set_property = sp_mark_visualizer_row_set_property;
|
||||
|
||||
widget_class->draw = sp_mark_visualizer_row_draw;
|
||||
widget_class->query_tooltip = sp_mark_visualizer_row_query_tooltip;
|
||||
|
||||
visualizer_class->set_reader = sp_mark_visualizer_row_set_reader;
|
||||
|
||||
properties [PROP_GROUP] =
|
||||
g_param_spec_string ("group",
|
||||
"Group",
|
||||
"The group of the row",
|
||||
NULL,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_TITLE] =
|
||||
g_param_spec_string ("title",
|
||||
"Title",
|
||||
"The title of the row",
|
||||
NULL,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_mark_visualizer_row_init (SpMarkVisualizerRow *self)
|
||||
{
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
PangoAttrList *attrs = pango_attr_list_new ();
|
||||
|
||||
gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE);
|
||||
|
||||
pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL * PANGO_SCALE_SMALL));
|
||||
|
||||
priv->label = g_object_new (GTK_TYPE_LABEL,
|
||||
"attributes", attrs,
|
||||
"visible", TRUE,
|
||||
"xalign", 0.0f,
|
||||
"yalign", 0.0f,
|
||||
NULL);
|
||||
gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (priv->label));
|
||||
|
||||
pango_attr_list_unref (attrs);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
sp_mark_visualizer_row_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_MARK_VISUALIZER_ROW, NULL);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
sp_mark_visualizer_row_get_group (SpMarkVisualizerRow *self)
|
||||
{
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_MARK_VISUALIZER_ROW (self), NULL);
|
||||
|
||||
return priv->group;
|
||||
}
|
||||
|
||||
void
|
||||
sp_mark_visualizer_row_set_group (SpMarkVisualizerRow *self,
|
||||
const gchar *group)
|
||||
{
|
||||
SpMarkVisualizerRowPrivate *priv = sp_mark_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_MARK_VISUALIZER_ROW (self));
|
||||
|
||||
if (g_strcmp0 (priv->group, group) != 0)
|
||||
{
|
||||
g_free (priv->group);
|
||||
priv->group = g_strdup (group);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_GROUP]);
|
||||
}
|
||||
}
|
||||
44
src/libsysprof-ui/sp-mark-visualizer-row.h
Normal file
44
src/libsysprof-ui/sp-mark-visualizer-row.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* sp-mark-visualizer-row.h
|
||||
*
|
||||
* Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sp-visualizer-row.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_MARK_VISUALIZER_ROW (sp_mark_visualizer_row_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpMarkVisualizerRow, sp_mark_visualizer_row, SP, MARK_VISUALIZER_ROW, SpVisualizerRow)
|
||||
|
||||
struct _SpMarkVisualizerRowClass
|
||||
{
|
||||
SpVisualizerRowClass parent_class;
|
||||
|
||||
/*< private >*/
|
||||
gpointer _reserved[16];
|
||||
};
|
||||
|
||||
GtkWidget *sp_mark_visualizer_row_new (void);
|
||||
const gchar *sp_mark_visualizer_row_get_group (SpMarkVisualizerRow *self);
|
||||
void sp_mark_visualizer_row_set_group (SpMarkVisualizerRow *self,
|
||||
const gchar *group);
|
||||
|
||||
G_END_DECLS
|
||||
495
src/libsysprof-ui/sp-model-filter.c
Normal file
495
src/libsysprof-ui/sp-model-filter.c
Normal file
@ -0,0 +1,495 @@
|
||||
/* sp-model-filter.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "sp-model-filter.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GSequenceIter *child_iter;
|
||||
GSequenceIter *filter_iter;
|
||||
} SpModelFilterItem;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* The list we are filtering */
|
||||
GListModel *child_model;
|
||||
|
||||
/*
|
||||
* Both sequences point to the same SpModelFilterItem which
|
||||
* contains cross-referencing stable GSequenceIter pointers.
|
||||
* The child_seq is considered the "owner" and used to release
|
||||
* allocated resources.
|
||||
*/
|
||||
GSequence *child_seq;
|
||||
GSequence *filter_seq;
|
||||
|
||||
/*
|
||||
* Typical set of callback/closure/free function pointers and data.
|
||||
* Called for child items to determine visibility state.
|
||||
*/
|
||||
SpModelFilterFunc filter_func;
|
||||
gpointer filter_func_data;
|
||||
GDestroyNotify filter_func_data_destroy;
|
||||
|
||||
/*
|
||||
* If set, we will not emit items-changed. This is useful during
|
||||
* invalidation so that we can do a single emission for all items
|
||||
* that have changed.
|
||||
*/
|
||||
guint supress_items_changed : 1;
|
||||
} SpModelFilterPrivate;
|
||||
|
||||
static void list_model_iface_init (GListModelInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpModelFilter, sp_model_filter, G_TYPE_OBJECT, 0,
|
||||
G_ADD_PRIVATE (SpModelFilter)
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
|
||||
list_model_iface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_CHILD_MODEL,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
static guint signal_id;
|
||||
|
||||
static void
|
||||
sp_model_filter_item_free (gpointer data)
|
||||
{
|
||||
SpModelFilterItem *item = data;
|
||||
|
||||
g_clear_pointer (&item->filter_iter, g_sequence_remove);
|
||||
item->child_iter = NULL;
|
||||
g_slice_free (SpModelFilterItem, item);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_model_filter_default_filter_func (GObject *item,
|
||||
gpointer user_data)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Locates the next item in the filter sequence starting from
|
||||
* the cross-reference found at @iter. If none are found, the
|
||||
* end_iter for the filter sequence is returned.
|
||||
*
|
||||
* This returns an iter in the filter_sequence, not the child_seq.
|
||||
*
|
||||
* Returns: a #GSequenceIter from the filter sequence.
|
||||
*/
|
||||
static GSequenceIter *
|
||||
find_next_visible_filter_iter (SpModelFilter *self,
|
||||
GSequenceIter *iter)
|
||||
{
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_MODEL_FILTER (self));
|
||||
g_assert (iter != NULL);
|
||||
|
||||
for (; !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter))
|
||||
{
|
||||
SpModelFilterItem *item = g_sequence_get (iter);
|
||||
|
||||
g_assert (item->child_iter == iter);
|
||||
g_assert (item->filter_iter == NULL ||
|
||||
g_sequence_iter_get_sequence (item->filter_iter) == priv->filter_seq);
|
||||
|
||||
if (item->filter_iter != NULL)
|
||||
return item->filter_iter;
|
||||
}
|
||||
|
||||
return g_sequence_get_end_iter (priv->filter_seq);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_model_filter_child_model_items_changed (SpModelFilter *self,
|
||||
guint position,
|
||||
guint n_removed,
|
||||
guint n_added,
|
||||
GListModel *child_model)
|
||||
{
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
gboolean unblocked;
|
||||
|
||||
g_assert (SP_IS_MODEL_FILTER (self));
|
||||
g_assert (G_IS_LIST_MODEL (child_model));
|
||||
g_assert (priv->child_model == child_model);
|
||||
g_assert (position <= (guint)g_sequence_get_length (priv->child_seq));
|
||||
g_assert ((g_sequence_get_length (priv->child_seq) - n_removed + n_added) ==
|
||||
g_list_model_get_n_items (child_model));
|
||||
|
||||
unblocked = !priv->supress_items_changed;
|
||||
|
||||
if (n_removed > 0)
|
||||
{
|
||||
GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position);
|
||||
gint first_position = -1;
|
||||
guint count = 0;
|
||||
|
||||
g_assert (!g_sequence_iter_is_end (iter));
|
||||
|
||||
/* Small shortcut when all items are removed */
|
||||
if (n_removed == (guint)g_sequence_get_length (priv->child_seq))
|
||||
{
|
||||
g_sequence_remove_range (g_sequence_get_begin_iter (priv->child_seq),
|
||||
g_sequence_get_end_iter (priv->child_seq));
|
||||
g_assert (g_sequence_is_empty (priv->child_seq));
|
||||
g_assert (g_sequence_is_empty (priv->filter_seq));
|
||||
goto add_new_items;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < n_removed; i++)
|
||||
{
|
||||
GSequenceIter *to_remove = iter;
|
||||
SpModelFilterItem *item = g_sequence_get (iter);
|
||||
|
||||
g_assert (item != NULL);
|
||||
g_assert (item->child_iter == iter);
|
||||
g_assert (item->filter_iter == NULL ||
|
||||
g_sequence_iter_get_sequence (item->filter_iter) == priv->filter_seq);
|
||||
|
||||
/* If this is visible, we need to notify about removal */
|
||||
if (unblocked && item->filter_iter != NULL)
|
||||
{
|
||||
if (first_position < 0)
|
||||
first_position = g_sequence_iter_get_position (item->filter_iter);
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
/* Fetch the next while the iter is still valid */
|
||||
iter = g_sequence_iter_next (iter);
|
||||
|
||||
/* Cascades into also removing from filter_seq. */
|
||||
g_sequence_remove (to_remove);
|
||||
}
|
||||
|
||||
if (unblocked && first_position >= 0)
|
||||
g_list_model_items_changed (G_LIST_MODEL (self), first_position, count, 0);
|
||||
}
|
||||
|
||||
add_new_items:
|
||||
|
||||
if (n_added > 0)
|
||||
{
|
||||
GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position);
|
||||
GSequenceIter *filter_iter = find_next_visible_filter_iter (self, iter);
|
||||
guint filter_position = g_sequence_iter_get_position (filter_iter);
|
||||
guint count = 0;
|
||||
|
||||
/* Walk backwards to insert items into the filter list so that
|
||||
* we can use the same filter_position for each items-changed
|
||||
* signal emission.
|
||||
*/
|
||||
for (guint i = position + n_added; i > position; i--)
|
||||
{
|
||||
g_autoptr(GObject) instance = NULL;
|
||||
SpModelFilterItem *item;
|
||||
|
||||
item = g_slice_new0 (SpModelFilterItem);
|
||||
item->filter_iter = NULL;
|
||||
item->child_iter = g_sequence_insert_before (iter, item);
|
||||
|
||||
instance = g_list_model_get_item (child_model, i - 1);
|
||||
g_assert (G_IS_OBJECT (instance));
|
||||
|
||||
/* Check if this item is visible */
|
||||
if (priv->filter_func (instance, priv->filter_func_data))
|
||||
{
|
||||
item->filter_iter = g_sequence_insert_before (filter_iter, item);
|
||||
|
||||
/* Use this in the future for relative positioning */
|
||||
filter_iter = item->filter_iter;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
/* Insert next item before this */
|
||||
iter = item->child_iter;
|
||||
}
|
||||
|
||||
if (unblocked && count)
|
||||
g_list_model_items_changed (G_LIST_MODEL (self), filter_position, 0, count);
|
||||
}
|
||||
|
||||
g_assert ((guint)g_sequence_get_length (priv->child_seq) ==
|
||||
g_list_model_get_n_items (child_model));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_model_filter_finalize (GObject *object)
|
||||
{
|
||||
SpModelFilter *self = (SpModelFilter *)object;
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->child_seq, g_sequence_free);
|
||||
g_clear_pointer (&priv->filter_seq, g_sequence_free);
|
||||
|
||||
if (priv->filter_func_data_destroy)
|
||||
{
|
||||
g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy);
|
||||
priv->filter_func_data_destroy = NULL;
|
||||
}
|
||||
|
||||
g_clear_object (&priv->child_model);
|
||||
|
||||
G_OBJECT_CLASS (sp_model_filter_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_model_filter_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpModelFilter *self = SP_MODEL_FILTER (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_CHILD_MODEL:
|
||||
g_value_set_object (value, sp_model_filter_get_child_model (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_model_filter_class_init (SpModelFilterClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_model_filter_finalize;
|
||||
object_class->get_property = sp_model_filter_get_property;
|
||||
|
||||
properties [PROP_CHILD_MODEL] =
|
||||
g_param_spec_object ("child-model",
|
||||
"Child Model",
|
||||
"The child model being filtered.",
|
||||
G_TYPE_LIST_MODEL,
|
||||
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
|
||||
signal_id = g_signal_lookup ("items-changed", SP_TYPE_MODEL_FILTER);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_model_filter_init (SpModelFilter *self)
|
||||
{
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
|
||||
priv->filter_func = sp_model_filter_default_filter_func;
|
||||
priv->child_seq = g_sequence_new (sp_model_filter_item_free);
|
||||
priv->filter_seq = g_sequence_new (NULL);
|
||||
}
|
||||
|
||||
static GType
|
||||
sp_model_filter_get_item_type (GListModel *model)
|
||||
{
|
||||
SpModelFilter *self = (SpModelFilter *)model;
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_MODEL_FILTER (self));
|
||||
|
||||
return g_list_model_get_item_type (priv->child_model);
|
||||
}
|
||||
|
||||
static guint
|
||||
sp_model_filter_get_n_items (GListModel *model)
|
||||
{
|
||||
SpModelFilter *self = (SpModelFilter *)model;
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_MODEL_FILTER (self));
|
||||
g_assert (priv->filter_seq != NULL);
|
||||
|
||||
return g_sequence_get_length (priv->filter_seq);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
sp_model_filter_get_item (GListModel *model,
|
||||
guint position)
|
||||
{
|
||||
SpModelFilter *self = (SpModelFilter *)model;
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
SpModelFilterItem *item;
|
||||
GSequenceIter *iter;
|
||||
guint child_position;
|
||||
|
||||
g_assert (SP_IS_MODEL_FILTER (self));
|
||||
g_assert (position < (guint)g_sequence_get_length (priv->filter_seq));
|
||||
|
||||
iter = g_sequence_get_iter_at_pos (priv->filter_seq, position);
|
||||
g_assert (!g_sequence_iter_is_end (iter));
|
||||
|
||||
item = g_sequence_get (iter);
|
||||
g_assert (item != NULL);
|
||||
g_assert (item->filter_iter == iter);
|
||||
g_assert (item->child_iter != NULL);
|
||||
g_assert (g_sequence_iter_get_sequence (item->child_iter) == priv->child_seq);
|
||||
|
||||
child_position = g_sequence_iter_get_position (item->child_iter);
|
||||
|
||||
return g_list_model_get_item (priv->child_model, child_position);
|
||||
}
|
||||
|
||||
static void
|
||||
list_model_iface_init (GListModelInterface *iface)
|
||||
{
|
||||
iface->get_item_type = sp_model_filter_get_item_type;
|
||||
iface->get_n_items = sp_model_filter_get_n_items;
|
||||
iface->get_item = sp_model_filter_get_item;
|
||||
}
|
||||
|
||||
SpModelFilter *
|
||||
sp_model_filter_new (GListModel *child_model)
|
||||
{
|
||||
SpModelFilter *ret;
|
||||
SpModelFilterPrivate *priv;
|
||||
|
||||
g_return_val_if_fail (G_IS_LIST_MODEL (child_model), NULL);
|
||||
|
||||
ret = g_object_new (SP_TYPE_MODEL_FILTER, NULL);
|
||||
priv = sp_model_filter_get_instance_private (ret);
|
||||
priv->child_model = g_object_ref (child_model);
|
||||
|
||||
g_signal_connect_object (child_model,
|
||||
"items-changed",
|
||||
G_CALLBACK (sp_model_filter_child_model_items_changed),
|
||||
ret,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
sp_model_filter_invalidate (ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_model_filter_get_child_model:
|
||||
* @self: A #SpModelFilter
|
||||
*
|
||||
* Gets the child model that is being filtered.
|
||||
*
|
||||
* Returns: (transfer none): A #GListModel.
|
||||
*/
|
||||
GListModel *
|
||||
sp_model_filter_get_child_model (SpModelFilter *self)
|
||||
{
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_MODEL_FILTER (self), NULL);
|
||||
|
||||
return priv->child_model;
|
||||
}
|
||||
|
||||
void
|
||||
sp_model_filter_invalidate (SpModelFilter *self)
|
||||
{
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
guint n_items;
|
||||
|
||||
g_return_if_fail (SP_IS_MODEL_FILTER (self));
|
||||
|
||||
/* We block emission while in invalidate so that we can use
|
||||
* a single larger items-changed rather lots of small emissions.
|
||||
*/
|
||||
priv->supress_items_changed = TRUE;
|
||||
|
||||
/* First determine how many items we need to synthesize as a removal */
|
||||
n_items = g_sequence_get_length (priv->filter_seq);
|
||||
|
||||
/*
|
||||
* If we have a child store, we want to rebuild our list of items
|
||||
* from scratch, so just remove everything.
|
||||
*/
|
||||
if (!g_sequence_is_empty (priv->child_seq))
|
||||
g_sequence_remove_range (g_sequence_get_begin_iter (priv->child_seq),
|
||||
g_sequence_get_end_iter (priv->child_seq));
|
||||
|
||||
g_assert (g_sequence_is_empty (priv->child_seq));
|
||||
g_assert (g_sequence_is_empty (priv->filter_seq));
|
||||
g_assert (!priv->child_model || G_IS_LIST_MODEL (priv->child_model));
|
||||
|
||||
/*
|
||||
* Now add the new items by synthesizing the addition of all the
|
||||
* itmes in the list.
|
||||
*/
|
||||
if (priv->child_model != NULL)
|
||||
{
|
||||
guint child_n_items;
|
||||
|
||||
/*
|
||||
* Now add all the items as one shot to our list so that
|
||||
* we get populate our sequence and filter sequence.
|
||||
*/
|
||||
child_n_items = g_list_model_get_n_items (priv->child_model);
|
||||
sp_model_filter_child_model_items_changed (self, 0, 0, child_n_items, priv->child_model);
|
||||
|
||||
g_assert ((guint)g_sequence_get_length (priv->child_seq) == child_n_items);
|
||||
g_assert ((guint)g_sequence_get_length (priv->filter_seq) <= child_n_items);
|
||||
}
|
||||
|
||||
priv->supress_items_changed = FALSE;
|
||||
|
||||
/* Now that we've updated our sequences, notify of all the changes
|
||||
* as a single series of updates to the consumers.
|
||||
*/
|
||||
if (n_items > 0 || !g_sequence_is_empty (priv->filter_seq))
|
||||
g_list_model_items_changed (G_LIST_MODEL (self),
|
||||
0,
|
||||
n_items,
|
||||
g_sequence_get_length (priv->filter_seq));
|
||||
}
|
||||
|
||||
void
|
||||
sp_model_filter_set_filter_func (SpModelFilter *self,
|
||||
SpModelFilterFunc filter_func,
|
||||
gpointer filter_func_data,
|
||||
GDestroyNotify filter_func_data_destroy)
|
||||
{
|
||||
SpModelFilterPrivate *priv = sp_model_filter_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_MODEL_FILTER (self));
|
||||
g_return_if_fail (filter_func || (!filter_func_data && !filter_func_data_destroy));
|
||||
|
||||
if (priv->filter_func_data_destroy != NULL)
|
||||
g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy);
|
||||
|
||||
if (filter_func != NULL)
|
||||
{
|
||||
priv->filter_func = filter_func;
|
||||
priv->filter_func_data = filter_func_data;
|
||||
priv->filter_func_data_destroy = filter_func_data_destroy;
|
||||
}
|
||||
else
|
||||
{
|
||||
priv->filter_func = sp_model_filter_default_filter_func;
|
||||
priv->filter_func_data = NULL;
|
||||
priv->filter_func_data_destroy = NULL;
|
||||
}
|
||||
|
||||
sp_model_filter_invalidate (self);
|
||||
}
|
||||
52
src/libsysprof-ui/sp-model-filter.h
Normal file
52
src/libsysprof-ui/sp-model-filter.h
Normal file
@ -0,0 +1,52 @@
|
||||
/* sp-model-filter.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_MODEL_FILTER_H
|
||||
#define SP_MODEL_FILTER_H
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_MODEL_FILTER (sp_model_filter_get_type())
|
||||
|
||||
typedef gboolean (*SpModelFilterFunc) (GObject *object,
|
||||
gpointer user_data);
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpModelFilter, sp_model_filter, SP, MODEL_FILTER, GObject)
|
||||
|
||||
struct _SpModelFilterClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
gpointer padding[8];
|
||||
};
|
||||
|
||||
SpModelFilter *sp_model_filter_new (GListModel *child_model);
|
||||
GListModel *sp_model_filter_get_child_model (SpModelFilter *self);
|
||||
void sp_model_filter_invalidate (SpModelFilter *self);
|
||||
void sp_model_filter_set_filter_func (SpModelFilter *self,
|
||||
SpModelFilterFunc filter_func,
|
||||
gpointer filter_func_data,
|
||||
GDestroyNotify filter_func_data_destroy);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_MODEL_FILTER_H */
|
||||
1915
src/libsysprof-ui/sp-multi-paned.c
Normal file
1915
src/libsysprof-ui/sp-multi-paned.c
Normal file
File diff suppressed because it is too large
Load Diff
57
src/libsysprof-ui/sp-multi-paned.h
Normal file
57
src/libsysprof-ui/sp-multi-paned.h
Normal file
@ -0,0 +1,57 @@
|
||||
/* sp-multi-paned.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 Lesser General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_MULTI_PANED_H
|
||||
#define SP_MULTI_PANED_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_MULTI_PANED (sp_multi_paned_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpMultiPaned, sp_multi_paned, SP, MULTI_PANED, GtkContainer)
|
||||
|
||||
struct _SpMultiPanedClass
|
||||
{
|
||||
GtkContainerClass parent;
|
||||
|
||||
void (*resize_drag_begin) (SpMultiPaned *self,
|
||||
GtkWidget *child);
|
||||
void (*resize_drag_end) (SpMultiPaned *self,
|
||||
GtkWidget *child);
|
||||
|
||||
gpointer _reserved1;
|
||||
gpointer _reserved2;
|
||||
gpointer _reserved3;
|
||||
gpointer _reserved4;
|
||||
gpointer _reserved5;
|
||||
gpointer _reserved6;
|
||||
gpointer _reserved7;
|
||||
gpointer _reserved8;
|
||||
};
|
||||
|
||||
GtkWidget *sp_multi_paned_new (void);
|
||||
guint sp_multi_paned_get_n_children (SpMultiPaned *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_MULTI_PANED_H */
|
||||
254
src/libsysprof-ui/sp-process-model-row.c
Normal file
254
src/libsysprof-ui/sp-process-model-row.c
Normal file
@ -0,0 +1,254 @@
|
||||
/* sp-process-model-row.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "sp-process-model-row.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpProcessModelItem *item;
|
||||
|
||||
GtkLabel *args_label;
|
||||
GtkLabel *label;
|
||||
GtkLabel *pid;
|
||||
GtkImage *image;
|
||||
GtkImage *check;
|
||||
} SpProcessModelRowPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (SpProcessModelRow, sp_process_model_row, GTK_TYPE_LIST_BOX_ROW)
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_ITEM,
|
||||
PROP_SELECTED,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
GtkWidget *
|
||||
sp_process_model_row_new (SpProcessModelItem *item)
|
||||
{
|
||||
return g_object_new (SP_TYPE_PROCESS_MODEL_ROW,
|
||||
"item", item,
|
||||
NULL);
|
||||
}
|
||||
|
||||
SpProcessModelItem *
|
||||
sp_process_model_row_get_item (SpProcessModelRow *self)
|
||||
{
|
||||
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ROW (self), NULL);
|
||||
|
||||
return priv->item;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_row_set_item (SpProcessModelRow *self,
|
||||
SpProcessModelItem *item)
|
||||
{
|
||||
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_PROCESS_MODEL_ROW (self));
|
||||
g_assert (SP_IS_PROCESS_MODEL_ITEM (item));
|
||||
|
||||
if (g_set_object (&priv->item, item))
|
||||
{
|
||||
const gchar *command_line;
|
||||
g_auto(GStrv) parts = NULL;
|
||||
g_autofree gchar *pidstr = NULL;
|
||||
const gchar * const *argv;
|
||||
GPid pid;
|
||||
|
||||
command_line = sp_process_model_item_get_command_line (item);
|
||||
parts = g_strsplit (command_line ?: "", "\n", 0);
|
||||
gtk_label_set_label (priv->label, parts [0]);
|
||||
|
||||
if ((NULL != (argv = sp_process_model_item_get_argv (item))) && (argv[0] != NULL))
|
||||
{
|
||||
g_autofree gchar *argvstr = g_strjoinv (" ", (gchar **)&argv[1]);
|
||||
g_autofree gchar *escaped = g_markup_escape_text (argvstr, -1);
|
||||
|
||||
gtk_label_set_label (priv->args_label, escaped);
|
||||
}
|
||||
|
||||
pid = sp_process_model_item_get_pid (item);
|
||||
pidstr = g_strdup_printf ("<small>%u</small>", pid);
|
||||
gtk_label_set_label (priv->pid, pidstr);
|
||||
gtk_label_set_use_markup (priv->pid, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_process_model_row_get_selected (SpProcessModelRow *self)
|
||||
{
|
||||
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ROW (self), FALSE);
|
||||
|
||||
return gtk_widget_get_visible (GTK_WIDGET (priv->check));
|
||||
}
|
||||
|
||||
void
|
||||
sp_process_model_row_set_selected (SpProcessModelRow *self,
|
||||
gboolean selected)
|
||||
{
|
||||
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_PROCESS_MODEL_ROW (self));
|
||||
|
||||
selected = !!selected;
|
||||
|
||||
if (selected != sp_process_model_row_get_selected (self))
|
||||
{
|
||||
gtk_widget_set_visible (GTK_WIDGET (priv->check), selected);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTED]);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_process_model_row_query_tooltip (GtkWidget *widget,
|
||||
gint x,
|
||||
gint y,
|
||||
gboolean keyboard_mode,
|
||||
GtkTooltip *tooltip)
|
||||
{
|
||||
SpProcessModelRow *self = (SpProcessModelRow *)widget;
|
||||
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_PROCESS_MODEL_ROW (self));
|
||||
g_assert (GTK_IS_TOOLTIP (tooltip));
|
||||
|
||||
if (priv->item != NULL)
|
||||
{
|
||||
const gchar * const *argv = sp_process_model_item_get_argv (priv->item);
|
||||
|
||||
if (argv != NULL)
|
||||
{
|
||||
g_autofree gchar *str = g_strjoinv (" ", (gchar **)argv);
|
||||
gtk_tooltip_set_text (tooltip, str);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_row_finalize (GObject *object)
|
||||
{
|
||||
SpProcessModelRow *self = (SpProcessModelRow *)object;
|
||||
SpProcessModelRowPrivate *priv = sp_process_model_row_get_instance_private (self);
|
||||
|
||||
g_clear_object (&priv->item);
|
||||
|
||||
G_OBJECT_CLASS (sp_process_model_row_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_row_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpProcessModelRow *self = SP_PROCESS_MODEL_ROW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_ITEM:
|
||||
g_value_set_object (value, sp_process_model_row_get_item (self));
|
||||
break;
|
||||
|
||||
case PROP_SELECTED:
|
||||
g_value_set_boolean (value, sp_process_model_row_get_selected (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_row_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpProcessModelRow *self = SP_PROCESS_MODEL_ROW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_ITEM:
|
||||
sp_process_model_row_set_item (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
case PROP_SELECTED:
|
||||
sp_process_model_row_set_selected (self, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_row_class_init (SpProcessModelRowClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_process_model_row_finalize;
|
||||
object_class->get_property = sp_process_model_row_get_property;
|
||||
object_class->set_property = sp_process_model_row_set_property;
|
||||
|
||||
widget_class->query_tooltip = sp_process_model_row_query_tooltip;
|
||||
|
||||
properties [PROP_ITEM] =
|
||||
g_param_spec_object ("item",
|
||||
"Item",
|
||||
"Item",
|
||||
SP_TYPE_PROCESS_MODEL_ITEM,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_SELECTED] =
|
||||
g_param_spec_boolean ("selected",
|
||||
"Selected",
|
||||
"Selected",
|
||||
FALSE,
|
||||
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class,
|
||||
"/org/gnome/sysprof/ui/sp-process-model-row.ui");
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, args_label);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, image);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, label);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, pid);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProcessModelRow, check);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_row_init (SpProcessModelRow *self)
|
||||
{
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
|
||||
gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE);
|
||||
}
|
||||
50
src/libsysprof-ui/sp-process-model-row.h
Normal file
50
src/libsysprof-ui/sp-process-model-row.h
Normal file
@ -0,0 +1,50 @@
|
||||
/* sp-process-model-row.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_PROCESS_MODEL_ROW_H
|
||||
#define SP_PROCESS_MODEL_ROW_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "sp-process-model-item.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PROCESS_MODEL_ROW (sp_process_model_row_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpProcessModelRow, sp_process_model_row, SP, PROCESS_MODEL_ROW, GtkListBoxRow)
|
||||
|
||||
struct _SpProcessModelRowClass
|
||||
{
|
||||
GtkListBoxRowClass parent;
|
||||
|
||||
gpointer padding[4];
|
||||
};
|
||||
|
||||
GtkWidget *sp_process_model_row_new (SpProcessModelItem *item);
|
||||
SpProcessModelItem *sp_process_model_row_get_item (SpProcessModelRow *self);
|
||||
gboolean sp_process_model_row_get_selected (SpProcessModelRow *self);
|
||||
void sp_process_model_row_set_selected (SpProcessModelRow *self,
|
||||
gboolean selected);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PROCESS_MODEL_ROW_H */
|
||||
|
||||
892
src/libsysprof-ui/sp-profiler-menu-button.c
Normal file
892
src/libsysprof-ui/sp-profiler-menu-button.c
Normal file
@ -0,0 +1,892 @@
|
||||
/* sp-profiler-menu-button.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-model-filter.h"
|
||||
#include "sp-process-model.h"
|
||||
#include "sp-process-model-item.h"
|
||||
#include "sp-process-model-row.h"
|
||||
#include "sp-profiler-menu-button.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpProfiler *profiler;
|
||||
SpModelFilter *process_filter;
|
||||
|
||||
/* Gtk template widgets */
|
||||
GtkTreeModel *environment_model;
|
||||
GtkLabel *label;
|
||||
GtkPopover *popover;
|
||||
GtkEntry *process_filter_entry;
|
||||
GtkListBox *process_list_box;
|
||||
SpProcessModel *process_model;
|
||||
GtkBox *processes_box;
|
||||
GtkEntry *spawn_entry;
|
||||
GtkStack *stack;
|
||||
GtkSwitch *whole_system_switch;
|
||||
GtkTreeView *env_tree_view;
|
||||
GtkTreeViewColumn *env_key_column;
|
||||
GtkTreeViewColumn *env_value_column;
|
||||
GtkCellRenderer *key_cell;
|
||||
GtkCellRenderer *value_cell;
|
||||
GtkCheckButton *inherit_environ;
|
||||
|
||||
/* Property Bindings */
|
||||
GBinding *inherit_binding;
|
||||
GBinding *list_sensitive_binding;
|
||||
GBinding *mutable_binding;
|
||||
GBinding *whole_system_binding;
|
||||
|
||||
/* Signal handlers */
|
||||
gulong notify_whole_system_handler;
|
||||
|
||||
/* GSources */
|
||||
guint save_env_source;
|
||||
} SpProfilerMenuButtonPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (SpProfilerMenuButton, sp_profiler_menu_button, GTK_TYPE_MENU_BUTTON)
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PROFILER,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static void sp_profiler_menu_button_env_row_changed (SpProfilerMenuButton *self,
|
||||
GtkTreePath *tree_path,
|
||||
GtkTreeIter *tree_iter,
|
||||
gpointer user_data);
|
||||
static void sp_profiler_menu_button_validate_spawn (SpProfilerMenuButton *self,
|
||||
GtkEntry *entry);
|
||||
static gboolean save_environ_to_gsettings (gpointer data);
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
GtkWidget *
|
||||
sp_profiler_menu_button_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_PROFILER_MENU_BUTTON, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_update_label (SpProfilerMenuButton *self)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
g_autofree gchar *str = NULL;
|
||||
const gchar *visible_child;
|
||||
const GPid *pids;
|
||||
guint n_pids = 0;
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
|
||||
if (priv->profiler == NULL)
|
||||
{
|
||||
gtk_label_set_label (priv->label, "");
|
||||
return;
|
||||
}
|
||||
|
||||
visible_child = gtk_stack_get_visible_child_name (priv->stack);
|
||||
|
||||
if (g_strcmp0 (visible_child, "spawn") == 0)
|
||||
{
|
||||
const gchar *text;
|
||||
|
||||
text = gtk_entry_get_text (priv->spawn_entry);
|
||||
|
||||
if (text && *text)
|
||||
gtk_label_set_label (priv->label, text);
|
||||
else if (sp_profiler_get_whole_system (priv->profiler))
|
||||
gtk_label_set_label (priv->label, _("All Processes"));
|
||||
else
|
||||
gtk_label_set_label (priv->label, _("New Process"));
|
||||
|
||||
sp_profiler_set_spawn (priv->profiler, text && *text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sp_profiler_set_spawn (priv->profiler, FALSE);
|
||||
|
||||
pids = sp_profiler_get_pids (priv->profiler, &n_pids);
|
||||
|
||||
if (n_pids == 0 || sp_profiler_get_whole_system (priv->profiler))
|
||||
{
|
||||
gtk_label_set_label (priv->label, _("All Processes"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (n_pids == 1)
|
||||
{
|
||||
/* Translators: %d is the PID of the process. */
|
||||
str = g_strdup_printf (_("Process %d"), pids[0]);
|
||||
gtk_label_set_label (priv->label, str);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Translators: %u is the number (amount) of processes. */
|
||||
str = g_strdup_printf (ngettext("%u Process", "%u Processes", n_pids), n_pids);
|
||||
gtk_label_set_label (priv->label, str);
|
||||
}
|
||||
|
||||
static void
|
||||
clear_selected_flags (GtkWidget *widget,
|
||||
gpointer user_data)
|
||||
{
|
||||
sp_process_model_row_set_selected (SP_PROCESS_MODEL_ROW (widget), FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
add_binding (GBinding **binding,
|
||||
gpointer src,
|
||||
const gchar *src_property,
|
||||
gpointer dst,
|
||||
const gchar *dst_property,
|
||||
GBindingFlags flags)
|
||||
{
|
||||
g_assert (binding != NULL);
|
||||
g_assert (*binding == NULL);
|
||||
g_assert (src != NULL);
|
||||
g_assert (src_property != NULL);
|
||||
g_assert (dst != NULL);
|
||||
g_assert (dst_property != NULL);
|
||||
|
||||
*binding = g_object_bind_property (src, src_property,
|
||||
dst, dst_property,
|
||||
flags);
|
||||
g_object_add_weak_pointer (G_OBJECT (*binding), (gpointer *)binding);
|
||||
}
|
||||
|
||||
static void
|
||||
clear_binding (GBinding **binding)
|
||||
{
|
||||
g_assert (binding != NULL);
|
||||
g_assert (!*binding || G_IS_BINDING (*binding));
|
||||
|
||||
if (*binding != NULL)
|
||||
{
|
||||
g_object_remove_weak_pointer (G_OBJECT (*binding), (gpointer *)binding);
|
||||
g_binding_unbind (*binding);
|
||||
*binding = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_connect (SpProfilerMenuButton *self)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_assert (SP_IS_PROFILER (priv->profiler));
|
||||
|
||||
add_binding (&priv->mutable_binding,
|
||||
priv->profiler, "is-mutable",
|
||||
self, "sensitive",
|
||||
G_BINDING_SYNC_CREATE);
|
||||
|
||||
add_binding (&priv->whole_system_binding,
|
||||
priv->profiler, "whole-system",
|
||||
priv->whole_system_switch, "active",
|
||||
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
|
||||
|
||||
add_binding (&priv->list_sensitive_binding,
|
||||
priv->profiler, "whole-system",
|
||||
priv->processes_box, "visible",
|
||||
G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
|
||||
|
||||
add_binding (&priv->inherit_binding,
|
||||
priv->inherit_environ, "active",
|
||||
priv->profiler, "spawn-inherit-environ",
|
||||
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
|
||||
|
||||
priv->notify_whole_system_handler =
|
||||
g_signal_connect_object (priv->profiler,
|
||||
"notify::whole-system",
|
||||
G_CALLBACK (sp_profiler_menu_button_update_label),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
sp_profiler_menu_button_update_label (self);
|
||||
|
||||
sp_profiler_menu_button_validate_spawn (self, priv->spawn_entry);
|
||||
sp_profiler_menu_button_env_row_changed (self, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_disconnect (SpProfilerMenuButton *self)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_assert (SP_IS_PROFILER (priv->profiler));
|
||||
|
||||
clear_binding (&priv->mutable_binding);
|
||||
clear_binding (&priv->whole_system_binding);
|
||||
clear_binding (&priv->list_sensitive_binding);
|
||||
clear_binding (&priv->inherit_binding);
|
||||
|
||||
if (priv->save_env_source != 0)
|
||||
save_environ_to_gsettings (self);
|
||||
|
||||
g_signal_handler_disconnect (priv->profiler, priv->notify_whole_system_handler);
|
||||
priv->notify_whole_system_handler = 0;
|
||||
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
|
||||
|
||||
g_clear_object (&priv->profiler);
|
||||
|
||||
gtk_container_foreach (GTK_CONTAINER (priv->process_list_box),
|
||||
clear_selected_flags,
|
||||
NULL);
|
||||
|
||||
sp_profiler_menu_button_update_label (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_profiler_menu_button_get_profiler:
|
||||
* @self: An #SpProfilerMenuButton
|
||||
*
|
||||
* Gets the profiler instance that is being configured.
|
||||
*
|
||||
* Returns: (nullable) (transfer none): An #SpProfiler or %NULL.
|
||||
*/
|
||||
SpProfiler *
|
||||
sp_profiler_menu_button_get_profiler (SpProfilerMenuButton *self)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_PROFILER_MENU_BUTTON (self), NULL);
|
||||
|
||||
return priv->profiler;
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_menu_button_set_profiler (SpProfilerMenuButton *self,
|
||||
SpProfiler *profiler)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_return_if_fail (!profiler || SP_IS_PROFILER (profiler));
|
||||
|
||||
if (priv->profiler != profiler)
|
||||
{
|
||||
if (priv->profiler != NULL)
|
||||
sp_profiler_menu_button_disconnect (self);
|
||||
|
||||
if (profiler != NULL)
|
||||
{
|
||||
priv->profiler = g_object_ref (profiler);
|
||||
sp_profiler_menu_button_connect (self);
|
||||
}
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROFILER]);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_row_activated (SpProfilerMenuButton *self,
|
||||
SpProcessModelRow *row,
|
||||
GtkListBox *list_box)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
gboolean selected;
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_assert (SP_IS_PROCESS_MODEL_ROW (row));
|
||||
g_assert (GTK_IS_LIST_BOX (list_box));
|
||||
|
||||
selected = !sp_process_model_row_get_selected (row);
|
||||
sp_process_model_row_set_selected (row, selected);
|
||||
|
||||
if (priv->profiler != NULL)
|
||||
{
|
||||
SpProcessModelItem *item = sp_process_model_row_get_item (row);
|
||||
GPid pid = sp_process_model_item_get_pid (item);
|
||||
|
||||
if (selected)
|
||||
sp_profiler_add_pid (priv->profiler, pid);
|
||||
else
|
||||
sp_profiler_remove_pid (priv->profiler, pid);
|
||||
}
|
||||
|
||||
sp_profiler_menu_button_update_label (self);
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
sp_profiler_menu_button_create_row (gpointer itemptr,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpProcessModelItem *item = itemptr;
|
||||
|
||||
g_assert (SP_IS_PROCESS_MODEL_ITEM (item));
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (user_data));
|
||||
|
||||
return g_object_new (SP_TYPE_PROCESS_MODEL_ROW,
|
||||
"item", item,
|
||||
"visible", TRUE,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_clicked (GtkButton *button)
|
||||
{
|
||||
SpProfilerMenuButton *self = (SpProfilerMenuButton *)button;
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
|
||||
sp_process_model_queue_reload (priv->process_model);
|
||||
|
||||
GTK_BUTTON_CLASS (sp_profiler_menu_button_parent_class)->clicked (button);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_profiler_menu_button_filter_func (GObject *object,
|
||||
gpointer user_data)
|
||||
{
|
||||
const gchar *needle = user_data;
|
||||
const gchar *haystack;
|
||||
|
||||
g_assert (SP_IS_PROCESS_MODEL_ITEM (object));
|
||||
|
||||
if (needle == NULL)
|
||||
return TRUE;
|
||||
|
||||
haystack = sp_process_model_item_get_command_line (SP_PROCESS_MODEL_ITEM (object));
|
||||
|
||||
if (haystack == NULL)
|
||||
return FALSE;
|
||||
|
||||
return strstr (haystack, needle) != NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_filter_changed (SpProfilerMenuButton *self,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
const gchar *text;
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_assert (GTK_IS_ENTRY (entry));
|
||||
|
||||
text = gtk_entry_get_text (entry);
|
||||
if (text && *text == 0)
|
||||
text = NULL;
|
||||
|
||||
sp_model_filter_set_filter_func (priv->process_filter,
|
||||
sp_profiler_menu_button_filter_func,
|
||||
g_strdup (text),
|
||||
g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_constructed (GObject *object)
|
||||
{
|
||||
SpProfilerMenuButton *self = (SpProfilerMenuButton *)object;
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
|
||||
priv->process_filter = sp_model_filter_new (G_LIST_MODEL (priv->process_model));
|
||||
|
||||
gtk_list_box_bind_model (priv->process_list_box,
|
||||
G_LIST_MODEL (priv->process_filter),
|
||||
sp_profiler_menu_button_create_row,
|
||||
self, NULL);
|
||||
|
||||
G_OBJECT_CLASS (sp_profiler_menu_button_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_realize (GtkWidget *widget)
|
||||
{
|
||||
SpProfilerMenuButton *self = (SpProfilerMenuButton *)widget;
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
g_autoptr(GSettings) settings = NULL;
|
||||
g_auto(GStrv) env = NULL;
|
||||
|
||||
GTK_WIDGET_CLASS (sp_profiler_menu_button_parent_class)->realize (widget);
|
||||
|
||||
settings = g_settings_new ("org.gnome.sysprof2");
|
||||
|
||||
env = g_settings_get_strv (settings, "last-spawn-env");
|
||||
|
||||
g_settings_bind (settings, "last-spawn-argv",
|
||||
priv->spawn_entry, "text",
|
||||
G_SETTINGS_BIND_DEFAULT);
|
||||
g_settings_bind (settings, "last-spawn-inherit-env",
|
||||
priv->inherit_environ, "active",
|
||||
G_SETTINGS_BIND_DEFAULT);
|
||||
|
||||
if (env)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
guint i;
|
||||
|
||||
model = gtk_tree_view_get_model (priv->env_tree_view);
|
||||
gtk_list_store_clear (GTK_LIST_STORE (model));
|
||||
|
||||
for (i = 0; env [i]; i++)
|
||||
{
|
||||
const gchar *key = env [i];
|
||||
const gchar *value = NULL;
|
||||
gchar *eq = strchr (env[i], '=');
|
||||
|
||||
if (eq)
|
||||
{
|
||||
*eq = '\0';
|
||||
value = eq + 1;
|
||||
}
|
||||
|
||||
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
|
||||
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
||||
0, key,
|
||||
1, value,
|
||||
-1);
|
||||
}
|
||||
|
||||
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
save_environ_to_gsettings (gpointer data)
|
||||
{
|
||||
SpProfilerMenuButton *self = data;
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
g_autoptr(GPtrArray) ar = NULL;
|
||||
g_autoptr(GSettings) settings = NULL;
|
||||
GtkTreeIter iter;
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
|
||||
priv->save_env_source = 0;
|
||||
|
||||
if (priv->environment_model == NULL)
|
||||
return G_SOURCE_REMOVE;
|
||||
|
||||
settings = g_settings_new ("org.gnome.sysprof2");
|
||||
|
||||
ar = g_ptr_array_new_with_free_func (g_free);
|
||||
|
||||
if (gtk_tree_model_get_iter_first (priv->environment_model, &iter))
|
||||
{
|
||||
do
|
||||
{
|
||||
g_autofree gchar *key = NULL;
|
||||
g_autofree gchar *value = NULL;
|
||||
|
||||
gtk_tree_model_get (priv->environment_model, &iter,
|
||||
0, &key,
|
||||
1, &value,
|
||||
-1);
|
||||
|
||||
if (!key || !*key)
|
||||
continue;
|
||||
|
||||
g_ptr_array_add (ar, g_strdup_printf ("%s=%s", key, value ? value : ""));
|
||||
}
|
||||
while (gtk_tree_model_iter_next (priv->environment_model, &iter));
|
||||
}
|
||||
|
||||
g_ptr_array_add (ar, NULL);
|
||||
|
||||
g_settings_set_strv (settings, "last-spawn-env", (const gchar * const *)ar->pdata);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_destroy (GtkWidget *widget)
|
||||
{
|
||||
SpProfilerMenuButton *self = (SpProfilerMenuButton *)widget;
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
if (priv->profiler != NULL)
|
||||
{
|
||||
sp_profiler_menu_button_disconnect (self);
|
||||
g_clear_object (&priv->profiler);
|
||||
}
|
||||
|
||||
g_clear_object (&priv->process_filter);
|
||||
|
||||
if (priv->save_env_source)
|
||||
{
|
||||
g_source_remove (priv->save_env_source);
|
||||
priv->save_env_source = 0;
|
||||
}
|
||||
|
||||
GTK_WIDGET_CLASS (sp_profiler_menu_button_parent_class)->destroy (widget);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpProfilerMenuButton *self = SP_PROFILER_MENU_BUTTON (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PROFILER:
|
||||
g_value_set_object (value, sp_profiler_menu_button_get_profiler (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpProfilerMenuButton *self = SP_PROFILER_MENU_BUTTON (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PROFILER:
|
||||
sp_profiler_menu_button_set_profiler (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_class_init (SpProfilerMenuButtonClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
|
||||
|
||||
object_class->constructed = sp_profiler_menu_button_constructed;
|
||||
object_class->get_property = sp_profiler_menu_button_get_property;
|
||||
object_class->set_property = sp_profiler_menu_button_set_property;
|
||||
|
||||
widget_class->destroy = sp_profiler_menu_button_destroy;
|
||||
widget_class->realize = sp_profiler_menu_button_realize;
|
||||
|
||||
button_class->clicked = sp_profiler_menu_button_clicked;
|
||||
|
||||
properties [PROP_PROFILER] =
|
||||
g_param_spec_object ("profiler",
|
||||
"Profiler",
|
||||
"Profiler",
|
||||
SP_TYPE_PROFILER,
|
||||
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sp-profiler-menu-button.ui");
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, env_key_column);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, env_tree_view);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, env_value_column);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, environment_model);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, inherit_environ);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, key_cell);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, label);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, popover);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, process_filter_entry);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, process_list_box);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, process_model);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, processes_box);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, spawn_entry);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, stack);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, value_cell);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpProfilerMenuButton, whole_system_switch);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_env_row_changed (SpProfilerMenuButton *self,
|
||||
GtkTreePath *tree_path,
|
||||
GtkTreeIter *tree_iter,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
g_autoptr(GPtrArray) env = NULL;
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_assert (GTK_IS_TREE_MODEL (priv->environment_model));
|
||||
|
||||
/* queue saving settings to gsettings */
|
||||
if (priv->save_env_source)
|
||||
g_source_remove (priv->save_env_source);
|
||||
priv->save_env_source = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
|
||||
1,
|
||||
save_environ_to_gsettings,
|
||||
g_object_ref (self),
|
||||
g_object_unref);
|
||||
|
||||
if (priv->profiler == NULL)
|
||||
return;
|
||||
|
||||
/* sync the environ to the profiler */
|
||||
env = g_ptr_array_new_with_free_func (g_free);
|
||||
model = priv->environment_model;
|
||||
|
||||
if (gtk_tree_model_get_iter_first (model, &iter))
|
||||
{
|
||||
do
|
||||
{
|
||||
g_autofree gchar *key = NULL;
|
||||
g_autofree gchar *value = NULL;
|
||||
|
||||
gtk_tree_model_get (model, &iter,
|
||||
0, &key,
|
||||
1, &value,
|
||||
-1);
|
||||
|
||||
if (key && *key)
|
||||
g_ptr_array_add (env, g_strdup_printf ("%s=%s", key, value));
|
||||
}
|
||||
while (gtk_tree_model_iter_next (model, &iter));
|
||||
}
|
||||
g_ptr_array_add (env, NULL);
|
||||
sp_profiler_set_spawn_env (priv->profiler, (const gchar * const *)env->pdata);
|
||||
}
|
||||
|
||||
static void
|
||||
on_backspace (SpProfilerMenuButton *self,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
if (g_object_get_data (G_OBJECT (entry), "CELL_WAS_EMPTY"))
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreeSelection *selection;
|
||||
GtkTreeIter iter;
|
||||
|
||||
model = gtk_tree_view_get_model (priv->env_tree_view);
|
||||
selection = gtk_tree_view_get_selection (priv->env_tree_view);
|
||||
|
||||
if (gtk_tree_selection_get_selected (selection, NULL, &iter))
|
||||
{
|
||||
gtk_cell_renderer_stop_editing (priv->key_cell, TRUE);
|
||||
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
|
||||
}
|
||||
}
|
||||
else
|
||||
g_object_set_data (G_OBJECT (entry), "CELL_WAS_EMPTY",
|
||||
GINT_TO_POINTER (*gtk_entry_get_text (entry) == '\0'));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_env_key_editing_started (SpProfilerMenuButton *self,
|
||||
GtkCellEditable *editable,
|
||||
const gchar *path,
|
||||
GtkCellRenderer *cell)
|
||||
{
|
||||
g_signal_connect_object (editable,
|
||||
"backspace",
|
||||
G_CALLBACK (on_backspace),
|
||||
self,
|
||||
G_CONNECT_AFTER | G_CONNECT_SWAPPED);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_env_key_edited (SpProfilerMenuButton *self,
|
||||
const gchar *path,
|
||||
const gchar *new_text,
|
||||
GtkCellRendererText *cell)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
GtkTreeModel *model;
|
||||
GtkTreePath *tree_path;
|
||||
GtkTreeIter iter;
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_assert (path != NULL);
|
||||
g_assert (new_text != NULL);
|
||||
g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
|
||||
|
||||
model = gtk_tree_view_get_model (priv->env_tree_view);
|
||||
|
||||
tree_path = gtk_tree_path_new_from_string (path);
|
||||
|
||||
if (gtk_tree_model_get_iter (model, &iter, tree_path))
|
||||
{
|
||||
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
||||
0, new_text,
|
||||
-1);
|
||||
|
||||
if (!gtk_tree_model_iter_next (model, &iter))
|
||||
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
|
||||
|
||||
gtk_tree_view_set_cursor_on_cell (priv->env_tree_view,
|
||||
tree_path,
|
||||
priv->env_value_column,
|
||||
priv->value_cell,
|
||||
TRUE);
|
||||
}
|
||||
|
||||
gtk_tree_path_free (tree_path);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_env_value_edited (SpProfilerMenuButton *self,
|
||||
const gchar *path,
|
||||
const gchar *new_text,
|
||||
GtkCellRendererText *cell)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
GtkTreeModel *model;
|
||||
GtkTreePath *tree_path;
|
||||
GtkTreeIter iter;
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_assert (path != NULL);
|
||||
g_assert (new_text != NULL);
|
||||
g_assert (GTK_IS_CELL_RENDERER_TEXT (cell));
|
||||
|
||||
model = gtk_tree_view_get_model (priv->env_tree_view);
|
||||
|
||||
tree_path = gtk_tree_path_new_from_string (path);
|
||||
|
||||
if (gtk_tree_model_get_iter (model, &iter, tree_path))
|
||||
{
|
||||
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
||||
1, new_text,
|
||||
-1);
|
||||
|
||||
if (!gtk_tree_model_iter_next (model, &iter))
|
||||
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
|
||||
|
||||
gtk_tree_path_next (tree_path);
|
||||
|
||||
gtk_tree_view_set_cursor_on_cell (priv->env_tree_view,
|
||||
tree_path,
|
||||
priv->env_key_column,
|
||||
priv->key_cell,
|
||||
TRUE);
|
||||
}
|
||||
|
||||
gtk_tree_path_free (tree_path);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_validate_spawn (SpProfilerMenuButton *self,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
g_auto(GStrv) argv = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
const gchar *text;
|
||||
gint argc;
|
||||
|
||||
g_assert (SP_IS_PROFILER_MENU_BUTTON (self));
|
||||
g_assert (GTK_IS_ENTRY (entry));
|
||||
|
||||
text = gtk_entry_get_text (entry);
|
||||
|
||||
if (text && *text && !g_shell_parse_argv (text, &argc, &argv, &error))
|
||||
{
|
||||
sp_profiler_set_spawn_argv (priv->profiler, NULL);
|
||||
g_object_set (entry,
|
||||
"secondary-icon-name", "dialog-warning-symbolic",
|
||||
"secondary-icon-tooltip-text", _("The command line arguments provided are invalid"),
|
||||
NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
sp_profiler_set_spawn_argv (priv->profiler, (const gchar * const *)argv);
|
||||
g_object_set (entry,
|
||||
"secondary-icon-name", NULL,
|
||||
"secondary-icon-tooltip-text", NULL,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profiler_menu_button_init (SpProfilerMenuButton *self)
|
||||
{
|
||||
SpProfilerMenuButtonPrivate *priv = sp_profiler_menu_button_get_instance_private (self);
|
||||
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
|
||||
g_signal_connect_object (priv->process_filter_entry,
|
||||
"changed",
|
||||
G_CALLBACK (sp_profiler_menu_button_filter_changed),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->spawn_entry,
|
||||
"changed",
|
||||
G_CALLBACK (sp_profiler_menu_button_update_label),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->spawn_entry,
|
||||
"changed",
|
||||
G_CALLBACK (sp_profiler_menu_button_validate_spawn),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->stack,
|
||||
"notify::visible-child",
|
||||
G_CALLBACK (sp_profiler_menu_button_update_label),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->process_list_box,
|
||||
"row-activated",
|
||||
G_CALLBACK (sp_profiler_menu_button_row_activated),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->key_cell,
|
||||
"edited",
|
||||
G_CALLBACK (sp_profiler_menu_button_env_key_edited),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->value_cell,
|
||||
"edited",
|
||||
G_CALLBACK (sp_profiler_menu_button_env_value_edited),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (gtk_tree_view_get_model (priv->env_tree_view),
|
||||
"row-changed",
|
||||
G_CALLBACK (sp_profiler_menu_button_env_row_changed),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->key_cell,
|
||||
"editing-started",
|
||||
G_CALLBACK (sp_profiler_menu_button_env_key_editing_started),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
|
||||
}
|
||||
48
src/libsysprof-ui/sp-profiler-menu-button.h
Normal file
48
src/libsysprof-ui/sp-profiler-menu-button.h
Normal file
@ -0,0 +1,48 @@
|
||||
/* sp-profiler-menu-button.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_PROFILER_MENU_BUTTON_H
|
||||
#define SP_PROFILER_MENU_BUTTON_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "sp-profiler.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PROFILER_MENU_BUTTON (sp_profiler_menu_button_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpProfilerMenuButton, sp_profiler_menu_button, SP, PROFILER_MENU_BUTTON, GtkMenuButton)
|
||||
|
||||
struct _SpProfilerMenuButtonClass
|
||||
{
|
||||
GtkMenuButtonClass parent_class;
|
||||
|
||||
gpointer padding[8];
|
||||
};
|
||||
|
||||
GtkWidget *sp_profiler_menu_button_new (void);
|
||||
void sp_profiler_menu_button_set_profiler (SpProfilerMenuButton *self,
|
||||
SpProfiler *profiler);
|
||||
SpProfiler *sp_profiler_menu_button_get_profiler (SpProfilerMenuButton *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PROFILER_MENU_BUTTON_H */
|
||||
195
src/libsysprof-ui/sp-recording-state-view.c
Normal file
195
src/libsysprof-ui/sp-recording-state-view.c
Normal file
@ -0,0 +1,195 @@
|
||||
/* sp-recording-state-view.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "sp-recording-state-view.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpProfiler *profiler;
|
||||
gulong notify_elapsed_handler;
|
||||
GtkLabel *elapsed;
|
||||
} SpRecordingStateViewPrivate;
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (SpRecordingStateView, sp_recording_state_view, GTK_TYPE_BIN)
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PROFILER,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
GtkWidget *
|
||||
sp_recording_state_view_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_RECORDING_STATE_VIEW, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_recording_state_view_notify_elapsed (SpRecordingStateView *self,
|
||||
GParamSpec *pspec,
|
||||
SpProfiler *profiler)
|
||||
{
|
||||
SpRecordingStateViewPrivate *priv = sp_recording_state_view_get_instance_private (self);
|
||||
g_autofree gchar *str = NULL;
|
||||
gint64 elapsed;
|
||||
guint hours;
|
||||
guint minutes;
|
||||
guint seconds;
|
||||
|
||||
g_assert (SP_IS_RECORDING_STATE_VIEW (self));
|
||||
g_assert (SP_IS_PROFILER (profiler));
|
||||
|
||||
elapsed = (gint64)sp_profiler_get_elapsed (profiler);
|
||||
|
||||
hours = elapsed / (60 * 60);
|
||||
if (hours > 0)
|
||||
minutes = (elapsed % (hours * 60 * 60)) / 60;
|
||||
else
|
||||
minutes = elapsed / 60;
|
||||
seconds = elapsed % 60;
|
||||
|
||||
if (hours == 0)
|
||||
str = g_strdup_printf ("%02u:%02u", minutes, seconds);
|
||||
else
|
||||
str = g_strdup_printf ("%02u:%02u:%02u", hours, minutes, seconds);
|
||||
|
||||
gtk_label_set_label (priv->elapsed, str);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_recording_state_view_destroy (GtkWidget *widget)
|
||||
{
|
||||
SpRecordingStateView *self = (SpRecordingStateView *)widget;
|
||||
SpRecordingStateViewPrivate *priv = sp_recording_state_view_get_instance_private (self);
|
||||
|
||||
if (priv->profiler != NULL)
|
||||
{
|
||||
g_signal_handler_disconnect (priv->profiler, priv->notify_elapsed_handler);
|
||||
g_clear_object (&priv->profiler);
|
||||
}
|
||||
|
||||
GTK_WIDGET_CLASS (sp_recording_state_view_parent_class)->destroy (widget);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_recording_state_view_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpRecordingStateView *self = SP_RECORDING_STATE_VIEW (object);
|
||||
SpRecordingStateViewPrivate *priv = sp_recording_state_view_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PROFILER:
|
||||
g_value_set_object (value, priv->profiler);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_recording_state_view_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpRecordingStateView *self = SP_RECORDING_STATE_VIEW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PROFILER:
|
||||
sp_recording_state_view_set_profiler (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_recording_state_view_class_init (SpRecordingStateViewClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->get_property = sp_recording_state_view_get_property;
|
||||
object_class->set_property = sp_recording_state_view_set_property;
|
||||
|
||||
widget_class->destroy = sp_recording_state_view_destroy;
|
||||
|
||||
properties [PROP_PROFILER] =
|
||||
g_param_spec_object ("profiler",
|
||||
"Profiler",
|
||||
"Profiler",
|
||||
SP_TYPE_PROFILER,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class,
|
||||
"/org/gnome/sysprof/ui/sp-recording-state-view.ui");
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpRecordingStateView, elapsed);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_recording_state_view_init (SpRecordingStateView *self)
|
||||
{
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
void
|
||||
sp_recording_state_view_set_profiler (SpRecordingStateView *self,
|
||||
SpProfiler *profiler)
|
||||
{
|
||||
SpRecordingStateViewPrivate *priv = sp_recording_state_view_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_RECORDING_STATE_VIEW (self));
|
||||
g_assert (!profiler || SP_IS_PROFILER (profiler));
|
||||
|
||||
gtk_label_set_label (priv->elapsed, "00:00");
|
||||
|
||||
if (profiler != priv->profiler)
|
||||
{
|
||||
if (priv->profiler != NULL)
|
||||
{
|
||||
g_signal_handler_disconnect (priv->profiler, priv->notify_elapsed_handler);
|
||||
g_clear_object (&priv->profiler);
|
||||
}
|
||||
|
||||
gtk_label_set_label (priv->elapsed, "00:00");
|
||||
|
||||
if (profiler != NULL)
|
||||
{
|
||||
priv->profiler = g_object_ref (profiler);
|
||||
priv->notify_elapsed_handler =
|
||||
g_signal_connect_object (profiler,
|
||||
"notify::elapsed",
|
||||
G_CALLBACK (sp_recording_state_view_notify_elapsed),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/libsysprof-ui/sp-recording-state-view.h
Normal file
47
src/libsysprof-ui/sp-recording-state-view.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* sp-recording-state-view.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_RECORDING_STATE_VIEW_H
|
||||
#define SP_RECORDING_STATE_VIEW_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "sp-profiler.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_RECORDING_STATE_VIEW (sp_recording_state_view_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpRecordingStateView, sp_recording_state_view, SP, RECORDING_STATE_VIEW, GtkBin)
|
||||
|
||||
struct _SpRecordingStateViewClass
|
||||
{
|
||||
GtkBinClass parent;
|
||||
|
||||
gpointer padding[4];
|
||||
};
|
||||
|
||||
GtkWidget *sp_recording_state_view_new (void);
|
||||
void sp_recording_state_view_set_profiler (SpRecordingStateView *self,
|
||||
SpProfiler *profiler);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_RECORDING_STATE_VIEW_H */
|
||||
266
src/libsysprof-ui/sp-theme-manager.c
Normal file
266
src/libsysprof-ui/sp-theme-manager.c
Normal file
@ -0,0 +1,266 @@
|
||||
/* sp-theme-manager.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-theme-manager"
|
||||
|
||||
#include "sp-theme-manager.h"
|
||||
|
||||
struct _SpThemeManager
|
||||
{
|
||||
GObject parent_instance;
|
||||
GHashTable *theme_resources;
|
||||
guint reload_source;
|
||||
guint registered_signals : 1;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint id;
|
||||
gchar *key;
|
||||
gchar *theme_name;
|
||||
gchar *variant;
|
||||
gchar *resource;
|
||||
GtkCssProvider *provider;
|
||||
} ThemeResource;
|
||||
|
||||
G_DEFINE_TYPE (SpThemeManager, sp_theme_manager, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
theme_resource_free (gpointer data)
|
||||
{
|
||||
ThemeResource *theme_resource = data;
|
||||
|
||||
if (theme_resource != NULL)
|
||||
{
|
||||
g_clear_pointer (&theme_resource->key, g_free);
|
||||
g_clear_pointer (&theme_resource->theme_name, g_free);
|
||||
g_clear_pointer (&theme_resource->variant, g_free);
|
||||
g_clear_pointer (&theme_resource->resource, g_free);
|
||||
|
||||
if (theme_resource->provider != NULL)
|
||||
{
|
||||
gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
|
||||
GTK_STYLE_PROVIDER (theme_resource->provider));
|
||||
g_clear_object (&theme_resource->provider);
|
||||
}
|
||||
|
||||
g_slice_free (ThemeResource, theme_resource);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
theme_resource_matches (ThemeResource *theme_resource,
|
||||
GtkSettings *settings)
|
||||
{
|
||||
g_autofree gchar *theme_name = NULL;
|
||||
gboolean dark_theme = FALSE;
|
||||
|
||||
g_assert (theme_resource != NULL);
|
||||
g_assert (GTK_IS_SETTINGS (settings));
|
||||
|
||||
if (theme_resource->theme_name == NULL)
|
||||
return TRUE;
|
||||
|
||||
g_object_get (settings,
|
||||
"gtk-theme-name", &theme_name,
|
||||
"gtk-application-prefer-dark-theme", &dark_theme,
|
||||
NULL);
|
||||
|
||||
if (g_strcmp0 (theme_name, theme_resource->theme_name) == 0)
|
||||
{
|
||||
if (dark_theme && g_strcmp0 ("dark", theme_resource->variant) == 0)
|
||||
return TRUE;
|
||||
|
||||
if (!dark_theme && (!theme_resource->variant || g_strcmp0 ("light", theme_resource->variant) == 0))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_theme_manager_do_reload (gpointer data)
|
||||
{
|
||||
SpThemeManager *self = data;
|
||||
ThemeResource *theme_resource;
|
||||
GHashTableIter iter;
|
||||
GtkSettings *settings;
|
||||
|
||||
g_assert (SP_IS_THEME_MANAGER (self));
|
||||
|
||||
self->reload_source = 0;
|
||||
|
||||
settings = gtk_settings_get_default ();
|
||||
|
||||
g_hash_table_iter_init (&iter, self->theme_resources);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&theme_resource))
|
||||
{
|
||||
if (theme_resource_matches (theme_resource, settings))
|
||||
{
|
||||
if (theme_resource->provider == NULL)
|
||||
{
|
||||
theme_resource->provider = gtk_css_provider_new ();
|
||||
gtk_css_provider_load_from_resource (theme_resource->provider, theme_resource->resource);
|
||||
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
|
||||
GTK_STYLE_PROVIDER (theme_resource->provider),
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (theme_resource->provider != NULL)
|
||||
{
|
||||
gtk_style_context_remove_provider_for_screen (gdk_screen_get_default (),
|
||||
GTK_STYLE_PROVIDER (theme_resource->provider));
|
||||
g_clear_object (&theme_resource->provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_theme_manager_queue_reload (SpThemeManager *self)
|
||||
{
|
||||
g_assert (SP_IS_THEME_MANAGER (self));
|
||||
|
||||
if (self->reload_source == 0)
|
||||
self->reload_source = gdk_threads_add_idle_full (G_PRIORITY_LOW,
|
||||
sp_theme_manager_do_reload,
|
||||
self,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_theme_manager_finalize (GObject *object)
|
||||
{
|
||||
SpThemeManager *self = (SpThemeManager *)object;
|
||||
|
||||
if (self->reload_source != 0)
|
||||
{
|
||||
g_source_remove (self->reload_source);
|
||||
self->reload_source = 0;
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->theme_resources, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_theme_manager_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_theme_manager_class_init (SpThemeManagerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_theme_manager_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_theme_manager_init (SpThemeManager *self)
|
||||
{
|
||||
self->theme_resources = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, theme_resource_free);
|
||||
|
||||
gtk_icon_theme_add_resource_path (gtk_icon_theme_get_default (), "/org/gnome/sysprof/icons");
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_theme_manager_get_default:
|
||||
*
|
||||
* Returns: (transfer none): An #SpThemeManager
|
||||
*/
|
||||
SpThemeManager *
|
||||
sp_theme_manager_get_default (void)
|
||||
{
|
||||
static SpThemeManager *instance;
|
||||
|
||||
if (instance == NULL)
|
||||
instance = g_object_new (SP_TYPE_THEME_MANAGER, NULL);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
guint
|
||||
sp_theme_manager_register_resource (SpThemeManager *self,
|
||||
const gchar *theme_name,
|
||||
const gchar *variant,
|
||||
const gchar *resource)
|
||||
{
|
||||
ThemeResource *theme_resource;
|
||||
static guint counter;
|
||||
guint id;
|
||||
|
||||
g_return_val_if_fail (SP_IS_THEME_MANAGER (self), 0);
|
||||
|
||||
theme_resource = g_slice_new0 (ThemeResource);
|
||||
theme_resource->id = id = ++counter;
|
||||
theme_resource->key = g_strdup_printf ("%s-%s-%d",
|
||||
theme_name ? theme_name : "shared",
|
||||
variant ? variant : "light",
|
||||
theme_resource->id);
|
||||
theme_resource->theme_name = g_strdup (theme_name);
|
||||
theme_resource->variant = g_strdup (variant);
|
||||
theme_resource->resource = g_strdup (resource);
|
||||
theme_resource->provider = NULL;
|
||||
|
||||
g_hash_table_insert (self->theme_resources, theme_resource->key, theme_resource);
|
||||
|
||||
if (!self->registered_signals)
|
||||
{
|
||||
self->registered_signals = TRUE;
|
||||
g_signal_connect_object (gtk_settings_get_default (),
|
||||
"notify::gtk-application-prefer-dark-theme",
|
||||
G_CALLBACK (sp_theme_manager_queue_reload),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
g_signal_connect_object (gtk_settings_get_default (),
|
||||
"notify::gtk-theme-name",
|
||||
G_CALLBACK (sp_theme_manager_queue_reload),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
}
|
||||
|
||||
sp_theme_manager_queue_reload (self);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void
|
||||
sp_theme_manager_unregister (SpThemeManager *self,
|
||||
guint registration_id)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
ThemeResource *theme_resource;
|
||||
|
||||
g_return_if_fail (SP_IS_THEME_MANAGER (self));
|
||||
|
||||
g_hash_table_iter_init (&iter, self->theme_resources);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&theme_resource))
|
||||
{
|
||||
if (theme_resource->id == registration_id)
|
||||
{
|
||||
/* Provider is unregistered during destroy */
|
||||
g_hash_table_iter_remove (&iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/libsysprof-ui/sp-theme-manager.h
Normal file
42
src/libsysprof-ui/sp-theme-manager.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* sp-theme-manager.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_THEME_MANAGER_H
|
||||
#define SP_THEME_MANAGER_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_THEME_MANAGER (sp_theme_manager_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpThemeManager, sp_theme_manager, SP, THEME_MANAGER, GObject)
|
||||
|
||||
SpThemeManager *sp_theme_manager_get_default (void);
|
||||
void sp_theme_manager_unregister (SpThemeManager *self,
|
||||
guint registration_id);
|
||||
guint sp_theme_manager_register_resource (SpThemeManager *self,
|
||||
const gchar *theme_name,
|
||||
const gchar *variant,
|
||||
const gchar *resource);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_THEME_MANAGER_H */
|
||||
453
src/libsysprof-ui/sp-visualizer-list.c
Normal file
453
src/libsysprof-ui/sp-visualizer-list.c
Normal file
@ -0,0 +1,453 @@
|
||||
/* sp-visualizer-list.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-visualizer-list"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <sysprof.h>
|
||||
|
||||
#include "sp-cpu-visualizer-row.h"
|
||||
#include "sp-visualizer-list.h"
|
||||
#include "sp-visualizer-row.h"
|
||||
#include "sp-mark-visualizer-row.h"
|
||||
#include "sp-zoom-manager.h"
|
||||
|
||||
#define NSEC_PER_SEC G_GUINT64_CONSTANT(1000000000)
|
||||
#define DEFAULT_PIXELS_PER_SECOND 20
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureReader *reader;
|
||||
SpZoomManager *zoom_manager;
|
||||
gint64 begin_time;
|
||||
gint64 end_time;
|
||||
} SpVisualizerListPrivate;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureCursor *cursor;
|
||||
GHashTable *mark_groups;
|
||||
guint fps_counter;
|
||||
GArray *memory;
|
||||
guint has_cpu : 1;
|
||||
} Discovery;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_READER,
|
||||
PROP_ZOOM_MANAGER,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (SpVisualizerList, sp_visualizer_list, GTK_TYPE_LIST_BOX)
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
static void
|
||||
discovery_free (Discovery *state)
|
||||
{
|
||||
g_clear_pointer (&state->mark_groups, g_hash_table_unref);
|
||||
g_clear_pointer (&state->memory, g_array_unref);
|
||||
g_clear_pointer (&state->cursor, sp_capture_cursor_unref);
|
||||
g_slice_free (Discovery, state);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_list_add (GtkContainer *container,
|
||||
GtkWidget *widget)
|
||||
{
|
||||
SpVisualizerList *self = (SpVisualizerList *)container;
|
||||
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
|
||||
|
||||
GTK_CONTAINER_CLASS (sp_visualizer_list_parent_class)->add (container, widget);
|
||||
|
||||
if (SP_IS_VISUALIZER_ROW (widget))
|
||||
{
|
||||
sp_visualizer_row_set_reader (SP_VISUALIZER_ROW (widget), priv->reader);
|
||||
sp_visualizer_row_set_zoom_manager (SP_VISUALIZER_ROW (widget), priv->zoom_manager);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_list_finalize (GObject *object)
|
||||
{
|
||||
SpVisualizerList *self = (SpVisualizerList *)object;
|
||||
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_visualizer_list_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_list_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpVisualizerList *self = SP_VISUALIZER_LIST (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_READER:
|
||||
g_value_set_boxed (value, sp_visualizer_list_get_reader (self));
|
||||
break;
|
||||
|
||||
case PROP_ZOOM_MANAGER:
|
||||
g_value_set_object (value, sp_visualizer_list_get_zoom_manager (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_list_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpVisualizerList *self = SP_VISUALIZER_LIST (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_READER:
|
||||
sp_visualizer_list_set_reader (self, g_value_get_boxed (value));
|
||||
break;
|
||||
|
||||
case PROP_ZOOM_MANAGER:
|
||||
sp_visualizer_list_set_zoom_manager (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_list_class_init (SpVisualizerListClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_visualizer_list_finalize;
|
||||
object_class->get_property = sp_visualizer_list_get_property;
|
||||
object_class->set_property = sp_visualizer_list_set_property;
|
||||
|
||||
container_class->add = sp_visualizer_list_add;
|
||||
|
||||
properties [PROP_READER] =
|
||||
g_param_spec_boxed ("reader",
|
||||
"Reader",
|
||||
"The capture reader",
|
||||
SP_TYPE_CAPTURE_READER,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_ZOOM_MANAGER] =
|
||||
g_param_spec_object ("zoom-manager",
|
||||
"Zoom Manager",
|
||||
"The zoom manager",
|
||||
SP_TYPE_ZOOM_MANAGER,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_list_init (SpVisualizerList *self)
|
||||
{
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
sp_visualizer_list_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_VISUALIZER_ROW, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_visualizer_list_get_reader:
|
||||
*
|
||||
* Gets the reader that is being used for the #SpVisualizerList.
|
||||
*
|
||||
* Returns: (transfer none): An #SpCaptureReader
|
||||
*/
|
||||
SpCaptureReader *
|
||||
sp_visualizer_list_get_reader (SpVisualizerList *self)
|
||||
{
|
||||
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_VISUALIZER_LIST (self), NULL);
|
||||
|
||||
return priv->reader;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
discover_new_rows_frame_cb (const SpCaptureFrame *frame,
|
||||
gpointer user_data)
|
||||
{
|
||||
Discovery *state = user_data;
|
||||
|
||||
g_assert (frame != NULL);
|
||||
g_assert (state != NULL);
|
||||
|
||||
/*
|
||||
* NOTE:
|
||||
*
|
||||
* It would be nice if we could redesign this all around the concept of
|
||||
* an "gadget" or something which combines a data collection series
|
||||
* and widget views to be displayed.
|
||||
*/
|
||||
|
||||
if (frame->type == SP_CAPTURE_FRAME_MARK)
|
||||
{
|
||||
const SpCaptureMark *mark = (const SpCaptureMark *)frame;
|
||||
|
||||
if (!g_hash_table_contains (state->mark_groups, mark->group))
|
||||
g_hash_table_add (state->mark_groups, g_strdup (mark->group));
|
||||
}
|
||||
|
||||
if (frame->type == SP_CAPTURE_FRAME_CTRDEF)
|
||||
{
|
||||
const SpCaptureFrameCounterDefine *def = (const SpCaptureFrameCounterDefine *)frame;
|
||||
|
||||
for (guint i = 0; i < def->n_counters; i++)
|
||||
{
|
||||
const SpCaptureCounter *ctr = &def->counters[i];
|
||||
|
||||
if (!state->has_cpu &&
|
||||
strstr (ctr->category, "CPU Percent") != NULL)
|
||||
state->has_cpu = TRUE;
|
||||
else if (!state->fps_counter &&
|
||||
strstr (ctr->category, "gtk") != NULL && strstr (ctr->name, "fps") != NULL)
|
||||
state->fps_counter = ctr->id;
|
||||
else if (strcmp ("Memory", ctr->category) == 0 &&
|
||||
strcmp ("Used", ctr->name) == 0)
|
||||
{
|
||||
guint counter_id = ctr->id;
|
||||
g_array_append_val (state->memory, counter_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
discover_new_rows_worker (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
Discovery *state = task_data;
|
||||
|
||||
g_assert (state != NULL);
|
||||
g_assert (state->cursor != NULL);
|
||||
|
||||
sp_capture_cursor_foreach (state->cursor, discover_new_rows_frame_cb, state);
|
||||
g_task_return_boolean (task, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
handle_capture_results (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpVisualizerList *self = (SpVisualizerList *)object;
|
||||
Discovery *state;
|
||||
const gchar *key;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_LIST (self));
|
||||
g_assert (G_IS_TASK (result));
|
||||
g_assert (user_data == NULL);
|
||||
|
||||
state = g_task_get_task_data (G_TASK (result));
|
||||
|
||||
/*
|
||||
* TODO: It would be really nice if we had a more structured way to do this
|
||||
* so that data collections and visualizations could be mapped
|
||||
* together. One way to do so might be to create the concept of an
|
||||
* "instrument" which represents that pair and allows the user to
|
||||
* select what sort of data collections they'd like to see.
|
||||
*/
|
||||
|
||||
if (state->has_cpu)
|
||||
{
|
||||
GtkWidget *row = g_object_new (SP_TYPE_CPU_VISUALIZER_ROW,
|
||||
/* Translators: CPU is the processor. */
|
||||
"title", _("CPU"),
|
||||
"height-request", 50,
|
||||
"selectable", FALSE,
|
||||
"visible", TRUE,
|
||||
"y-lower", 0.0,
|
||||
"y-upper", 100.0,
|
||||
NULL);
|
||||
gtk_container_add (GTK_CONTAINER (self), row);
|
||||
}
|
||||
|
||||
for (guint i = 0; i < state->memory->len; i++)
|
||||
{
|
||||
guint counter_id = g_array_index (state->memory, guint, i);
|
||||
GdkRGBA rgba;
|
||||
GtkWidget *row = g_object_new (SP_TYPE_LINE_VISUALIZER_ROW,
|
||||
"title", _("Memory Used"),
|
||||
"height-request", 35,
|
||||
"selectable", FALSE,
|
||||
"visible", TRUE,
|
||||
"y-lower", 0.0,
|
||||
NULL);
|
||||
gdk_rgba_parse (&rgba, "#204a87");
|
||||
sp_line_visualizer_row_add_counter (SP_LINE_VISUALIZER_ROW (row), counter_id, &rgba);
|
||||
rgba.alpha = 0.3;
|
||||
sp_line_visualizer_row_set_fill (SP_LINE_VISUALIZER_ROW (row), counter_id, &rgba);
|
||||
gtk_container_add (GTK_CONTAINER (self), row);
|
||||
}
|
||||
|
||||
if (state->fps_counter)
|
||||
{
|
||||
GdkRGBA rgba;
|
||||
GtkWidget *row = g_object_new (SP_TYPE_LINE_VISUALIZER_ROW,
|
||||
/* Translators: FPS is frames per second. */
|
||||
"title", _("FPS"),
|
||||
"height-request", 35,
|
||||
"selectable", FALSE,
|
||||
"visible", TRUE,
|
||||
"y-lower", 0.0,
|
||||
"y-upper", 150.0,
|
||||
NULL);
|
||||
gdk_rgba_parse (&rgba, "#204a87");
|
||||
sp_line_visualizer_row_add_counter (SP_LINE_VISUALIZER_ROW (row), state->fps_counter, &rgba);
|
||||
rgba.alpha = 0.3;
|
||||
sp_line_visualizer_row_set_fill (SP_LINE_VISUALIZER_ROW (row), state->fps_counter, &rgba);
|
||||
gtk_container_add (GTK_CONTAINER (self), row);
|
||||
}
|
||||
|
||||
if (g_hash_table_size (state->mark_groups) < 30)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
|
||||
g_hash_table_iter_init (&iter, state->mark_groups);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL))
|
||||
{
|
||||
GtkWidget *row = g_object_new (SP_TYPE_MARK_VISUALIZER_ROW,
|
||||
"group", key,
|
||||
"title", key,
|
||||
"height-request", 50,
|
||||
"selectable", FALSE,
|
||||
"visible", TRUE,
|
||||
NULL);
|
||||
gtk_container_add (GTK_CONTAINER (self), row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
discover_new_rows (SpVisualizerList *self,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
static const SpCaptureFrameType types[] = { SP_CAPTURE_FRAME_CTRDEF, SP_CAPTURE_FRAME_MARK };
|
||||
g_autoptr(SpCaptureCursor) cursor = NULL;
|
||||
g_autoptr(GTask) task = NULL;
|
||||
SpCaptureCondition *condition;
|
||||
Discovery *state;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_LIST (self));
|
||||
g_assert (reader != NULL);
|
||||
|
||||
/*
|
||||
* The goal here is to automatically discover what rows should be added to
|
||||
* the visualizer list based on events we find in the capture file. In the
|
||||
* future, we might be able to add a UI flow to ask what the user wants or
|
||||
* denote capabilities at the beginning of the capture stream.
|
||||
*/
|
||||
|
||||
cursor = sp_capture_cursor_new (reader);
|
||||
condition = sp_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types);
|
||||
sp_capture_cursor_add_condition (cursor, g_steal_pointer (&condition));
|
||||
|
||||
state = g_slice_new0 (Discovery);
|
||||
state->mark_groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
state->cursor = g_steal_pointer (&cursor);
|
||||
state->memory = g_array_new (FALSE, FALSE, sizeof (guint));
|
||||
|
||||
task = g_task_new (self, NULL, handle_capture_results, NULL);
|
||||
g_task_set_task_data (task, g_steal_pointer (&state), (GDestroyNotify)discovery_free);
|
||||
g_task_run_in_thread (task, discover_new_rows_worker);
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_list_set_reader (SpVisualizerList *self,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_VISUALIZER_LIST (self));
|
||||
|
||||
if (reader != priv->reader)
|
||||
{
|
||||
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
|
||||
|
||||
if (reader != NULL)
|
||||
{
|
||||
priv->reader = sp_capture_reader_ref (reader);
|
||||
discover_new_rows (self, reader);
|
||||
}
|
||||
|
||||
gtk_container_foreach (GTK_CONTAINER (self),
|
||||
(GtkCallback)sp_visualizer_row_set_reader,
|
||||
reader);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READER]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_list_set_zoom_manager (SpVisualizerList *self,
|
||||
SpZoomManager *zoom_manager)
|
||||
{
|
||||
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_VISUALIZER_LIST (self));
|
||||
g_return_if_fail (SP_IS_ZOOM_MANAGER (zoom_manager));
|
||||
|
||||
if (g_set_object (&priv->zoom_manager, zoom_manager))
|
||||
{
|
||||
gtk_container_foreach (GTK_CONTAINER (self),
|
||||
(GtkCallback)sp_visualizer_row_set_zoom_manager,
|
||||
zoom_manager);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM_MANAGER]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_visualizer_list_get_zoom_manager:
|
||||
*
|
||||
* Returns: (nullable) (transfer): A #SpZoomManager or %NULL.
|
||||
*/
|
||||
SpZoomManager *
|
||||
sp_visualizer_list_get_zoom_manager (SpVisualizerList *self)
|
||||
{
|
||||
SpVisualizerListPrivate *priv = sp_visualizer_list_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_VISUALIZER_LIST (self), NULL);
|
||||
|
||||
return priv->zoom_manager;
|
||||
}
|
||||
59
src/libsysprof-ui/sp-visualizer-list.h
Normal file
59
src/libsysprof-ui/sp-visualizer-list.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* sp-visualizer-list.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_VISUALIZER_LIST_H
|
||||
#define SP_VISUALIZER_LIST_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "sp-capture-reader.h"
|
||||
#include "sp-zoom-manager.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_VISUALIZER_LIST (sp_visualizer_list_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpVisualizerList, sp_visualizer_list, SP, VISUALIZER_LIST, GtkListBox)
|
||||
|
||||
struct _SpVisualizerListClass
|
||||
{
|
||||
GtkListBoxClass parent_class;
|
||||
|
||||
gpointer _reserved1;
|
||||
gpointer _reserved2;
|
||||
gpointer _reserved3;
|
||||
gpointer _reserved4;
|
||||
gpointer _reserved5;
|
||||
gpointer _reserved6;
|
||||
gpointer _reserved7;
|
||||
gpointer _reserved8;
|
||||
};
|
||||
|
||||
GtkWidget *sp_visualizer_list_new (void);
|
||||
void sp_visualizer_list_set_reader (SpVisualizerList *self,
|
||||
SpCaptureReader *reader);
|
||||
SpCaptureReader *sp_visualizer_list_get_reader (SpVisualizerList *self);
|
||||
SpZoomManager *sp_visualizer_list_get_zoom_manager (SpVisualizerList *self);
|
||||
void sp_visualizer_list_set_zoom_manager (SpVisualizerList *self,
|
||||
SpZoomManager *zoom_manager);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_VISUALIZER_LIST_H */
|
||||
32
src/libsysprof-ui/sp-visualizer-row-private.h
Normal file
32
src/libsysprof-ui/sp-visualizer-row-private.h
Normal file
@ -0,0 +1,32 @@
|
||||
/* sp-visualizer-row-private.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_VISUALIZER_ROW_PRIVATE_H
|
||||
#define SP_VISUALIZER_ROW_PRIVATE_H
|
||||
|
||||
#include "sp-visualizer-row.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gint _sp_visualizer_row_get_graph_width (SpVisualizerRow *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_VISUALIZER_ROW_PRIVATE_H */
|
||||
308
src/libsysprof-ui/sp-visualizer-row.c
Normal file
308
src/libsysprof-ui/sp-visualizer-row.c
Normal file
@ -0,0 +1,308 @@
|
||||
/* sp-visualizer-row.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-visualizer-row"
|
||||
|
||||
#include "sp-visualizer-row.h"
|
||||
#include "sp-visualizer-row-private.h"
|
||||
|
||||
#define NSEC_PER_SEC G_GINT64_CONSTANT(1000000000)
|
||||
#define DEFAULT_PIXELS_PER_SECOND 20
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureReader *reader;
|
||||
SpZoomManager *zoom_manager;
|
||||
} SpVisualizerRowPrivate;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_ZOOM_MANAGER,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SpVisualizerRow, sp_visualizer_row, GTK_TYPE_LIST_BOX_ROW)
|
||||
|
||||
gint
|
||||
_sp_visualizer_row_get_graph_width (SpVisualizerRow *self)
|
||||
{
|
||||
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
|
||||
gdouble zoom_level = 1.0;
|
||||
gint64 begin_time;
|
||||
gint64 end_time;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_ROW (self));
|
||||
|
||||
if (priv->reader == NULL)
|
||||
return 0;
|
||||
|
||||
if (priv->zoom_manager != NULL)
|
||||
zoom_level = sp_zoom_manager_get_zoom (priv->zoom_manager);
|
||||
|
||||
begin_time = sp_capture_reader_get_start_time (priv->reader);
|
||||
end_time = sp_capture_reader_get_end_time (priv->reader);
|
||||
|
||||
return (end_time - begin_time)
|
||||
/ (gdouble)NSEC_PER_SEC
|
||||
* zoom_level
|
||||
* DEFAULT_PIXELS_PER_SECOND;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_row_get_preferred_width (GtkWidget *widget,
|
||||
gint *min_width,
|
||||
gint *nat_width)
|
||||
{
|
||||
SpVisualizerRow *self = (SpVisualizerRow *)widget;
|
||||
gint graph_width;
|
||||
gint real_min_width = 0;
|
||||
gint real_nat_width = 0;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_ROW (self));
|
||||
|
||||
GTK_WIDGET_CLASS (sp_visualizer_row_parent_class)->get_preferred_width (widget, &real_min_width, &real_nat_width);
|
||||
|
||||
graph_width = _sp_visualizer_row_get_graph_width (self);
|
||||
|
||||
*min_width = *nat_width = real_min_width + graph_width;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_row_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpVisualizerRow *self = SP_VISUALIZER_ROW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_ZOOM_MANAGER:
|
||||
g_value_set_object (value, sp_visualizer_row_get_zoom_manager (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_row_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpVisualizerRow *self = SP_VISUALIZER_ROW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_ZOOM_MANAGER:
|
||||
sp_visualizer_row_set_zoom_manager (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_row_finalize (GObject *object)
|
||||
{
|
||||
SpVisualizerRow *self = (SpVisualizerRow *)object;
|
||||
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
|
||||
g_clear_object (&priv->zoom_manager);
|
||||
|
||||
G_OBJECT_CLASS (sp_visualizer_row_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_row_class_init (SpVisualizerRowClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_visualizer_row_finalize;
|
||||
object_class->get_property = sp_visualizer_row_get_property;
|
||||
object_class->set_property = sp_visualizer_row_set_property;
|
||||
|
||||
widget_class->get_preferred_width = sp_visualizer_row_get_preferred_width;
|
||||
|
||||
properties [PROP_ZOOM_MANAGER] =
|
||||
g_param_spec_object ("zoom-manager",
|
||||
"Zoom Manager",
|
||||
"Zoom Manager",
|
||||
SP_TYPE_ZOOM_MANAGER,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_row_init (SpVisualizerRow *self)
|
||||
{
|
||||
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (self), FALSE);
|
||||
gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (self), FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_row_zoom_manager_notify_zoom (SpVisualizerRow *self,
|
||||
GParamSpec *pspec,
|
||||
SpZoomManager *zoom_manager)
|
||||
{
|
||||
g_assert (SP_IS_VISUALIZER_ROW (self));
|
||||
g_assert (SP_IS_ZOOM_MANAGER (zoom_manager));
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_visualizer_row_get_zoom_manager:
|
||||
*
|
||||
* Returns: (transfer none) (nullable): A #SpZoomManager or %NULL.
|
||||
*/
|
||||
SpZoomManager *
|
||||
sp_visualizer_row_get_zoom_manager (SpVisualizerRow *self)
|
||||
{
|
||||
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_VISUALIZER_ROW (self), NULL);
|
||||
|
||||
return priv->zoom_manager;
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_row_set_zoom_manager (SpVisualizerRow *self,
|
||||
SpZoomManager *zoom_manager)
|
||||
{
|
||||
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_VISUALIZER_ROW (self));
|
||||
g_return_if_fail (!zoom_manager || SP_IS_ZOOM_MANAGER (zoom_manager));
|
||||
|
||||
if (priv->zoom_manager != zoom_manager)
|
||||
{
|
||||
if (priv->zoom_manager != NULL)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (priv->zoom_manager,
|
||||
G_CALLBACK (sp_visualizer_row_zoom_manager_notify_zoom),
|
||||
self);
|
||||
g_clear_object (&priv->zoom_manager);
|
||||
}
|
||||
|
||||
if (zoom_manager != NULL)
|
||||
{
|
||||
priv->zoom_manager = g_object_ref (zoom_manager);
|
||||
g_signal_connect_object (priv->zoom_manager,
|
||||
"notify::zoom",
|
||||
G_CALLBACK (sp_visualizer_row_zoom_manager_notify_zoom),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
}
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM_MANAGER]);
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_row_set_reader (SpVisualizerRow *self,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpVisualizerRowPrivate *priv = sp_visualizer_row_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_VISUALIZER_ROW (self));
|
||||
|
||||
if (priv->reader != reader)
|
||||
{
|
||||
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
|
||||
|
||||
if (reader != NULL)
|
||||
priv->reader = sp_capture_reader_ref (reader);
|
||||
|
||||
if (SP_VISUALIZER_ROW_GET_CLASS (self)->set_reader)
|
||||
SP_VISUALIZER_ROW_GET_CLASS (self)->set_reader (self, reader);
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
subtract_border (GtkAllocation *alloc,
|
||||
GtkBorder *border)
|
||||
{
|
||||
#if 0
|
||||
g_print ("Border; %d %d %d %d\n", border->top, border->left, border->bottom, border->right);
|
||||
#endif
|
||||
|
||||
alloc->x += border->left;
|
||||
alloc->y += border->top;
|
||||
alloc->width -= border->left + border->right;
|
||||
alloc->height -= border->top + border->bottom;
|
||||
}
|
||||
|
||||
static void
|
||||
adjust_alloc_for_borders (SpVisualizerRow *self,
|
||||
GtkAllocation *alloc)
|
||||
{
|
||||
GtkStyleContext *style_context;
|
||||
GtkBorder border;
|
||||
GtkStateFlags state;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_ROW (self));
|
||||
g_assert (alloc != NULL);
|
||||
|
||||
state = gtk_widget_get_state_flags (GTK_WIDGET (self));
|
||||
style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
|
||||
gtk_style_context_get_border (style_context, state, &border);
|
||||
|
||||
subtract_border (alloc, &border);
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_row_translate_points (SpVisualizerRow *self,
|
||||
const SpVisualizerRowRelativePoint *in_points,
|
||||
guint n_in_points,
|
||||
SpVisualizerRowAbsolutePoint *out_points,
|
||||
guint n_out_points)
|
||||
{
|
||||
GtkAllocation alloc;
|
||||
gint graph_width;
|
||||
|
||||
g_return_if_fail (SP_IS_VISUALIZER_ROW (self));
|
||||
g_return_if_fail (in_points != NULL);
|
||||
g_return_if_fail (out_points != NULL);
|
||||
g_return_if_fail (n_in_points == n_out_points);
|
||||
|
||||
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
||||
adjust_alloc_for_borders (self, &alloc);
|
||||
|
||||
graph_width = _sp_visualizer_row_get_graph_width (self);
|
||||
|
||||
for (guint i = 0; i < n_in_points; i++)
|
||||
{
|
||||
out_points[i].x = (in_points[i].x * graph_width);
|
||||
out_points[i].y = alloc.height - (in_points[i].y * alloc.height);
|
||||
}
|
||||
}
|
||||
|
||||
78
src/libsysprof-ui/sp-visualizer-row.h
Normal file
78
src/libsysprof-ui/sp-visualizer-row.h
Normal file
@ -0,0 +1,78 @@
|
||||
/* sp-visualizer-row.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_VISUALIZER_ROW_H
|
||||
#define SP_VISUALIZER_ROW_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "sp-capture-reader.h"
|
||||
#include "sp-zoom-manager.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_VISUALIZER_ROW (sp_visualizer_row_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpVisualizerRow, sp_visualizer_row, SP, VISUALIZER_ROW, GtkListBoxRow)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gdouble x;
|
||||
gdouble y;
|
||||
} SpVisualizerRowRelativePoint;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gint x;
|
||||
gint y;
|
||||
} SpVisualizerRowAbsolutePoint;
|
||||
|
||||
struct _SpVisualizerRowClass
|
||||
{
|
||||
GtkListBoxRowClass parent_class;
|
||||
|
||||
/**
|
||||
* SpVisualizerRow::set_reader:
|
||||
*
|
||||
* Sets the reader that the row should use to extract counters.
|
||||
* This reader is private to the row and should be freed when
|
||||
* no longer in use with sp_capture_reader_unref().
|
||||
*/
|
||||
void (*set_reader) (SpVisualizerRow *self,
|
||||
SpCaptureReader *reader);
|
||||
|
||||
/*< private >*/
|
||||
gpointer _reserved[16];
|
||||
};
|
||||
|
||||
void sp_visualizer_row_set_reader (SpVisualizerRow *self,
|
||||
SpCaptureReader *reader);
|
||||
SpZoomManager *sp_visualizer_row_get_zoom_manager (SpVisualizerRow *self);
|
||||
void sp_visualizer_row_set_zoom_manager (SpVisualizerRow *self,
|
||||
SpZoomManager *zoom_manager);
|
||||
void sp_visualizer_row_translate_points (SpVisualizerRow *self,
|
||||
const SpVisualizerRowRelativePoint *in_points,
|
||||
guint n_in_points,
|
||||
SpVisualizerRowAbsolutePoint *out_points,
|
||||
guint n_out_points);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_VISUALIZER_ROW_H */
|
||||
390
src/libsysprof-ui/sp-visualizer-ticks.c
Normal file
390
src/libsysprof-ui/sp-visualizer-ticks.c
Normal file
@ -0,0 +1,390 @@
|
||||
/* sp-visualizer-ticks.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include "sp-visualizer-ticks.h"
|
||||
|
||||
#define NSEC_PER_SEC G_GINT64_CONSTANT(1000000000)
|
||||
#define NSEC_PER_HOUR (NSEC_PER_SEC * 60 * 60)
|
||||
#define NSEC_PER_MIN (NSEC_PER_SEC * 60)
|
||||
#define NSEC_PER_MSEC (NSEC_PER_SEC/G_GINT64_CONSTANT(1000))
|
||||
#define MIN_TICK_DISTANCE 20
|
||||
#define LABEL_HEIGHT_PX 8
|
||||
|
||||
struct _SpVisualizerTicks
|
||||
{
|
||||
GtkDrawingArea parent_instance;
|
||||
|
||||
gint64 epoch;
|
||||
gint64 begin_time;
|
||||
gint64 end_time;
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
enum {
|
||||
TICK_MINUTES,
|
||||
TICK_HALF_MINUTES,
|
||||
TICK_FIVE_SECONDS,
|
||||
TICK_SECONDS,
|
||||
TICK_HALF_SECONDS,
|
||||
TICK_QUARTER_SECONDS,
|
||||
TICK_TENTHS,
|
||||
TICK_HUNDREDTHS,
|
||||
TICK_THOUSANDTHS,
|
||||
N_TICKS
|
||||
};
|
||||
|
||||
struct {
|
||||
gint width;
|
||||
gint height;
|
||||
gint64 span;
|
||||
} tick_sizing[N_TICKS] = {
|
||||
{ 1, 12, NSEC_PER_SEC * 60 },
|
||||
{ 1, 11, NSEC_PER_SEC * 30 },
|
||||
{ 1, 10, NSEC_PER_SEC * 5 },
|
||||
{ 1, 9, NSEC_PER_SEC },
|
||||
{ 1, 8, NSEC_PER_SEC / 2 },
|
||||
{ 1, 6, NSEC_PER_SEC / 4 },
|
||||
{ 1, 5, NSEC_PER_SEC / 10 },
|
||||
{ 1, 4, NSEC_PER_SEC / 100 },
|
||||
{ 1, 3, NSEC_PER_SEC / 1000 },
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (SpVisualizerTicks, sp_visualizer_ticks, GTK_TYPE_DRAWING_AREA)
|
||||
|
||||
static void
|
||||
update_label_text (PangoLayout *layout,
|
||||
gint64 time,
|
||||
gboolean want_msec)
|
||||
{
|
||||
g_autofree gchar *str = NULL;
|
||||
gint64 tmp;
|
||||
gint msec = 0;
|
||||
gint hours = 0;
|
||||
gint min = 0;
|
||||
gint sec = 0;
|
||||
|
||||
g_assert (PANGO_IS_LAYOUT (layout));
|
||||
|
||||
tmp = time % NSEC_PER_SEC;
|
||||
time -= tmp;
|
||||
msec = tmp / 100000L;
|
||||
|
||||
if (time >= NSEC_PER_HOUR)
|
||||
{
|
||||
hours = time / NSEC_PER_HOUR;
|
||||
time %= NSEC_PER_HOUR;
|
||||
}
|
||||
|
||||
if (time >= NSEC_PER_MIN)
|
||||
{
|
||||
min = time / NSEC_PER_MIN;
|
||||
time %= NSEC_PER_MIN;
|
||||
}
|
||||
|
||||
if (time >= NSEC_PER_SEC)
|
||||
{
|
||||
sec = time / NSEC_PER_SEC;
|
||||
time %= NSEC_PER_SEC;
|
||||
}
|
||||
|
||||
if (want_msec || (!hours && !min && !sec && msec))
|
||||
{
|
||||
if (hours > 0)
|
||||
str = g_strdup_printf ("%02u:%02u:%02u.%04u", hours, min, sec, msec);
|
||||
else
|
||||
str = g_strdup_printf ("%02u:%02u.%04u", min, sec, msec);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hours > 0)
|
||||
str = g_strdup_printf ("%02u:%02u:%02u", hours, min, sec);
|
||||
else
|
||||
str = g_strdup_printf ("%02u:%02u", min, sec);
|
||||
}
|
||||
|
||||
pango_layout_set_text (layout, str, -1);
|
||||
}
|
||||
|
||||
static inline gdouble
|
||||
get_x_for_time (SpVisualizerTicks *self,
|
||||
const GtkAllocation *alloc,
|
||||
gint64 t)
|
||||
{
|
||||
gint64 timespan = self->end_time - self->begin_time;
|
||||
gdouble x_ratio = (gdouble)(t - self->begin_time) / (gdouble)timespan;
|
||||
return alloc->width * x_ratio;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline gint64
|
||||
get_time_at_x (SpVisualizerTicks *self,
|
||||
const GtkAllocation *alloc,
|
||||
gdouble x)
|
||||
{
|
||||
return self->begin_time
|
||||
- self->epoch
|
||||
+ ((self->end_time - self->begin_time) / (gdouble)alloc->width * x);
|
||||
}
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
draw_ticks (SpVisualizerTicks *self,
|
||||
cairo_t *cr,
|
||||
GtkAllocation *area,
|
||||
gint ticks,
|
||||
gboolean label_mode)
|
||||
{
|
||||
GtkAllocation alloc;
|
||||
gdouble half;
|
||||
gint64 x_offset;
|
||||
gint count = 0;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_TICKS (self));
|
||||
g_assert (cr != NULL);
|
||||
g_assert (area != NULL);
|
||||
g_assert (ticks >= 0);
|
||||
g_assert (ticks < N_TICKS);
|
||||
|
||||
/*
|
||||
* If we are in label_model, we don't draw the ticks but only the labels.
|
||||
* That way we can determine which tick level managed to draw a tick in
|
||||
* the visible region so we only show labels for the largest tick level.
|
||||
* (Returning true from this method indicates we successfully drew a tick).
|
||||
*/
|
||||
|
||||
half = tick_sizing[ticks].width / 2.0;
|
||||
|
||||
/* Take our epoch into account to calculate the offset of the
|
||||
* first tick, which might not align perfectly when the beginning
|
||||
* of the visible area.
|
||||
*/
|
||||
x_offset = (self->begin_time - self->epoch) % tick_sizing[ticks].span;
|
||||
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
||||
|
||||
if G_UNLIKELY (label_mode)
|
||||
{
|
||||
PangoLayout *layout;
|
||||
PangoFontDescription *font_desc;
|
||||
gboolean want_msec;
|
||||
|
||||
layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), "00:10:00");
|
||||
|
||||
font_desc = pango_font_description_new ();
|
||||
pango_font_description_set_family_static (font_desc, "Monospace");
|
||||
pango_font_description_set_absolute_size (font_desc, LABEL_HEIGHT_PX * PANGO_SCALE);
|
||||
pango_layout_set_font_description (layout, font_desc);
|
||||
pango_font_description_free (font_desc);
|
||||
|
||||
/* If we are operating on smaller than seconds here, then we want
|
||||
* to ensure we include msec with the timestamps.
|
||||
*/
|
||||
want_msec = tick_sizing[ticks].span < NSEC_PER_SEC;
|
||||
|
||||
for (gint64 t = self->begin_time - x_offset;
|
||||
t <= self->end_time;
|
||||
t += tick_sizing[ticks].span)
|
||||
{
|
||||
gdouble x = get_x_for_time (self, &alloc, t);
|
||||
|
||||
cairo_move_to (cr, (gint)x + .5 - (gint)half, alloc.height - LABEL_HEIGHT_PX);
|
||||
update_label_text (layout, t - self->epoch, want_msec);
|
||||
pango_cairo_show_layout (cr, layout);
|
||||
}
|
||||
|
||||
g_clear_object (&layout);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (gint64 t = self->begin_time - x_offset;
|
||||
t <= self->end_time;
|
||||
t += tick_sizing[ticks].span)
|
||||
{
|
||||
gdouble x = get_x_for_time (self, &alloc, t);
|
||||
|
||||
cairo_move_to (cr, (gint)x - .5 - (gint)half, 0);
|
||||
cairo_line_to (cr, (gint)x - .5 - (gint)half, tick_sizing[ticks].height);
|
||||
count++;
|
||||
}
|
||||
|
||||
cairo_set_line_width (cr, tick_sizing[ticks].width);
|
||||
cairo_stroke (cr);
|
||||
}
|
||||
|
||||
return count > 2;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_visualizer_ticks_draw (GtkWidget *widget,
|
||||
cairo_t *cr)
|
||||
{
|
||||
SpVisualizerTicks *self = SP_VISUALIZER_TICKS (widget);
|
||||
GtkStyleContext *style;
|
||||
GtkAllocation alloc;
|
||||
GtkStateFlags state;
|
||||
gint64 timespan;
|
||||
GdkRGBA color;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_TICKS (self));
|
||||
g_assert (cr != NULL);
|
||||
|
||||
if (0 == (timespan = self->end_time - self->begin_time))
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
|
||||
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
||||
|
||||
style = gtk_widget_get_style_context (widget);
|
||||
state = gtk_widget_get_state_flags (widget);
|
||||
gtk_style_context_get_color (style, state, &color);
|
||||
|
||||
gdk_cairo_set_source_rgba (cr, &color);
|
||||
|
||||
/*
|
||||
* We need to discover up to what level we will draw tick marks.
|
||||
* This is based on the width of the widget and the number of ticks
|
||||
* to draw (which is determined from our timespan). We will skip a
|
||||
* mark if they will end up less than MIN_TICK_DISTANCE px apart.
|
||||
*/
|
||||
|
||||
for (guint i = G_N_ELEMENTS (tick_sizing); i > 0; i--)
|
||||
{
|
||||
gint64 n_ticks = timespan / tick_sizing[i - 1].span;
|
||||
gint largest_match = -1;
|
||||
|
||||
if (n_ticks == 0 || (alloc.width / n_ticks) < MIN_TICK_DISTANCE)
|
||||
continue;
|
||||
|
||||
for (guint j = i; j > 0; j--)
|
||||
{
|
||||
if (draw_ticks (self, cr, &alloc, j - 1, FALSE))
|
||||
largest_match = j - 1;
|
||||
}
|
||||
|
||||
if (largest_match != -1)
|
||||
draw_ticks (self, cr, &alloc, largest_match, TRUE);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_ticks_get_preferred_height (GtkWidget *widget,
|
||||
gint *min_height,
|
||||
gint *nat_height)
|
||||
{
|
||||
g_assert (SP_IS_VISUALIZER_TICKS (widget));
|
||||
|
||||
*min_height = *nat_height = tick_sizing[0].height + LABEL_HEIGHT_PX;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_ticks_class_init (SpVisualizerTicksClass *klass)
|
||||
{
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
widget_class->draw = sp_visualizer_ticks_draw;
|
||||
widget_class->get_preferred_height = sp_visualizer_ticks_get_preferred_height;
|
||||
|
||||
gtk_widget_class_set_css_name (widget_class, "ticks");
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_ticks_init (SpVisualizerTicks *self)
|
||||
{
|
||||
self->end_time = G_GINT64_CONSTANT (1000000000) * 60;
|
||||
|
||||
gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
sp_visualizer_ticks_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_VISUALIZER_TICKS, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_ticks_get_time_range (SpVisualizerTicks *self,
|
||||
gint64 *begin_time,
|
||||
gint64 *end_time)
|
||||
{
|
||||
g_return_if_fail (SP_IS_VISUALIZER_TICKS (self));
|
||||
g_return_if_fail (begin_time != NULL || end_time != NULL);
|
||||
|
||||
if (begin_time != NULL)
|
||||
*begin_time = self->begin_time;
|
||||
|
||||
if (end_time != NULL)
|
||||
*end_time = self->end_time;
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_ticks_set_time_range (SpVisualizerTicks *self,
|
||||
gint64 begin_time,
|
||||
gint64 end_time)
|
||||
{
|
||||
g_return_if_fail (SP_IS_VISUALIZER_TICKS (self));
|
||||
|
||||
if (begin_time > end_time)
|
||||
{
|
||||
gint64 tmp = begin_time;
|
||||
begin_time = end_time;
|
||||
end_time = tmp;
|
||||
}
|
||||
|
||||
self->begin_time = begin_time;
|
||||
self->end_time = end_time;
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
gint64
|
||||
sp_visualizer_ticks_get_epoch (SpVisualizerTicks *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_VISUALIZER_TICKS (self), 0);
|
||||
|
||||
return self->epoch;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the epoch for the visualizer ticks.
|
||||
*
|
||||
* The epoch is the "real" starting time of the capture, where as the
|
||||
* sp_visualizer_ticks_set_time_range() function sets the visible range
|
||||
* of the capture.
|
||||
*
|
||||
* This is used to calculate the offset of the beginning of the capture
|
||||
* from begin_time so that the ticks appear in the proper location.
|
||||
*
|
||||
* This function should only need to be called when the reader is changed.
|
||||
*/
|
||||
void
|
||||
sp_visualizer_ticks_set_epoch (SpVisualizerTicks *self,
|
||||
gint64 epoch)
|
||||
{
|
||||
g_return_if_fail (SP_IS_VISUALIZER_TICKS (self));
|
||||
|
||||
if (self->epoch != epoch)
|
||||
{
|
||||
self->epoch = epoch;
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
}
|
||||
45
src/libsysprof-ui/sp-visualizer-ticks.h
Normal file
45
src/libsysprof-ui/sp-visualizer-ticks.h
Normal file
@ -0,0 +1,45 @@
|
||||
/* sp-visualizer-ticks.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_VISUALIZER_TICKS_H
|
||||
#define SP_VISUALIZER_TICKS_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_VISUALIZER_TICKS (sp_visualizer_ticks_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpVisualizerTicks, sp_visualizer_ticks, SP, VISUALIZER_TICKS, GtkDrawingArea)
|
||||
|
||||
GtkWidget *sp_visualizer_ticks_new (void);
|
||||
void sp_visualizer_ticks_set_epoch (SpVisualizerTicks *self,
|
||||
gint64 epoch);
|
||||
gint64 sp_visualizer_ticks_get_epoch (SpVisualizerTicks *self);
|
||||
void sp_visualizer_ticks_get_time_range (SpVisualizerTicks *self,
|
||||
gint64 *begin_time,
|
||||
gint64 *end_time);
|
||||
void sp_visualizer_ticks_set_time_range (SpVisualizerTicks *self,
|
||||
gint64 begin_time,
|
||||
gint64 end_time);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_VISUALIZER_TICKS_H */
|
||||
756
src/libsysprof-ui/sp-visualizer-view.c
Normal file
756
src/libsysprof-ui/sp-visualizer-view.c
Normal file
@ -0,0 +1,756 @@
|
||||
/* sp-visualizer-view.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-visualizer-view"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include "sp-theme-manager.h"
|
||||
#include "sp-visualizer-list.h"
|
||||
#include "sp-visualizer-row.h"
|
||||
#include "sp-visualizer-row-private.h"
|
||||
#include "sp-selection.h"
|
||||
#include "sp-visualizer-ticks.h"
|
||||
#include "sp-visualizer-view.h"
|
||||
|
||||
#define NSEC_PER_SEC G_GINT64_CONSTANT(1000000000)
|
||||
#define DEFAULT_PIXELS_PER_SECOND 20
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureReader *reader;
|
||||
SpZoomManager *zoom_manager;
|
||||
SpSelection *selection;
|
||||
|
||||
SpVisualizerList *list;
|
||||
GtkScrolledWindow *scroller;
|
||||
SpVisualizerTicks *ticks;
|
||||
|
||||
gint64 drag_begin_at;
|
||||
gint64 drag_selection_at;
|
||||
|
||||
guint button_pressed : 1;
|
||||
} SpVisualizerViewPrivate;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpVisualizerView *self;
|
||||
GtkStyleContext *style_context;
|
||||
cairo_t *cr;
|
||||
GtkAllocation alloc;
|
||||
} SelectionDraw;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_READER,
|
||||
PROP_ZOOM_MANAGER,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
enum {
|
||||
VISUALIZER_ADDED,
|
||||
VISUALIZER_REMOVED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static void buildable_iface_init (GtkBuildableIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpVisualizerView, sp_visualizer_view, GTK_TYPE_BIN, 0,
|
||||
G_ADD_PRIVATE (SpVisualizerView)
|
||||
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init))
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
static guint signals [N_SIGNALS];
|
||||
static GtkBuildableIface *parent_buildable;
|
||||
|
||||
static void
|
||||
find_row1 (GtkWidget *widget,
|
||||
gpointer data)
|
||||
{
|
||||
GtkWidget **row1 = data;
|
||||
|
||||
if (*row1 == NULL && SP_IS_VISUALIZER_ROW (widget))
|
||||
*row1 = widget;
|
||||
}
|
||||
|
||||
static gint64
|
||||
get_time_from_coordinates (SpVisualizerView *self,
|
||||
gint x,
|
||||
gint y)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
SpVisualizerRow *row1 = NULL;
|
||||
GtkAllocation alloc;
|
||||
gint64 begin_time;
|
||||
gint64 end_time;
|
||||
gint graph_width;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
|
||||
if (priv->reader == NULL)
|
||||
return 0;
|
||||
|
||||
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
||||
|
||||
x -= alloc.x;
|
||||
y -= alloc.y;
|
||||
|
||||
/*
|
||||
* Find the first row so we can get an idea of how wide the graph is
|
||||
* (ignoring spacing caused by the widget being wider than the data points.
|
||||
*/
|
||||
gtk_container_foreach (GTK_CONTAINER (priv->list), find_row1, &row1);
|
||||
if (!SP_IS_VISUALIZER_ROW (row1))
|
||||
return 0;
|
||||
|
||||
begin_time = sp_capture_reader_get_start_time (priv->reader);
|
||||
end_time = sp_capture_reader_get_end_time (priv->reader);
|
||||
graph_width = _sp_visualizer_row_get_graph_width (row1);
|
||||
|
||||
return begin_time + ((end_time - begin_time) * (x / (gdouble)graph_width));
|
||||
}
|
||||
|
||||
static gint
|
||||
get_x_for_time_at (SpVisualizerView *self,
|
||||
const GtkAllocation *alloc,
|
||||
gint64 time_at)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
SpVisualizerRow *row1 = NULL;
|
||||
GtkAdjustment *hadjustment;
|
||||
gdouble nsec_per_pixel;
|
||||
gdouble value;
|
||||
gint64 begin_time;
|
||||
gint64 end_time;
|
||||
gint graph_width;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (alloc != NULL);
|
||||
|
||||
/*
|
||||
* Find the first row so we can get an idea of how wide the graph is
|
||||
* (ignoring spacing caused by the widget being wider than the data points.
|
||||
*/
|
||||
gtk_container_foreach (GTK_CONTAINER (priv->list), find_row1, &row1);
|
||||
if (!SP_IS_VISUALIZER_ROW (row1))
|
||||
return 0;
|
||||
|
||||
hadjustment = gtk_scrolled_window_get_hadjustment (priv->scroller);
|
||||
value = gtk_adjustment_get_value (hadjustment);
|
||||
|
||||
begin_time = sp_capture_reader_get_start_time (priv->reader);
|
||||
end_time = sp_capture_reader_get_end_time (priv->reader);
|
||||
|
||||
graph_width = _sp_visualizer_row_get_graph_width (row1);
|
||||
nsec_per_pixel = (end_time - begin_time) / (gdouble)graph_width;
|
||||
begin_time += value * nsec_per_pixel;
|
||||
|
||||
return ((time_at - begin_time) / nsec_per_pixel);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_row_added (SpVisualizerView *self,
|
||||
GtkWidget *widget,
|
||||
SpVisualizerList *list)
|
||||
{
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (GTK_IS_WIDGET (widget));
|
||||
g_assert (SP_IS_VISUALIZER_LIST (list));
|
||||
|
||||
if (SP_IS_VISUALIZER_ROW (widget))
|
||||
g_signal_emit (self, signals [VISUALIZER_ADDED], 0, widget);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_row_removed (SpVisualizerView *self,
|
||||
GtkWidget *widget,
|
||||
SpVisualizerList *list)
|
||||
{
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (GTK_IS_WIDGET (widget));
|
||||
g_assert (SP_IS_VISUALIZER_LIST (list));
|
||||
|
||||
if (SP_IS_VISUALIZER_ROW (widget))
|
||||
g_signal_emit (self, signals [VISUALIZER_REMOVED], 0, widget);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_update_ticks (SpVisualizerView *self)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
GtkAdjustment *hadjustment;
|
||||
GtkAllocation alloc;
|
||||
gdouble value;
|
||||
gint64 begin_time;
|
||||
gint64 end_time;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
|
||||
hadjustment = gtk_scrolled_window_get_hadjustment (priv->scroller);
|
||||
value = gtk_adjustment_get_value (hadjustment);
|
||||
|
||||
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
|
||||
|
||||
begin_time = get_time_from_coordinates (self, alloc.x + value, alloc.y);
|
||||
end_time = get_time_from_coordinates (self, alloc.x + value + alloc.width, alloc.y);
|
||||
|
||||
sp_visualizer_ticks_set_time_range (priv->ticks, begin_time, end_time);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_hadjustment_value_changed (SpVisualizerView *self,
|
||||
GtkAdjustment *adjustment)
|
||||
{
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (GTK_IS_ADJUSTMENT (adjustment));
|
||||
|
||||
sp_visualizer_view_update_ticks (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_size_allocate (GtkWidget *widget,
|
||||
GtkAllocation *allocation)
|
||||
{
|
||||
SpVisualizerView *self = (SpVisualizerView *)widget;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (allocation != NULL);
|
||||
|
||||
GTK_WIDGET_CLASS (sp_visualizer_view_parent_class)->size_allocate (widget, allocation);
|
||||
|
||||
sp_visualizer_view_update_ticks (self);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_selection_cb (SpSelection *selection,
|
||||
gint64 range_begin,
|
||||
gint64 range_end,
|
||||
gpointer user_data)
|
||||
{
|
||||
SelectionDraw *draw = user_data;
|
||||
GdkRectangle area;
|
||||
|
||||
g_assert (SP_IS_SELECTION (selection));
|
||||
g_assert (draw != NULL);
|
||||
g_assert (draw->cr != NULL);
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (draw->self));
|
||||
|
||||
area.x = get_x_for_time_at (draw->self, &draw->alloc, range_begin);
|
||||
area.width = get_x_for_time_at (draw->self, &draw->alloc, range_end) - area.x;
|
||||
area.y = 0;
|
||||
area.height = draw->alloc.height;
|
||||
|
||||
if (area.width < 0)
|
||||
{
|
||||
area.width = ABS (area.width);
|
||||
area.x -= area.width;
|
||||
}
|
||||
|
||||
gtk_render_background (draw->style_context, draw->cr, area.x, area.y, area.width, area.height);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_visualizer_view_draw (GtkWidget *widget,
|
||||
cairo_t *cr)
|
||||
{
|
||||
SpVisualizerView *self = (SpVisualizerView *)widget;
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
SelectionDraw draw = { 0 };
|
||||
gboolean ret;
|
||||
|
||||
g_assert (GTK_IS_WIDGET (widget));
|
||||
g_assert (cr != NULL);
|
||||
|
||||
draw.style_context = gtk_widget_get_style_context (widget);
|
||||
draw.self = self;
|
||||
draw.cr = cr;
|
||||
|
||||
gtk_widget_get_allocation (widget, &draw.alloc);
|
||||
|
||||
ret = GTK_WIDGET_CLASS (sp_visualizer_view_parent_class)->draw (widget, cr);
|
||||
|
||||
if (sp_selection_get_has_selection (priv->selection) || priv->button_pressed)
|
||||
{
|
||||
gtk_style_context_add_class (draw.style_context, "selection");
|
||||
sp_selection_foreach (priv->selection, draw_selection_cb, &draw);
|
||||
if (priv->button_pressed)
|
||||
draw_selection_cb (priv->selection, priv->drag_begin_at, priv->drag_selection_at, &draw);
|
||||
gtk_style_context_remove_class (draw.style_context, "selection");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_visualizer_view_list_button_press_event (SpVisualizerView *self,
|
||||
GdkEventButton *ev,
|
||||
SpVisualizerList *list)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (ev != NULL);
|
||||
g_assert (SP_IS_VISUALIZER_LIST (list));
|
||||
|
||||
if (priv->reader == NULL)
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
|
||||
if (ev->button != GDK_BUTTON_PRIMARY)
|
||||
{
|
||||
if (sp_selection_get_has_selection (priv->selection))
|
||||
{
|
||||
sp_selection_unselect_all (priv->selection);
|
||||
return GDK_EVENT_STOP;
|
||||
}
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
if ((ev->state & GDK_SHIFT_MASK) == 0)
|
||||
sp_selection_unselect_all (priv->selection);
|
||||
|
||||
priv->button_pressed = TRUE;
|
||||
|
||||
priv->drag_begin_at = get_time_from_coordinates (self, ev->x, ev->y);
|
||||
priv->drag_selection_at = priv->drag_begin_at;
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_visualizer_view_list_button_release_event (SpVisualizerView *self,
|
||||
GdkEventButton *ev,
|
||||
SpVisualizerList *list)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (ev != NULL);
|
||||
g_assert (SP_IS_VISUALIZER_LIST (list));
|
||||
|
||||
if (!priv->button_pressed || ev->button != GDK_BUTTON_PRIMARY)
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
|
||||
priv->button_pressed = FALSE;
|
||||
|
||||
if (priv->drag_begin_at != priv->drag_selection_at)
|
||||
{
|
||||
sp_selection_select_range (priv->selection,
|
||||
priv->drag_begin_at,
|
||||
priv->drag_selection_at);
|
||||
priv->drag_begin_at = -1;
|
||||
priv->drag_selection_at = -1;
|
||||
}
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
|
||||
return GDK_EVENT_STOP;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_visualizer_view_list_motion_notify_event (SpVisualizerView *self,
|
||||
GdkEventMotion *ev,
|
||||
SpVisualizerList *list)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (ev != NULL);
|
||||
g_assert (SP_IS_VISUALIZER_LIST (list));
|
||||
|
||||
if (!priv->button_pressed)
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
|
||||
priv->drag_selection_at = get_time_from_coordinates (self, ev->x, ev->y);
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
|
||||
return GDK_EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_list_realize_after (SpVisualizerView *self,
|
||||
SpVisualizerList *list)
|
||||
{
|
||||
GdkDisplay *display;
|
||||
GdkWindow *window;
|
||||
GdkCursor *cursor;
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (SP_IS_VISUALIZER_LIST (list));
|
||||
|
||||
window = gtk_widget_get_window (GTK_WIDGET (list));
|
||||
display = gdk_window_get_display (window);
|
||||
cursor = gdk_cursor_new_from_name (display, "text");
|
||||
gdk_window_set_cursor (window, cursor);
|
||||
g_clear_object (&cursor);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_selection_changed (SpVisualizerView *self,
|
||||
SpSelection *selection)
|
||||
{
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (SP_IS_SELECTION (selection));
|
||||
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_finalize (GObject *object)
|
||||
{
|
||||
SpVisualizerView *self = (SpVisualizerView *)object;
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
|
||||
g_clear_object (&priv->zoom_manager);
|
||||
g_clear_object (&priv->selection);
|
||||
|
||||
G_OBJECT_CLASS (sp_visualizer_view_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpVisualizerView *self = SP_VISUALIZER_VIEW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_READER:
|
||||
g_value_set_boxed (value, sp_visualizer_view_get_reader (self));
|
||||
break;
|
||||
|
||||
case PROP_ZOOM_MANAGER:
|
||||
g_value_set_object (value, sp_visualizer_view_get_zoom_manager (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpVisualizerView *self = SP_VISUALIZER_VIEW (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_READER:
|
||||
sp_visualizer_view_set_reader (self, g_value_get_boxed (value));
|
||||
break;
|
||||
|
||||
case PROP_ZOOM_MANAGER:
|
||||
sp_visualizer_view_set_zoom_manager (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_class_init (SpVisualizerViewClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
SpThemeManager *theme_manager = sp_theme_manager_get_default ();
|
||||
|
||||
object_class->finalize = sp_visualizer_view_finalize;
|
||||
object_class->get_property = sp_visualizer_view_get_property;
|
||||
object_class->set_property = sp_visualizer_view_set_property;
|
||||
|
||||
widget_class->draw = sp_visualizer_view_draw;
|
||||
widget_class->size_allocate = sp_visualizer_view_size_allocate;
|
||||
|
||||
properties [PROP_READER] =
|
||||
g_param_spec_boxed ("reader",
|
||||
"Reader",
|
||||
"The reader for the visualizers",
|
||||
SP_TYPE_CAPTURE_READER,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_ZOOM_MANAGER] =
|
||||
g_param_spec_object ("zoom-manager",
|
||||
"Zoom Manager",
|
||||
"The zoom manager for the view",
|
||||
SP_TYPE_ZOOM_MANAGER,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
|
||||
signals [VISUALIZER_ADDED] =
|
||||
g_signal_new ("visualizer-added",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (SpVisualizerViewClass, visualizer_added),
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, SP_TYPE_VISUALIZER_ROW);
|
||||
|
||||
signals [VISUALIZER_REMOVED] =
|
||||
g_signal_new ("visualizer-removed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (SpVisualizerViewClass, visualizer_removed),
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, SP_TYPE_VISUALIZER_ROW);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sp-visualizer-view.ui");
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpVisualizerView, list);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpVisualizerView, scroller);
|
||||
gtk_widget_class_bind_template_child_private (widget_class, SpVisualizerView, ticks);
|
||||
|
||||
gtk_widget_class_set_css_name (widget_class, "visualizers");
|
||||
|
||||
sp_theme_manager_register_resource (theme_manager, NULL, NULL, "/org/gnome/sysprof/css/SpVisualizerView-shared.css");
|
||||
sp_theme_manager_register_resource (theme_manager, "Adwaita", NULL, "/org/gnome/sysprof/css/SpVisualizerView-Adwaita.css");
|
||||
sp_theme_manager_register_resource (theme_manager, "Adwaita", "dark", "/org/gnome/sysprof/css/SpVisualizerView-Adwaita-dark.css");
|
||||
|
||||
g_type_ensure (SP_TYPE_VISUALIZER_LIST);
|
||||
g_type_ensure (SP_TYPE_VISUALIZER_TICKS);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_init (SpVisualizerView *self)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
GtkAdjustment *hadjustment;
|
||||
|
||||
priv->drag_begin_at = -1;
|
||||
priv->drag_selection_at = -1;
|
||||
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
|
||||
priv->selection = g_object_new (SP_TYPE_SELECTION, NULL);
|
||||
|
||||
g_signal_connect_object (priv->selection,
|
||||
"changed",
|
||||
G_CALLBACK (sp_visualizer_view_selection_changed),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->list,
|
||||
"button-press-event",
|
||||
G_CALLBACK (sp_visualizer_view_list_button_press_event),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->list,
|
||||
"button-release-event",
|
||||
G_CALLBACK (sp_visualizer_view_list_button_release_event),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->list,
|
||||
"motion-notify-event",
|
||||
G_CALLBACK (sp_visualizer_view_list_motion_notify_event),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->list,
|
||||
"realize",
|
||||
G_CALLBACK (sp_visualizer_view_list_realize_after),
|
||||
self,
|
||||
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
|
||||
|
||||
g_signal_connect_object (priv->list,
|
||||
"add",
|
||||
G_CALLBACK (sp_visualizer_view_row_added),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (priv->list,
|
||||
"remove",
|
||||
G_CALLBACK (sp_visualizer_view_row_removed),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
hadjustment = gtk_scrolled_window_get_hadjustment (priv->scroller);
|
||||
|
||||
g_signal_connect_object (hadjustment,
|
||||
"value-changed",
|
||||
G_CALLBACK (sp_visualizer_view_hadjustment_value_changed),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_visualizer_view_get_reader:
|
||||
*
|
||||
* Returns: (transfer none): An #SpCaptureReader
|
||||
*/
|
||||
SpCaptureReader *
|
||||
sp_visualizer_view_get_reader (SpVisualizerView *self)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_VISUALIZER_VIEW (self), NULL);
|
||||
|
||||
return priv->reader;
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_view_set_reader (SpVisualizerView *self,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_VISUALIZER_VIEW (self));
|
||||
|
||||
if (priv->reader != reader)
|
||||
{
|
||||
g_clear_pointer (&priv->reader, sp_capture_reader_unref);
|
||||
|
||||
if (reader != NULL)
|
||||
{
|
||||
gint64 begin_time;
|
||||
|
||||
priv->reader = sp_capture_reader_ref (reader);
|
||||
|
||||
begin_time = sp_capture_reader_get_start_time (priv->reader);
|
||||
|
||||
sp_visualizer_ticks_set_epoch (priv->ticks, begin_time);
|
||||
sp_visualizer_ticks_set_time_range (priv->ticks, begin_time, begin_time);
|
||||
|
||||
sp_selection_unselect_all (priv->selection);
|
||||
}
|
||||
|
||||
sp_visualizer_list_set_reader (priv->list, reader);
|
||||
sp_visualizer_view_update_ticks (self);
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READER]);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_add_child (GtkBuildable *buildable,
|
||||
GtkBuilder *builder,
|
||||
GObject *child,
|
||||
const gchar *type)
|
||||
{
|
||||
SpVisualizerView *self = (SpVisualizerView *)buildable;
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (GTK_IS_BUILDER (builder));
|
||||
g_assert (G_IS_OBJECT (child));
|
||||
|
||||
if (g_strcmp0 (type, "visualizer") == 0 && GTK_IS_WIDGET (child))
|
||||
{
|
||||
gtk_container_add (GTK_CONTAINER (priv->list), GTK_WIDGET (child));
|
||||
return;
|
||||
}
|
||||
|
||||
parent_buildable->add_child (buildable, builder, child, type);
|
||||
}
|
||||
|
||||
static void
|
||||
buildable_iface_init (GtkBuildableIface *iface)
|
||||
{
|
||||
parent_buildable = g_type_interface_peek_parent (iface);
|
||||
iface->add_child = sp_visualizer_view_add_child;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_visualizer_view_zoom_manager_notify_zoom (SpVisualizerView *self,
|
||||
GParamSpec *pspec,
|
||||
SpZoomManager *zoom_manager)
|
||||
{
|
||||
g_assert (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_assert (SP_IS_ZOOM_MANAGER (zoom_manager));
|
||||
|
||||
sp_visualizer_view_update_ticks (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_visualizer_view_get_zoom_manager:
|
||||
*
|
||||
* Returns: (transfer none) (nullable): An #SpZoomManager or %NULL
|
||||
*/
|
||||
SpZoomManager *
|
||||
sp_visualizer_view_get_zoom_manager (SpVisualizerView *self)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_VISUALIZER_VIEW (self), NULL);
|
||||
|
||||
return priv->zoom_manager;
|
||||
}
|
||||
|
||||
void
|
||||
sp_visualizer_view_set_zoom_manager (SpVisualizerView *self,
|
||||
SpZoomManager *zoom_manager)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_VISUALIZER_VIEW (self));
|
||||
g_return_if_fail (!zoom_manager || SP_IS_ZOOM_MANAGER (zoom_manager));
|
||||
|
||||
if (priv->zoom_manager != zoom_manager)
|
||||
{
|
||||
if (priv->zoom_manager != NULL)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (priv->zoom_manager,
|
||||
G_CALLBACK (sp_visualizer_view_zoom_manager_notify_zoom),
|
||||
self);
|
||||
g_clear_object (&priv->zoom_manager);
|
||||
}
|
||||
|
||||
if (zoom_manager != NULL)
|
||||
{
|
||||
priv->zoom_manager = g_object_ref (zoom_manager);
|
||||
g_signal_connect_object (priv->zoom_manager,
|
||||
"notify::zoom",
|
||||
G_CALLBACK (sp_visualizer_view_zoom_manager_notify_zoom),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
}
|
||||
|
||||
sp_visualizer_list_set_zoom_manager (priv->list, zoom_manager);
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM_MANAGER]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_visualizer_view_get_selection:
|
||||
*
|
||||
* Gets the #SpSelection instance for the visualizer view.
|
||||
* This can be used to alter the selection or selections of the visualizers.
|
||||
*
|
||||
* Returns: (transfer none): An #SpSelection.
|
||||
*/
|
||||
SpSelection *
|
||||
sp_visualizer_view_get_selection (SpVisualizerView *self)
|
||||
{
|
||||
SpVisualizerViewPrivate *priv = sp_visualizer_view_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_VISUALIZER_VIEW (self), NULL);
|
||||
|
||||
return priv->selection;
|
||||
}
|
||||
75
src/libsysprof-ui/sp-visualizer-view.h
Normal file
75
src/libsysprof-ui/sp-visualizer-view.h
Normal file
@ -0,0 +1,75 @@
|
||||
/* sp-visualizer-view.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_VISUALIZER_VIEW_H
|
||||
#define SP_VISUALIZER_VIEW_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include <sysprof.h>
|
||||
|
||||
#include "sp-visualizer-row.h"
|
||||
#include "sp-selection.h"
|
||||
#include "sp-zoom-manager.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_VISUALIZER_VIEW (sp_visualizer_view_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpVisualizerView, sp_visualizer_view, SP, VISUALIZER_VIEW, GtkBin)
|
||||
|
||||
struct _SpVisualizerViewClass
|
||||
{
|
||||
GtkBinClass parent_class;
|
||||
|
||||
void (*visualizer_added) (SpVisualizerView *self,
|
||||
SpVisualizerRow *visualizer);
|
||||
void (*visualizer_removed) (SpVisualizerView *self,
|
||||
SpVisualizerRow *visualizer);
|
||||
|
||||
gpointer _reserved1;
|
||||
gpointer _reserved2;
|
||||
gpointer _reserved3;
|
||||
gpointer _reserved4;
|
||||
gpointer _reserved5;
|
||||
gpointer _reserved6;
|
||||
gpointer _reserved7;
|
||||
gpointer _reserved8;
|
||||
gpointer _reserved9;
|
||||
gpointer _reserved10;
|
||||
gpointer _reserved11;
|
||||
gpointer _reserved12;
|
||||
gpointer _reserved13;
|
||||
gpointer _reserved14;
|
||||
gpointer _reserved15;
|
||||
gpointer _reserved16;
|
||||
};
|
||||
|
||||
GtkWidget *sp_visualizer_view_new (void);
|
||||
SpCaptureReader *sp_visualizer_view_get_reader (SpVisualizerView *self);
|
||||
void sp_visualizer_view_set_reader (SpVisualizerView *self,
|
||||
SpCaptureReader *reader);
|
||||
SpZoomManager *sp_visualizer_view_get_zoom_manager (SpVisualizerView *self);
|
||||
void sp_visualizer_view_set_zoom_manager (SpVisualizerView *self,
|
||||
SpZoomManager *zoom_manager);
|
||||
SpSelection *sp_visualizer_view_get_selection (SpVisualizerView *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_VISUALIZER_VIEW_H */
|
||||
476
src/libsysprof-ui/sp-zoom-manager.c
Normal file
476
src/libsysprof-ui/sp-zoom-manager.c
Normal file
@ -0,0 +1,476 @@
|
||||
/* sp-zoom-manager.c
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-zoom-manager"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "sp-zoom-manager.h"
|
||||
|
||||
struct _SpZoomManager
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GSimpleActionGroup *actions;
|
||||
|
||||
gdouble min_zoom;
|
||||
gdouble max_zoom;
|
||||
gdouble zoom;
|
||||
} __attribute__((aligned(8)));
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_CAN_ZOOM_IN,
|
||||
PROP_CAN_ZOOM_OUT,
|
||||
PROP_MIN_ZOOM,
|
||||
PROP_MAX_ZOOM,
|
||||
PROP_ZOOM,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static void action_group_iface_init (GActionGroupInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpZoomManager, sp_zoom_manager, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, action_group_iface_init))
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
static gdouble zoom_levels[] = {
|
||||
0.3,
|
||||
0.5,
|
||||
0.67,
|
||||
0.80,
|
||||
0.90,
|
||||
1.0,
|
||||
1.1,
|
||||
1.2,
|
||||
1.33,
|
||||
1.5,
|
||||
1.7,
|
||||
2.0,
|
||||
2.4,
|
||||
3.0,
|
||||
5.0,
|
||||
10.0,
|
||||
20.0,
|
||||
30.0,
|
||||
50.0,
|
||||
};
|
||||
|
||||
static void
|
||||
sp_zoom_manager_zoom_in_action (GSimpleAction *action,
|
||||
GVariant *param,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpZoomManager *self = user_data;
|
||||
g_assert (SP_IS_ZOOM_MANAGER (self));
|
||||
sp_zoom_manager_zoom_in (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_zoom_manager_zoom_out_action (GSimpleAction *action,
|
||||
GVariant *param,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpZoomManager *self = user_data;
|
||||
g_assert (SP_IS_ZOOM_MANAGER (self));
|
||||
sp_zoom_manager_zoom_out (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_zoom_manager_zoom_one_action (GSimpleAction *action,
|
||||
GVariant *param,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpZoomManager *self = user_data;
|
||||
g_assert (SP_IS_ZOOM_MANAGER (self));
|
||||
sp_zoom_manager_reset (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_zoom_manager_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpZoomManager *self = SP_ZOOM_MANAGER (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_MIN_ZOOM:
|
||||
g_value_set_double (value, sp_zoom_manager_get_min_zoom (self));
|
||||
break;
|
||||
|
||||
case PROP_MAX_ZOOM:
|
||||
g_value_set_double (value, sp_zoom_manager_get_max_zoom (self));
|
||||
break;
|
||||
|
||||
case PROP_ZOOM:
|
||||
g_value_set_double (value, sp_zoom_manager_get_zoom (self));
|
||||
break;
|
||||
|
||||
case PROP_CAN_ZOOM_IN:
|
||||
g_value_set_boolean (value, sp_zoom_manager_get_can_zoom_in (self));
|
||||
break;
|
||||
|
||||
case PROP_CAN_ZOOM_OUT:
|
||||
g_value_set_boolean (value, sp_zoom_manager_get_can_zoom_out (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_zoom_manager_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpZoomManager *self = SP_ZOOM_MANAGER (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_MIN_ZOOM:
|
||||
sp_zoom_manager_set_min_zoom (self, g_value_get_double (value));
|
||||
break;
|
||||
|
||||
case PROP_MAX_ZOOM:
|
||||
sp_zoom_manager_set_max_zoom (self, g_value_get_double (value));
|
||||
break;
|
||||
|
||||
case PROP_ZOOM:
|
||||
sp_zoom_manager_set_zoom (self, g_value_get_double (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_zoom_manager_class_init (SpZoomManagerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->get_property = sp_zoom_manager_get_property;
|
||||
object_class->set_property = sp_zoom_manager_set_property;
|
||||
|
||||
properties [PROP_CAN_ZOOM_IN] =
|
||||
g_param_spec_boolean ("can-zoom-in",
|
||||
"Can Zoom In",
|
||||
"Can Zoom In",
|
||||
TRUE,
|
||||
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_CAN_ZOOM_OUT] =
|
||||
g_param_spec_boolean ("can-zoom-out",
|
||||
"Can Zoom Out",
|
||||
"Can Zoom Out",
|
||||
TRUE,
|
||||
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_MIN_ZOOM] =
|
||||
g_param_spec_double ("min-zoom",
|
||||
"Min Zoom",
|
||||
"The minimum zoom to apply",
|
||||
-G_MAXDOUBLE,
|
||||
G_MAXDOUBLE,
|
||||
0.0,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_MAX_ZOOM] =
|
||||
g_param_spec_double ("max-zoom",
|
||||
"Max Zoom",
|
||||
"The maximum zoom to apply",
|
||||
-G_MAXDOUBLE,
|
||||
G_MAXDOUBLE,
|
||||
0.0,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_ZOOM] =
|
||||
g_param_spec_double ("zoom",
|
||||
"Zoom",
|
||||
"The current zoom level",
|
||||
-G_MAXDOUBLE,
|
||||
G_MAXDOUBLE,
|
||||
1.0,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_zoom_manager_init (SpZoomManager *self)
|
||||
{
|
||||
static const GActionEntry entries[] = {
|
||||
{ "zoom-in", sp_zoom_manager_zoom_in_action },
|
||||
{ "zoom-out", sp_zoom_manager_zoom_out_action },
|
||||
{ "zoom-one", sp_zoom_manager_zoom_one_action },
|
||||
};
|
||||
GAction *action;
|
||||
|
||||
self->min_zoom = 0.0;
|
||||
self->max_zoom = 0.0;
|
||||
self->zoom = 1.0;
|
||||
|
||||
self->actions = g_simple_action_group_new ();
|
||||
g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
|
||||
entries,
|
||||
G_N_ELEMENTS (entries),
|
||||
self);
|
||||
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "zoom-in");
|
||||
g_object_bind_property (self, "can-zoom-in", action, "enabled", G_BINDING_SYNC_CREATE);
|
||||
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "zoom-out");
|
||||
g_object_bind_property (self, "can-zoom-out", action, "enabled", G_BINDING_SYNC_CREATE);
|
||||
}
|
||||
|
||||
SpZoomManager *
|
||||
sp_zoom_manager_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_ZOOM_MANAGER, NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_zoom_manager_get_can_zoom_in (SpZoomManager *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), FALSE);
|
||||
|
||||
return self->max_zoom == 0.0 || self->zoom < self->max_zoom;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_zoom_manager_get_can_zoom_out (SpZoomManager *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), FALSE);
|
||||
|
||||
return self->min_zoom == 0.0 || self->zoom > self->min_zoom;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_zoom_manager_get_min_zoom (SpZoomManager *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), FALSE);
|
||||
|
||||
return self->min_zoom;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_zoom_manager_get_max_zoom (SpZoomManager *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), FALSE);
|
||||
|
||||
return self->max_zoom;
|
||||
}
|
||||
|
||||
void
|
||||
sp_zoom_manager_set_min_zoom (SpZoomManager *self,
|
||||
gdouble min_zoom)
|
||||
{
|
||||
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
|
||||
|
||||
if (min_zoom != self->min_zoom)
|
||||
{
|
||||
self->min_zoom = min_zoom;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MIN_ZOOM]);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_OUT]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_zoom_manager_set_max_zoom (SpZoomManager *self,
|
||||
gdouble max_zoom)
|
||||
{
|
||||
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
|
||||
|
||||
if (max_zoom != self->max_zoom)
|
||||
{
|
||||
self->max_zoom = max_zoom;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MAX_ZOOM]);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_IN]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_zoom_manager_zoom_in (SpZoomManager *self)
|
||||
{
|
||||
gdouble zoom;
|
||||
|
||||
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
|
||||
|
||||
if (!sp_zoom_manager_get_can_zoom_in (self))
|
||||
return;
|
||||
|
||||
zoom = self->zoom;
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (zoom_levels); i++)
|
||||
{
|
||||
if (zoom_levels[i] > zoom)
|
||||
{
|
||||
zoom = zoom_levels[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (zoom == self->zoom)
|
||||
zoom *= 2;
|
||||
|
||||
sp_zoom_manager_set_zoom (self, zoom);
|
||||
}
|
||||
|
||||
void
|
||||
sp_zoom_manager_zoom_out (SpZoomManager *self)
|
||||
{
|
||||
gdouble zoom;
|
||||
|
||||
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
|
||||
|
||||
if (!sp_zoom_manager_get_can_zoom_out (self))
|
||||
return;
|
||||
|
||||
zoom = self->zoom;
|
||||
|
||||
for (guint i = G_N_ELEMENTS (zoom_levels); i > 0; i--)
|
||||
{
|
||||
if (zoom_levels[i-1] < zoom)
|
||||
{
|
||||
zoom = zoom_levels[i-1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (zoom == self->zoom)
|
||||
zoom /= 2.0;
|
||||
|
||||
sp_zoom_manager_set_zoom (self, zoom);
|
||||
}
|
||||
|
||||
void
|
||||
sp_zoom_manager_reset (SpZoomManager *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
|
||||
|
||||
sp_zoom_manager_set_zoom (self, 1.0);
|
||||
}
|
||||
|
||||
gdouble
|
||||
sp_zoom_manager_get_zoom (SpZoomManager *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_ZOOM_MANAGER (self), 0.0);
|
||||
|
||||
return self->zoom;
|
||||
}
|
||||
|
||||
void
|
||||
sp_zoom_manager_set_zoom (SpZoomManager *self,
|
||||
gdouble zoom)
|
||||
{
|
||||
gdouble min_zoom;
|
||||
gdouble max_zoom;
|
||||
|
||||
g_return_if_fail (SP_IS_ZOOM_MANAGER (self));
|
||||
|
||||
min_zoom = (self->min_zoom == 0.0) ? -G_MAXDOUBLE : self->min_zoom;
|
||||
max_zoom = (self->max_zoom == 0.0) ? G_MAXDOUBLE : self->max_zoom;
|
||||
|
||||
zoom = CLAMP (zoom, min_zoom, max_zoom);
|
||||
|
||||
if (zoom == 0.0)
|
||||
zoom = 1.0;
|
||||
|
||||
if (zoom != self->zoom)
|
||||
{
|
||||
self->zoom = zoom;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM]);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_IN]);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_OUT]);
|
||||
}
|
||||
}
|
||||
|
||||
static gchar **
|
||||
sp_zoom_manager_list_actions (GActionGroup *action_group)
|
||||
{
|
||||
SpZoomManager *self = (SpZoomManager *)action_group;
|
||||
|
||||
g_assert (SP_IS_ZOOM_MANAGER (self));
|
||||
|
||||
return g_action_group_list_actions (G_ACTION_GROUP (self->actions));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_zoom_manager_query_action (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
gboolean *enabled,
|
||||
const GVariantType **parameter_type,
|
||||
const GVariantType **state_type,
|
||||
GVariant **state_hint,
|
||||
GVariant **state)
|
||||
{
|
||||
SpZoomManager *self = (SpZoomManager *)action_group;
|
||||
|
||||
g_assert (SP_IS_ZOOM_MANAGER (self));
|
||||
g_assert (action_name != NULL);
|
||||
|
||||
return g_action_group_query_action (G_ACTION_GROUP (self->actions),
|
||||
action_name,
|
||||
enabled,
|
||||
parameter_type,
|
||||
state_type,
|
||||
state_hint,
|
||||
state);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_zoom_manager_change_action_state (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
GVariant *value)
|
||||
{
|
||||
SpZoomManager *self = (SpZoomManager *)action_group;
|
||||
|
||||
g_assert (SP_IS_ZOOM_MANAGER (self));
|
||||
g_assert (action_name != NULL);
|
||||
|
||||
g_action_group_change_action_state (G_ACTION_GROUP (self->actions), action_name, value);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_zoom_manager_activate_action (GActionGroup *action_group,
|
||||
const gchar *action_name,
|
||||
GVariant *parameter)
|
||||
{
|
||||
SpZoomManager *self = (SpZoomManager *)action_group;
|
||||
|
||||
g_assert (SP_IS_ZOOM_MANAGER (self));
|
||||
g_assert (action_name != NULL);
|
||||
|
||||
g_action_group_activate_action (G_ACTION_GROUP (self->actions), action_name, parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
action_group_iface_init (GActionGroupInterface *iface)
|
||||
{
|
||||
iface->list_actions = sp_zoom_manager_list_actions;
|
||||
iface->query_action = sp_zoom_manager_query_action;
|
||||
iface->change_action_state = sp_zoom_manager_change_action_state;
|
||||
iface->activate_action = sp_zoom_manager_activate_action;
|
||||
}
|
||||
50
src/libsysprof-ui/sp-zoom-manager.h
Normal file
50
src/libsysprof-ui/sp-zoom-manager.h
Normal file
@ -0,0 +1,50 @@
|
||||
/* sp-zoom-manager.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SP_ZOOM_MANAGER_H
|
||||
#define SP_ZOOM_MANAGER_H
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_ZOOM_MANAGER (sp_zoom_manager_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpZoomManager, sp_zoom_manager, SP, ZOOM_MANAGER, GObject)
|
||||
|
||||
SpZoomManager *sp_zoom_manager_new (void);
|
||||
gboolean sp_zoom_manager_get_can_zoom_in (SpZoomManager *self);
|
||||
gboolean sp_zoom_manager_get_can_zoom_out (SpZoomManager *self);
|
||||
gboolean sp_zoom_manager_get_min_zoom (SpZoomManager *self);
|
||||
gboolean sp_zoom_manager_get_max_zoom (SpZoomManager *self);
|
||||
void sp_zoom_manager_set_min_zoom (SpZoomManager *self,
|
||||
gdouble min_zoom);
|
||||
void sp_zoom_manager_set_max_zoom (SpZoomManager *self,
|
||||
gdouble max_zoom);
|
||||
void sp_zoom_manager_zoom_in (SpZoomManager *self);
|
||||
void sp_zoom_manager_zoom_out (SpZoomManager *self);
|
||||
void sp_zoom_manager_reset (SpZoomManager *self);
|
||||
gdouble sp_zoom_manager_get_zoom (SpZoomManager *self);
|
||||
void sp_zoom_manager_set_zoom (SpZoomManager *self,
|
||||
gdouble zoom);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_ZOOM_MANAGER_H */
|
||||
49
src/libsysprof-ui/sysprof-ui.h
Normal file
49
src/libsysprof-ui/sysprof-ui.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* sysprof-ui.h
|
||||
*
|
||||
* Copyright 2016-2019 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SYSPROF_UI_H
|
||||
#define SYSPROF_UI_H
|
||||
|
||||
#include <sysprof.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SYSPROF_INSIDE
|
||||
# include "sp-callgraph-view.h"
|
||||
# include "sp-cell-renderer-percent.h"
|
||||
# include "sp-cpu-visualizer-row.h"
|
||||
# include "sp-failed-state-view.h"
|
||||
# include "sp-line-visualizer-row.h"
|
||||
# include "sp-empty-state-view.h"
|
||||
# include "sp-model-filter.h"
|
||||
# include "sp-multi-paned.h"
|
||||
# include "sp-recording-state-view.h"
|
||||
# include "sp-process-model.h"
|
||||
# include "sp-process-model-item.h"
|
||||
# include "sp-process-model-row.h"
|
||||
# include "sp-profiler-menu-button.h"
|
||||
# include "sp-visualizer-row.h"
|
||||
# include "sp-visualizer-view.h"
|
||||
# include "sp-zoom-manager.h"
|
||||
#undef SYSPROF_INSIDE
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SYSPROF_UI_H */
|
||||
190
src/libsysprof-ui/ui/sp-callgraph-view.ui
Normal file
190
src/libsysprof-ui/ui/sp-callgraph-view.ui
Normal file
@ -0,0 +1,190 @@
|
||||
<interface>
|
||||
<template class="SpCallgraphView" parent="GtkBin">
|
||||
<child>
|
||||
<object class="GtkPaned">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="position">450</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkPaned">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="functions_view">
|
||||
<property name="fixed-height-mode">true</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="function_name_column">
|
||||
<property name="expand">true</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">0</property>
|
||||
<property name="title" translatable="yes">Functions</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText">
|
||||
<property name="ellipsize">middle</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="function_self_column">
|
||||
<property name="expand">false</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
<property name="title" translatable="yes">Self</property>
|
||||
<child>
|
||||
<object class="SpCellRendererPercent">
|
||||
<property name="xpad">6</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="percent">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="function_total_column">
|
||||
<property name="expand">false</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">2</property>
|
||||
<property name="title" translatable="yes">Total</property>
|
||||
<child>
|
||||
<object class="SpCellRendererPercent">
|
||||
<property name="xpad">6</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="percent">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">true</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="callers_view">
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="callers_name_column">
|
||||
<property name="expand">true</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">0</property>
|
||||
<property name="title" translatable="yes">Callers</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText">
|
||||
<property name="ellipsize">middle</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="callers_self_column">
|
||||
<property name="expand">false</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
<property name="title" translatable="yes">Self</property>
|
||||
<child>
|
||||
<object class="SpCellRendererPercent">
|
||||
<property name="xpad">6</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="percent">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="callers_total_column">
|
||||
<property name="expand">false</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">2</property>
|
||||
<property name="title" translatable="yes">Total</property>
|
||||
<child>
|
||||
<object class="SpCellRendererPercent">
|
||||
<property name="xpad">6</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="percent">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">true</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="descendants_view">
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="descendants_name_column">
|
||||
<property name="expand">true</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">0</property>
|
||||
<property name="title" translatable="yes">Descendants</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="descendants_self_column">
|
||||
<property name="expand">false</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">1</property>
|
||||
<property name="title" translatable="yes">Self</property>
|
||||
<child>
|
||||
<object class="SpCellRendererPercent">
|
||||
<property name="xpad">6</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="percent">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="descendants_total_column">
|
||||
<property name="expand">false</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="sort-column-id">2</property>
|
||||
<property name="title" translatable="yes">Cumulative</property>
|
||||
<child>
|
||||
<object class="SpCellRendererPercent">
|
||||
<property name="xpad">6</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="percent">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
64
src/libsysprof-ui/ui/sp-empty-state-view.ui
Normal file
64
src/libsysprof-ui/ui/sp-empty-state-view.ui
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="SpEmptyStateView" parent="GtkBin">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="border-width">36</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<property name="visible">true</property>
|
||||
<child type="center">
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">org.gnome.Sysprof-symbolic</property>
|
||||
<property name="pixel-size">256</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="title">
|
||||
<property name="label" translatable="yes">Welcome to Sysprof</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
<attributes>
|
||||
<attribute name="scale" value="2"/>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="subtitle">
|
||||
<property name="label" translatable="yes">Start profiling your system with the <b>Record</b> button above</property>
|
||||
<property name="use-markup">true</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="vexpand">true</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
<property name="pack-type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
64
src/libsysprof-ui/ui/sp-failed-state-view.ui
Normal file
64
src/libsysprof-ui/ui/sp-failed-state-view.ui
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0"?>
|
||||
<interface>
|
||||
<template class="SpFailedStateView" parent="GtkBin">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="border-width">36</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<property name="visible">true</property>
|
||||
<child type="center">
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">computer-fail-symbolic</property>
|
||||
<property name="pixel-size">256</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Ouch, that hurt!</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
<attributes>
|
||||
<attribute name="scale" value="2"/>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Something unexpectedly went wrong while trying to profile your system.</property>
|
||||
<property name="use-markup">true</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="vexpand">true</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
<property name="pack-type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
52
src/libsysprof-ui/ui/sp-process-model-row.ui
Normal file
52
src/libsysprof-ui/ui/sp-process-model-row.ui
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<interface>
|
||||
<template class="SpProcessModelRow" parent="GtkListBoxRow">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">true</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="image">
|
||||
<!-- Todo -->
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label">
|
||||
<property name="hexpand">false</property>
|
||||
<property name="visible">true</property>
|
||||
<property name="ellipsize">middle</property>
|
||||
<property name="xalign">0.0</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="args_label">
|
||||
<property name="hexpand">false</property>
|
||||
<property name="visible">true</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="xalign">0.0</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="check">
|
||||
<property name="hexpand">false</property>
|
||||
<property name="icon-name">object-select-symbolic</property>
|
||||
<property name="visible">false</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="pid">
|
||||
<property name="hexpand">true</property>
|
||||
<property name="visible">true</property>
|
||||
<property name="xalign">1.0</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
235
src/libsysprof-ui/ui/sp-profiler-menu-button.ui
Normal file
235
src/libsysprof-ui/ui/sp-profiler-menu-button.ui
Normal file
@ -0,0 +1,235 @@
|
||||
<?xml version="1.0"?>
|
||||
<interface>
|
||||
<template class="SpProfilerMenuButton" parent="GtkMenuButton">
|
||||
<property name="popover">popover</property>
|
||||
<property name="width-request">150</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label">
|
||||
<property name="ellipsize">end</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">true</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">pan-down-symbolic</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
<object class="SpProcessModel" id="process_model">
|
||||
</object>
|
||||
<object class="GtkPopover" id="popover">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkStackSwitcher">
|
||||
<property name="border-width">6</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="stack">stack</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="width-request">425</property>
|
||||
<property name="visible">true</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="margin">10</property>
|
||||
<property name="spacing">12</property>
|
||||
<child type="center">
|
||||
<object class="GtkLabel">
|
||||
<property name="hexpand">true</property>
|
||||
<property name="label" translatable="yes" comments="Translators: This is the description for a switch.">Profile my _entire system</property>
|
||||
<property name="use-underline">true</property>
|
||||
<property name="visible">true</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="mnemonic-widget">whole_system_switch</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="whole_system_switch">
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="hhomogeneous">true</property>
|
||||
<property name="vhomogeneous">false</property>
|
||||
<property name="interpolate-size">true</property>
|
||||
<property name="visible">true</property>
|
||||
<property name="border-width">10</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<!-- box with single child is used so we can toggle visibility of the child -->
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="processes_box">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="linked"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkEntry" id="process_filter_entry">
|
||||
<property name="placeholder-text" translatable="yes" comments="Translators: This is the placeholder in a search entry.">Search</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="expand">true</property>
|
||||
<property name="min-content-width">100</property>
|
||||
<property name="max-content-width">400</property>
|
||||
<property name="max-content-height">450</property>
|
||||
<property name="propagate-natural-width">true</property>
|
||||
<property name="propagate-natural-height">true</property>
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="process_list_box">
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">existing</property>
|
||||
<property name="title" translatable="yes">Existing Process</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Command Line</property>
|
||||
<property name="visible">true</property>
|
||||
<property name="xalign">0.0</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="spawn_entry">
|
||||
<property name="secondary-icon-activatable">false</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Environment</property>
|
||||
<property name="visible">true</property>
|
||||
<property name="xalign">0.0</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="inherit_environ">
|
||||
<property name="active">true</property>
|
||||
<property name="label" translatable="yes" comments="Translators: This is a check button.">Inherit current environment</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="shadow-type">in</property>
|
||||
<property name="min-content-height">100</property>
|
||||
<property name="max-content-height">400</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="env_tree_view">
|
||||
<property name="model">environment_model</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="env_key_column">
|
||||
<property name="expand">true</property>
|
||||
<property name="resizable">true</property>
|
||||
<property name="title" translatable="yes">Key</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="key_cell">
|
||||
<property name="editable">true</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="env_value_column">
|
||||
<property name="expand">true</property>
|
||||
<property name="resizable">true</property>
|
||||
<property name="title" translatable="yes">Value</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="value_cell">
|
||||
<property name="editable">true</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">spawn</property>
|
||||
<property name="title" translatable="yes">New Process</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkListStore" id="environment_model">
|
||||
<columns>
|
||||
<column type="gchararray"/>
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<data>
|
||||
<row>
|
||||
<col id="0"></col>
|
||||
<col id="1"></col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
</interface>
|
||||
64
src/libsysprof-ui/ui/sp-recording-state-view.ui
Normal file
64
src/libsysprof-ui/ui/sp-recording-state-view.ui
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0"?>
|
||||
<interface>
|
||||
<template class="SpRecordingStateView" parent="GtkBin">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="border-width">36</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">12</property>
|
||||
<property name="visible">true</property>
|
||||
<child type="center">
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">org.gnome.Sysprof-symbolic</property>
|
||||
<property name="pixel-size">256</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="elapsed">
|
||||
<property name="label" translatable="yes">00:00</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
<attributes>
|
||||
<attribute name="scale" value="4"/>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Did you know you can use <a href="help:sysprof">sysprof-cli</a> to record?</property>
|
||||
<property name="use-markup">true</property>
|
||||
<property name="visible">true</property>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack-type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="vexpand">true</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
<property name="pack-type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
32
src/libsysprof-ui/ui/sp-visualizer-view.ui
Normal file
32
src/libsysprof-ui/ui/sp-visualizer-view.ui
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="SpVisualizerView" parent="GtkBin">
|
||||
<child>
|
||||
<object class="GtkOverlay">
|
||||
<property name="visible">true</property>
|
||||
<child type="overlay">
|
||||
<object class="SpVisualizerTicks" id="ticks">
|
||||
<property name="valign">start</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pass-through">true</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scroller">
|
||||
<property name="propagate-natural-height">true</property>
|
||||
<property name="propagate-natural-width">false</property>
|
||||
<property name="visible">true</property>
|
||||
<child>
|
||||
<object class="SpVisualizerList" id="list">
|
||||
<property name="visible">true</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
532
src/libsysprof/binfile.c
Normal file
532
src/libsysprof/binfile.c
Normal file
@ -0,0 +1,532 @@
|
||||
/* MemProf -- memory profiler and leak detector
|
||||
* Copyright 1999, 2000, 2001, Red Hat, Inc.
|
||||
* Copyright 2002, Kristian Rietveld
|
||||
*
|
||||
* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2004, 2005, 2006, 2007, Soeren Sandmann
|
||||
*
|
||||
* 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/* Most interesting code in this file is lifted from bfdutils.c
|
||||
* and process.c from Memprof,
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "binfile.h"
|
||||
#include "elfparser.h"
|
||||
#include "sp-symbol-dirs.h"
|
||||
|
||||
struct bin_file_t
|
||||
{
|
||||
int ref_count;
|
||||
|
||||
GList * elf_files;
|
||||
|
||||
char * filename;
|
||||
|
||||
char * undefined_name;
|
||||
|
||||
gulong text_offset;
|
||||
|
||||
gboolean inode_check;
|
||||
ino_t inode;
|
||||
};
|
||||
|
||||
static ino_t
|
||||
read_inode (const char *filename)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (strcmp (filename, "[vdso]") == 0)
|
||||
return (ino_t)0;
|
||||
|
||||
if (stat (filename, &statbuf) < 0)
|
||||
return (ino_t)-1;
|
||||
|
||||
return statbuf.st_ino;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
already_warned (const char *name)
|
||||
{
|
||||
static GPtrArray *warnings;
|
||||
guint i;
|
||||
|
||||
if (!warnings)
|
||||
warnings = g_ptr_array_new ();
|
||||
|
||||
for (i = 0; i < warnings->len; ++i)
|
||||
{
|
||||
if (strcmp (warnings->pdata[i], name) == 0)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_ptr_array_add (warnings, g_strdup (name));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static ElfParser *
|
||||
get_build_id_file (ElfParser *elf)
|
||||
{
|
||||
const char *build_id;
|
||||
GList *tries = NULL, *list;
|
||||
char *init, *rest;
|
||||
ElfParser *result = NULL;
|
||||
char *tmp;
|
||||
|
||||
build_id = elf_parser_get_build_id (elf);
|
||||
|
||||
if (!build_id)
|
||||
return NULL;
|
||||
|
||||
if (strlen (build_id) < 4)
|
||||
return NULL;
|
||||
|
||||
init = g_strndup (build_id, 2);
|
||||
rest = g_strdup_printf ("%s%s", build_id + 2, ".debug");
|
||||
|
||||
tmp = g_build_filename (
|
||||
"/usr", "lib", "debug", ".build-id", init, rest, NULL);
|
||||
tries = g_list_append (tries, tmp);
|
||||
|
||||
tmp = g_build_filename (DEBUGDIR, ".build-id", init, rest, NULL);
|
||||
tries = g_list_append (tries, tmp);
|
||||
|
||||
for (list = tries; list != NULL; list = list->next)
|
||||
{
|
||||
char *name = list->data;
|
||||
ElfParser *parser = elf_parser_new (name, NULL);
|
||||
|
||||
if (parser)
|
||||
{
|
||||
const char *file_id = elf_parser_get_build_id (parser);
|
||||
|
||||
if (file_id && strcmp (build_id, file_id) == 0)
|
||||
{
|
||||
result = parser;
|
||||
break;
|
||||
}
|
||||
|
||||
elf_parser_free (parser);
|
||||
}
|
||||
}
|
||||
|
||||
g_list_foreach (tries, (GFunc)g_free, NULL);
|
||||
g_list_free (tries);
|
||||
|
||||
g_free (init);
|
||||
g_free (rest);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ElfParser *
|
||||
get_debuglink_file (ElfParser *elf,
|
||||
const char *filename,
|
||||
char **new_name)
|
||||
{
|
||||
const char *basename;
|
||||
char *dir;
|
||||
guint32 crc32;
|
||||
gchar **tries;
|
||||
ElfParser *result = NULL;
|
||||
const char *build_id;
|
||||
guint i;
|
||||
|
||||
if (!elf)
|
||||
return NULL;
|
||||
|
||||
basename = elf_parser_get_debug_link (elf, &crc32);
|
||||
|
||||
build_id = elf_parser_get_build_id (elf);
|
||||
|
||||
#if 0
|
||||
g_print (" debug link for %s is %s\n", filename, basename);
|
||||
#endif
|
||||
|
||||
if (!basename)
|
||||
return NULL;
|
||||
|
||||
dir = g_path_get_dirname (filename);
|
||||
|
||||
tries = sp_symbol_dirs_get_paths (dir, basename);
|
||||
|
||||
for (i = 0; tries[i]; i++)
|
||||
{
|
||||
const char *name = tries[i];
|
||||
ElfParser *parser = elf_parser_new (name, NULL);
|
||||
guint32 file_crc;
|
||||
const char *file_build_id;
|
||||
|
||||
if (parser)
|
||||
{
|
||||
/* If both files have build ids, and they don't match,
|
||||
* there is no point computing a CRC32 that we know
|
||||
* will fail
|
||||
*/
|
||||
file_build_id = elf_parser_get_build_id (parser);
|
||||
if (build_id && file_build_id && strcmp (build_id, file_build_id) != 0)
|
||||
goto skip;
|
||||
|
||||
file_crc = elf_parser_get_crc32 (parser);
|
||||
|
||||
if (file_crc == crc32)
|
||||
{
|
||||
result = parser;
|
||||
*new_name = g_strdup (name);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!already_warned (name))
|
||||
{
|
||||
g_print ("warning: %s has wrong crc %x, %s has crc %x)\n",
|
||||
name, file_crc, filename, crc32);
|
||||
}
|
||||
}
|
||||
|
||||
skip:
|
||||
elf_parser_free (parser);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (dir);
|
||||
g_strfreev (tries);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static GList *
|
||||
get_debug_binaries (GList *files,
|
||||
ElfParser *elf,
|
||||
const char *filename)
|
||||
{
|
||||
ElfParser *build_id_file;
|
||||
GHashTable *seen_names;
|
||||
GList *free_us = NULL;
|
||||
|
||||
build_id_file = get_build_id_file (elf);
|
||||
|
||||
if (build_id_file)
|
||||
return g_list_prepend (files, build_id_file);
|
||||
|
||||
/* .gnu_debuglink is actually a chain of debuglinks, and
|
||||
* there have been real-world cases where following it was
|
||||
* necessary to get useful debug information.
|
||||
*/
|
||||
seen_names = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
|
||||
while (elf)
|
||||
{
|
||||
char *debug_name;
|
||||
|
||||
if (g_hash_table_lookup (seen_names, filename))
|
||||
break;
|
||||
|
||||
g_hash_table_insert (seen_names, (char *)filename, (char *)filename);
|
||||
|
||||
elf = get_debuglink_file (elf, filename, &debug_name);
|
||||
|
||||
if (elf)
|
||||
{
|
||||
files = g_list_prepend (files, elf);
|
||||
free_us = g_list_prepend (free_us, debug_name);
|
||||
filename = debug_name;
|
||||
}
|
||||
}
|
||||
|
||||
g_list_foreach (free_us, (GFunc)g_free, NULL);
|
||||
g_list_free (free_us);
|
||||
|
||||
g_hash_table_destroy (seen_names);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
static char **
|
||||
get_lines (const char *format,
|
||||
...)
|
||||
G_GNUC_PRINTF (1, 2);
|
||||
|
||||
static char **
|
||||
get_lines (const char *format,
|
||||
...)
|
||||
{
|
||||
va_list args;
|
||||
char *filename;
|
||||
char **result = NULL;
|
||||
char *contents;
|
||||
|
||||
va_start (args, format);
|
||||
filename = g_strdup_vprintf (format, args);
|
||||
va_end (args);
|
||||
|
||||
if (g_file_get_contents (filename, &contents, NULL, NULL))
|
||||
{
|
||||
result = g_strsplit (contents, "\n", -1);
|
||||
|
||||
g_free (contents);
|
||||
}
|
||||
|
||||
g_free (filename);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const uint8_t *
|
||||
get_vdso_bytes (size_t *length)
|
||||
{
|
||||
static const uint8_t *bytes = NULL;
|
||||
static size_t n_bytes = 0;
|
||||
static gboolean has_data;
|
||||
|
||||
if (!has_data)
|
||||
{
|
||||
char **lines = get_lines ("/proc/%d/maps", getpid());
|
||||
int i;
|
||||
|
||||
for (i = 0; lines[i] != NULL; ++i)
|
||||
{
|
||||
char file[256];
|
||||
gulong start;
|
||||
gulong end;
|
||||
int count = sscanf (
|
||||
lines[i], "%lx-%lx %*15s %*x %*x:%*x %*u %255s",
|
||||
&start, &end, file);
|
||||
|
||||
if (count == 3 && strcmp (file, "[vdso]") == 0)
|
||||
{
|
||||
n_bytes = end - start;
|
||||
|
||||
/* Dup the memory here so that valgrind will only
|
||||
* report one 1 byte invalid read instead of
|
||||
* a ton when the elf parser scans the vdso
|
||||
*
|
||||
* The reason we get a spurious invalid read from
|
||||
* valgrind is that we are getting the address directly
|
||||
* from /proc/maps, and valgrind knows that its mmap()
|
||||
* wrapper never returned that address. But since it
|
||||
* is a legal mapping, it is legal to read it.
|
||||
*/
|
||||
bytes = g_memdup ((uint8_t *)start, n_bytes);
|
||||
|
||||
has_data = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (length)
|
||||
*length = n_bytes;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bin_file_t *
|
||||
bin_file_new (const char *filename)
|
||||
{
|
||||
ElfParser *elf = NULL;
|
||||
bin_file_t *bf;
|
||||
|
||||
bf = g_new0 (bin_file_t, 1);
|
||||
|
||||
bf->inode_check = FALSE;
|
||||
bf->filename = g_strdup (filename);
|
||||
bf->undefined_name = g_strdup_printf ("In file %s", filename);
|
||||
bf->ref_count = 1;
|
||||
bf->elf_files = NULL;
|
||||
|
||||
if (strcmp (filename, "[vdso]") == 0)
|
||||
{
|
||||
const guint8 *vdso_bytes;
|
||||
gsize length;
|
||||
|
||||
vdso_bytes = get_vdso_bytes (&length);
|
||||
|
||||
if (vdso_bytes)
|
||||
elf = elf_parser_new_from_data (vdso_bytes, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
elf = elf_parser_new (filename, NULL);
|
||||
}
|
||||
|
||||
if (elf)
|
||||
{
|
||||
/* We need the text offset of the actual binary, not the
|
||||
* (potential) debug binaries
|
||||
*/
|
||||
bf->text_offset = elf_parser_get_text_offset (elf);
|
||||
|
||||
bf->elf_files = get_debug_binaries (bf->elf_files, elf, filename);
|
||||
bf->elf_files = g_list_append (bf->elf_files, elf);
|
||||
|
||||
bf->inode = read_inode (filename);
|
||||
}
|
||||
|
||||
return bf;
|
||||
}
|
||||
|
||||
void
|
||||
bin_file_free (bin_file_t *bin_file)
|
||||
{
|
||||
if (--bin_file->ref_count == 0)
|
||||
{
|
||||
g_list_foreach (bin_file->elf_files, (GFunc)elf_parser_free, NULL);
|
||||
g_list_free (bin_file->elf_files);
|
||||
|
||||
g_free (bin_file->filename);
|
||||
g_free (bin_file->undefined_name);
|
||||
g_free (bin_file);
|
||||
}
|
||||
}
|
||||
|
||||
const bin_symbol_t *
|
||||
bin_file_lookup_symbol (bin_file_t *bin_file,
|
||||
gulong address)
|
||||
{
|
||||
GList *list;
|
||||
|
||||
#if 0
|
||||
g_print ("-=-=-=- \n");
|
||||
|
||||
g_print ("bin file lookup lookup %d\n", address);
|
||||
#endif
|
||||
|
||||
address -= bin_file->text_offset;
|
||||
|
||||
#if 0
|
||||
g_print ("lookup %d in %s\n", address, bin_file->filename);
|
||||
#endif
|
||||
|
||||
for (list = bin_file->elf_files; list != NULL; list = list->next)
|
||||
{
|
||||
ElfParser *elf = list->data;
|
||||
const ElfSym *sym = elf_parser_lookup_symbol (elf, address);
|
||||
|
||||
if (sym)
|
||||
{
|
||||
#if 0
|
||||
g_print ("found %lx => %s\n", address,
|
||||
bin_symbol_get_name (bin_file, sym));
|
||||
#endif
|
||||
return (const bin_symbol_t *)sym;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
g_print ("%lx undefined in %s (textoffset %x)\n",
|
||||
address + bin_file->text_offset,
|
||||
bin_file->filename,
|
||||
bin_file->text_offset);
|
||||
#endif
|
||||
|
||||
return (const bin_symbol_t *)bin_file->undefined_name;
|
||||
}
|
||||
|
||||
gboolean
|
||||
bin_file_check_inode (bin_file_t *bin_file,
|
||||
ino_t inode)
|
||||
{
|
||||
if (bin_file->inode == inode)
|
||||
return TRUE;
|
||||
|
||||
if (!bin_file->elf_files)
|
||||
return FALSE;
|
||||
|
||||
if (!bin_file->inode_check)
|
||||
{
|
||||
g_print ("warning: Inode mismatch for %s (disk: %"G_GUINT64_FORMAT", memory: %"G_GUINT64_FORMAT")\n",
|
||||
bin_file->filename, (guint64)bin_file->inode, (guint64)inode);
|
||||
|
||||
bin_file->inode_check = TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static const ElfSym *
|
||||
get_elf_sym (bin_file_t *file,
|
||||
const bin_symbol_t *symbol,
|
||||
ElfParser **elf_ret)
|
||||
{
|
||||
GList *list;
|
||||
|
||||
for (list = file->elf_files; list != NULL; list = list->next)
|
||||
{
|
||||
const ElfSym *sym = (const ElfSym *)symbol;
|
||||
ElfParser *elf = list->data;
|
||||
|
||||
if (elf_parser_owns_symbol (elf, sym))
|
||||
{
|
||||
*elf_ret = elf;
|
||||
return sym;
|
||||
}
|
||||
}
|
||||
|
||||
g_critical ("Internal error: unrecognized symbol pointer");
|
||||
|
||||
*elf_ret = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *
|
||||
bin_symbol_get_name (bin_file_t *file,
|
||||
const bin_symbol_t *symbol)
|
||||
{
|
||||
if (file->undefined_name == (char *)symbol)
|
||||
{
|
||||
return file->undefined_name;
|
||||
}
|
||||
else
|
||||
{
|
||||
ElfParser *elf;
|
||||
const ElfSym *sym;
|
||||
|
||||
sym = get_elf_sym (file, symbol, &elf);
|
||||
|
||||
return elf_parser_get_sym_name (elf, sym);
|
||||
}
|
||||
}
|
||||
|
||||
gulong
|
||||
bin_symbol_get_address (bin_file_t *file,
|
||||
const bin_symbol_t *symbol)
|
||||
{
|
||||
if (file->undefined_name == (char *)symbol)
|
||||
{
|
||||
return 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ElfParser *elf;
|
||||
const ElfSym *sym;
|
||||
|
||||
sym = get_elf_sym (file, symbol, &elf);
|
||||
|
||||
return elf_parser_get_sym_address (elf, sym);
|
||||
}
|
||||
}
|
||||
46
src/libsysprof/binfile.h
Normal file
46
src/libsysprof/binfile.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* MemProf -- memory profiler and leak detector
|
||||
* Copyright 1999, 2000, 2001, Red Hat, Inc.
|
||||
* Copyright 2002, Kristian Rietveld
|
||||
*
|
||||
* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2004, Red Hat, Inc.
|
||||
* Copyright 2004, 2005, Soeren Sandmann
|
||||
*
|
||||
* 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef BIN_FILE_H
|
||||
#define BIN_FILE_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef struct bin_file_t bin_file_t;
|
||||
typedef struct bin_symbol_t bin_symbol_t;
|
||||
|
||||
/* Binary File */
|
||||
|
||||
bin_file_t * bin_file_new (const char *filename);
|
||||
void bin_file_free (bin_file_t *bin_file);
|
||||
const bin_symbol_t *bin_file_lookup_symbol (bin_file_t *bin_file,
|
||||
gulong address);
|
||||
gboolean bin_file_check_inode (bin_file_t *bin_file,
|
||||
ino_t inode);
|
||||
const char * bin_symbol_get_name (bin_file_t *bin_file,
|
||||
const bin_symbol_t *symbol);
|
||||
gulong bin_symbol_get_address (bin_file_t *bin_file,
|
||||
const bin_symbol_t *symbol);
|
||||
|
||||
#endif
|
||||
40
src/libsysprof/demangle.cpp
Normal file
40
src/libsysprof/demangle.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
/* demangle.cpp
|
||||
*
|
||||
* Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "demangle.h"
|
||||
|
||||
gchar *
|
||||
sysprof_cplus_demangle (const gchar *name)
|
||||
{
|
||||
char *real_name;
|
||||
gchar *ret;
|
||||
int status;
|
||||
|
||||
real_name = abi::__cxa_demangle (name, 0, 0, &status);
|
||||
|
||||
if (real_name == NULL)
|
||||
return NULL;
|
||||
|
||||
ret = g_strdup (real_name);
|
||||
free (real_name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
30
src/libsysprof/demangle.h
Normal file
30
src/libsysprof/demangle.h
Normal file
@ -0,0 +1,30 @@
|
||||
/* demangle.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DEMANGLE_H
|
||||
#define DEMANGLE_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gchar *sysprof_cplus_demangle (const gchar *name);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* DEMANGLE_H */
|
||||
811
src/libsysprof/elfparser.c
Normal file
811
src/libsysprof/elfparser.c
Normal file
@ -0,0 +1,811 @@
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2006, 2007, Soeren Sandmann
|
||||
*
|
||||
* 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <elf.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "demangle.h"
|
||||
#include "elfparser.h"
|
||||
|
||||
typedef struct Section Section;
|
||||
|
||||
struct ElfSym
|
||||
{
|
||||
gulong table;
|
||||
gulong offset;
|
||||
gulong address;
|
||||
};
|
||||
|
||||
struct Section
|
||||
{
|
||||
const gchar * name;
|
||||
gsize offset;
|
||||
gsize size;
|
||||
gboolean allocated;
|
||||
gulong load_address;
|
||||
guint type;
|
||||
};
|
||||
|
||||
struct ElfParser
|
||||
{
|
||||
gboolean is_64;
|
||||
const guchar * data;
|
||||
gsize length;
|
||||
|
||||
guint n_sections;
|
||||
Section ** sections;
|
||||
|
||||
guint n_symbols;
|
||||
ElfSym * symbols;
|
||||
gsize sym_strings;
|
||||
|
||||
GMappedFile * file;
|
||||
|
||||
char * filename;
|
||||
|
||||
gboolean checked_build_id;
|
||||
char * build_id;
|
||||
|
||||
const Section * text_section;
|
||||
};
|
||||
|
||||
/* FIXME: All of these should in principle do endian swapping,
|
||||
* but sysprof never has to deal with binaries of a different
|
||||
* endianness than sysprof itself
|
||||
*/
|
||||
#define GET_FIELD(parser, offset, struct_name, idx, field_name) \
|
||||
(((parser))->is_64? \
|
||||
((Elf64_ ## struct_name *)(gpointer)(((parser)->data + offset)) + (idx))->field_name : \
|
||||
((Elf32_ ## struct_name *)(gpointer)(((parser)->data + offset)) + (idx))->field_name)
|
||||
|
||||
#define GET_UINT32(parser, offset) \
|
||||
*((uint32_t *)(gpointer)(parser->data + offset)) \
|
||||
|
||||
#define GET_SIZE(parser, struct_name) \
|
||||
(((parser)->is_64? \
|
||||
sizeof (Elf64_ ## struct_name) : \
|
||||
sizeof (Elf32_ ## struct_name)))
|
||||
|
||||
#define MAKE_ELF_UINT_ACCESSOR(field_name) \
|
||||
static uint64_t field_name (ElfParser *parser) \
|
||||
{ \
|
||||
return GET_FIELD (parser, 0, Ehdr, 0, field_name); \
|
||||
}
|
||||
|
||||
MAKE_ELF_UINT_ACCESSOR (e_shoff)
|
||||
MAKE_ELF_UINT_ACCESSOR (e_shnum)
|
||||
MAKE_ELF_UINT_ACCESSOR (e_shstrndx)
|
||||
|
||||
#define MAKE_SECTION_HEADER_ACCESSOR(field_name) \
|
||||
static uint64_t field_name (ElfParser *parser, int nth_section) \
|
||||
{ \
|
||||
gsize offset = e_shoff (parser); \
|
||||
\
|
||||
return GET_FIELD (parser, offset, Shdr, nth_section, field_name); \
|
||||
}
|
||||
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_name);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_type);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_flags);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_addr);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_offset);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_size);
|
||||
|
||||
#define MAKE_SYMBOL_ACCESSOR(field_name) \
|
||||
static uint64_t field_name (ElfParser *parser, gulong offset, gulong nth) \
|
||||
{ \
|
||||
return GET_FIELD (parser, offset, Sym, nth, field_name); \
|
||||
}
|
||||
|
||||
MAKE_SYMBOL_ACCESSOR(st_name);
|
||||
MAKE_SYMBOL_ACCESSOR(st_info);
|
||||
MAKE_SYMBOL_ACCESSOR(st_value);
|
||||
MAKE_SYMBOL_ACCESSOR(st_size);
|
||||
MAKE_SYMBOL_ACCESSOR(st_shndx);
|
||||
|
||||
static void
|
||||
section_free (Section *section)
|
||||
{
|
||||
g_free (section);
|
||||
}
|
||||
|
||||
static const Section *
|
||||
find_section (ElfParser *parser,
|
||||
const char *name,
|
||||
guint type)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < parser->n_sections; ++i)
|
||||
{
|
||||
Section *section = parser->sections[i];
|
||||
|
||||
if (strcmp (section->name, name) == 0 && section->type == type)
|
||||
return section;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_elf_signature (const guchar *data,
|
||||
gsize length,
|
||||
gboolean *is_64,
|
||||
gboolean *is_be)
|
||||
{
|
||||
/* FIXME: this function should be able to return an error */
|
||||
if (length < EI_NIDENT)
|
||||
{
|
||||
/* FIXME set error */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (data[EI_CLASS] != ELFCLASS32 &&
|
||||
data[EI_CLASS] != ELFCLASS64)
|
||||
{
|
||||
/* FIXME set error */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (data[EI_DATA] != ELFDATA2LSB &&
|
||||
data[EI_DATA] != ELFDATA2MSB)
|
||||
{
|
||||
/* FIXME set error */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (is_64)
|
||||
*is_64 = (data[EI_CLASS] == ELFCLASS64);
|
||||
|
||||
if (is_be)
|
||||
*is_be = (data[EI_DATA] == ELFDATA2MSB);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
ElfParser *
|
||||
elf_parser_new_from_data (const guchar *data,
|
||||
gsize length)
|
||||
{
|
||||
ElfParser *parser;
|
||||
gboolean is_64, is_big_endian;
|
||||
int section_names_idx;
|
||||
const guchar *section_names;
|
||||
G_GNUC_UNUSED gsize section_headers;
|
||||
guint i;
|
||||
|
||||
if (!parse_elf_signature (data, length, &is_64, &is_big_endian))
|
||||
{
|
||||
/* FIXME: set error */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
parser = g_new0 (ElfParser, 1);
|
||||
|
||||
parser->is_64 = is_64;
|
||||
parser->data = data;
|
||||
parser->length = length;
|
||||
|
||||
#if 0
|
||||
g_print (" new parser : %p\n", parser);
|
||||
#endif
|
||||
|
||||
/* Read ELF header */
|
||||
|
||||
parser->n_sections = e_shnum (parser);
|
||||
section_names_idx = e_shstrndx (parser);
|
||||
section_headers = e_shoff (parser);
|
||||
|
||||
/* Read section headers */
|
||||
parser->sections = g_new0 (Section *, parser->n_sections);
|
||||
|
||||
section_names = parser->data + sh_offset (parser, section_names_idx);
|
||||
|
||||
for (i = 0; i < parser->n_sections; ++i)
|
||||
{
|
||||
Section *section = g_new (Section, 1);
|
||||
|
||||
section->name = (char *)(section_names + sh_name (parser, i));
|
||||
section->size = sh_size (parser, i);
|
||||
section->offset = sh_offset (parser, i);
|
||||
section->allocated = !!(sh_flags (parser, i) & SHF_ALLOC);
|
||||
|
||||
if (section->allocated)
|
||||
section->load_address = sh_addr (parser, i);
|
||||
else
|
||||
section->load_address = 0;
|
||||
|
||||
section->type = sh_type (parser, i);
|
||||
|
||||
parser->sections[i] = section;
|
||||
}
|
||||
|
||||
/* Cache the text section */
|
||||
parser->text_section = find_section (parser, ".text", SHT_PROGBITS);
|
||||
if (!parser->text_section)
|
||||
parser->text_section = find_section (parser, ".text", SHT_NOBITS);
|
||||
|
||||
parser->filename = NULL;
|
||||
parser->build_id = NULL;
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
ElfParser *
|
||||
elf_parser_new (const char *filename,
|
||||
GError **err)
|
||||
{
|
||||
const guchar *data;
|
||||
gsize length;
|
||||
ElfParser *parser;
|
||||
|
||||
GMappedFile *file = g_mapped_file_new (filename, FALSE, NULL);
|
||||
|
||||
if (!file)
|
||||
return NULL;
|
||||
|
||||
#if 0
|
||||
g_print ("elf parser new : %s\n", filename);
|
||||
#endif
|
||||
|
||||
data = (guchar *)g_mapped_file_get_contents (file);
|
||||
length = g_mapped_file_get_length (file);
|
||||
|
||||
#if 0
|
||||
g_print ("data %p: for %s\n", data, filename);
|
||||
#endif
|
||||
|
||||
parser = elf_parser_new_from_data (data, length);
|
||||
|
||||
#if 0
|
||||
g_print ("Parser for %s: %p\n", filename, parser);
|
||||
#endif
|
||||
|
||||
if (!parser)
|
||||
{
|
||||
g_mapped_file_unref (file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
parser->filename = g_strdup (filename);
|
||||
|
||||
parser->file = file;
|
||||
|
||||
#if 0
|
||||
g_print ("Elf file: %s (debug: %s)\n",
|
||||
filename, elf_parser_get_debug_link (parser, NULL));
|
||||
|
||||
if (!parser->symbols)
|
||||
g_print ("at this point %s has no symbols\n", filename);
|
||||
#endif
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
guint32
|
||||
elf_parser_get_crc32 (ElfParser *parser)
|
||||
{
|
||||
static const unsigned long crc32_table[256] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
};
|
||||
const guchar *data;
|
||||
gsize length;
|
||||
gulong crc;
|
||||
gsize i;
|
||||
|
||||
data = parser->data;
|
||||
length = parser->length;
|
||||
|
||||
crc = 0xffffffff;
|
||||
|
||||
madvise ((char *)data, length, MADV_SEQUENTIAL);
|
||||
|
||||
for (i = 0; i < length; ++i)
|
||||
crc = crc32_table[(crc ^ data[i]) & 0xff] ^ (crc >> 8);
|
||||
|
||||
/* We just read the entire file into memory, but we only really
|
||||
* need the symbol table, so swap the whole thing out.
|
||||
*
|
||||
* We could be more exact here, but it's only a few minor
|
||||
* pagefaults.
|
||||
*/
|
||||
if (parser->file)
|
||||
madvise ((char *)data, length, MADV_DONTNEED);
|
||||
|
||||
return ~crc & 0xffffffff;
|
||||
}
|
||||
|
||||
void
|
||||
elf_parser_free (ElfParser *parser)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < parser->n_sections; ++i)
|
||||
section_free (parser->sections[i]);
|
||||
g_free (parser->sections);
|
||||
|
||||
if (parser->file)
|
||||
g_mapped_file_unref (parser->file);
|
||||
|
||||
g_free (parser->symbols);
|
||||
|
||||
if (parser->filename)
|
||||
g_free (parser->filename);
|
||||
|
||||
if (parser->build_id)
|
||||
g_free (parser->build_id);
|
||||
|
||||
g_free (parser);
|
||||
}
|
||||
|
||||
gchar *
|
||||
elf_demangle (const char *name)
|
||||
{
|
||||
gchar *demangled = sysprof_cplus_demangle (name);
|
||||
|
||||
if (demangled)
|
||||
return demangled;
|
||||
else
|
||||
return g_strdup (name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Looking up symbols
|
||||
*/
|
||||
static int
|
||||
compare_sym (const void *a, const void *b)
|
||||
{
|
||||
const ElfSym *sym_a = a;
|
||||
const ElfSym *sym_b = b;
|
||||
|
||||
if (sym_a->address < sym_b->address)
|
||||
return -1;
|
||||
else if (sym_a->address == sym_b->address)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
dump_symbols (ElfParser *parser, ElfSym *syms, guint n_syms)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n_syms; ++i)
|
||||
{
|
||||
ElfSym *s = &(syms[i]);
|
||||
|
||||
g_print (" %s: %lx\n", elf_parser_get_sym_name (parser, s), s->address);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
read_table (ElfParser *parser,
|
||||
const Section *sym_table,
|
||||
const Section *str_table)
|
||||
{
|
||||
int sym_size = GET_SIZE (parser, Sym);
|
||||
guint i, n_symbols;
|
||||
|
||||
#if 0
|
||||
g_print ("elf: Reading table for %s\n", parser->filename? parser->filename : "<unknown>");
|
||||
#endif
|
||||
|
||||
parser->n_symbols = sym_table->size / sym_size;
|
||||
parser->symbols = g_new (ElfSym, parser->n_symbols);
|
||||
|
||||
#if 0
|
||||
g_print ("sym table offset: %d\n", sym_table->offset);
|
||||
#endif
|
||||
|
||||
n_symbols = 0;
|
||||
#if 0
|
||||
g_print ("n syms: %d\n", parser->n_symbols);
|
||||
#endif
|
||||
for (i = 0; i < parser->n_symbols; ++i)
|
||||
{
|
||||
guint info;
|
||||
gulong addr;
|
||||
gulong shndx;
|
||||
|
||||
info = st_info (parser, sym_table->offset, i);
|
||||
addr = st_value (parser, sym_table->offset, i);
|
||||
shndx = st_shndx (parser, sym_table->offset, i);
|
||||
|
||||
#if 0
|
||||
g_print ("read symbol: %s (section: %d)\n", get_string_indirct (parser->parser,
|
||||
parser->sym_format, "st_name",
|
||||
str_table->offset),
|
||||
shndx);
|
||||
#endif
|
||||
|
||||
if (addr != 0 &&
|
||||
shndx < parser->n_sections &&
|
||||
parser->sections[shndx] == parser->text_section &&
|
||||
(info & 0xf) == STT_FUNC &&
|
||||
((info >> 4) == STB_GLOBAL ||
|
||||
(info >> 4) == STB_LOCAL ||
|
||||
(info >> 4) == STB_WEAK))
|
||||
{
|
||||
parser->symbols[n_symbols].address = addr;
|
||||
parser->symbols[n_symbols].table = sym_table->offset;
|
||||
parser->symbols[n_symbols].offset = i;
|
||||
|
||||
n_symbols++;
|
||||
|
||||
#if 0
|
||||
g_print (" symbol: %s: %lx\n",
|
||||
get_string_indirect (parser->parser,
|
||||
parser->sym_format, "st_name",
|
||||
str_table->offset),
|
||||
addr - parser->text_section->load_address);
|
||||
g_print (" sym %d in %p (info: %d:%d) (func:global %d:%d)\n",
|
||||
addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL);
|
||||
#endif
|
||||
}
|
||||
else if (addr != 0)
|
||||
{
|
||||
#if 0
|
||||
g_print (" rejecting %d in %p (info: %d:%d) (func:global %d:%d)\n",
|
||||
addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
parser->sym_strings = str_table->offset;
|
||||
parser->n_symbols = n_symbols;
|
||||
|
||||
/* Allocate space for at least one symbol, so that parser->symbols will be
|
||||
* non-NULL. If it ends up being NULL, we will be parsing the file over and
|
||||
* over.
|
||||
*/
|
||||
parser->symbols = g_renew (ElfSym, parser->symbols, parser->n_symbols + 1);
|
||||
|
||||
qsort (parser->symbols, parser->n_symbols, sizeof (ElfSym), compare_sym);
|
||||
}
|
||||
|
||||
static void
|
||||
read_symbols (ElfParser *parser)
|
||||
{
|
||||
const Section *symtab = find_section (parser, ".symtab", SHT_SYMTAB);
|
||||
const Section *strtab = find_section (parser, ".strtab", SHT_STRTAB);
|
||||
const Section *dynsym = find_section (parser, ".dynsym", SHT_DYNSYM);
|
||||
const Section *dynstr = find_section (parser, ".dynstr", SHT_STRTAB);
|
||||
|
||||
if (symtab && strtab)
|
||||
{
|
||||
#if 0
|
||||
g_print ("reading symbol table of %s\n", parser->filename);
|
||||
#endif
|
||||
read_table (parser, symtab, strtab);
|
||||
}
|
||||
else if (dynsym && dynstr)
|
||||
{
|
||||
#if 0
|
||||
g_print ("reading dynamic symbol table of %s\n", parser->filename);
|
||||
#endif
|
||||
read_table (parser, dynsym, dynstr);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* To make sure parser->symbols is non-NULL */
|
||||
parser->n_symbols = 0;
|
||||
parser->symbols = g_new (ElfSym, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static ElfSym *
|
||||
do_lookup (ElfSym *symbols,
|
||||
gulong address,
|
||||
int first,
|
||||
int last)
|
||||
{
|
||||
if (address >= symbols[last].address)
|
||||
{
|
||||
return &(symbols[last]);
|
||||
}
|
||||
else if (last - first < 3)
|
||||
{
|
||||
while (last >= first)
|
||||
{
|
||||
if (address >= symbols[last].address)
|
||||
return &(symbols[last]);
|
||||
|
||||
last--;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
int mid = (first + last) / 2;
|
||||
|
||||
if (symbols[mid].address > address)
|
||||
return do_lookup (symbols, address, first, mid);
|
||||
else
|
||||
return do_lookup (symbols, address, mid, last);
|
||||
}
|
||||
}
|
||||
|
||||
/* Address should be given in 'offset into text segment' */
|
||||
const ElfSym *
|
||||
elf_parser_lookup_symbol (ElfParser *parser,
|
||||
gulong address)
|
||||
{
|
||||
const ElfSym *result;
|
||||
|
||||
if (!parser->symbols)
|
||||
{
|
||||
#if 0
|
||||
g_print ("reading symbols at %p\n", parser);
|
||||
#endif
|
||||
read_symbols (parser);
|
||||
}
|
||||
|
||||
if (parser->n_symbols == 0)
|
||||
return NULL;
|
||||
|
||||
if (!parser->text_section)
|
||||
return NULL;
|
||||
|
||||
address += parser->text_section->load_address;
|
||||
|
||||
#if 0
|
||||
g_print ("elf: the address we are looking up is %p\n", address);
|
||||
#endif
|
||||
|
||||
result = do_lookup (parser->symbols, address, 0, parser->n_symbols - 1);
|
||||
|
||||
#if 0
|
||||
if (result)
|
||||
{
|
||||
g_print (" elf: found %s at %lx\n", elf_parser_get_sym_name (parser, result), result->address);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_print ("elf: not found\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (result)
|
||||
{
|
||||
gulong size = st_size (parser, result->table, result->offset);
|
||||
|
||||
if (size > 0 && result->address + size <= address)
|
||||
{
|
||||
#if 0
|
||||
g_print (" elf: ends at %lx, so rejecting\n",
|
||||
result->address + size);
|
||||
#endif
|
||||
result = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (result)
|
||||
{
|
||||
/* Reject the symbols if the address is outside the text section */
|
||||
if (address > parser->text_section->load_address + parser->text_section->size)
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
gulong
|
||||
elf_parser_get_text_offset (ElfParser *parser)
|
||||
{
|
||||
g_return_val_if_fail (parser != NULL, (gulong)-1);
|
||||
|
||||
if (!parser->text_section)
|
||||
return (gulong)-1;
|
||||
|
||||
return parser->text_section->offset;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
make_hex_string (const guchar *data, int n_bytes)
|
||||
{
|
||||
static const char hex_digits[] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
GString *string = g_string_new (NULL);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n_bytes; ++i)
|
||||
{
|
||||
char c = data[i];
|
||||
|
||||
g_string_append_c (string, hex_digits[(c & 0xf0) >> 4]);
|
||||
g_string_append_c (string, hex_digits[(c & 0x0f)]);
|
||||
}
|
||||
|
||||
return g_string_free (string, FALSE);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
elf_parser_get_build_id (ElfParser *parser)
|
||||
{
|
||||
if (!parser->checked_build_id)
|
||||
{
|
||||
const Section *build_id =
|
||||
find_section (parser, ".note.gnu.build-id", SHT_NOTE);
|
||||
guint64 name_size;
|
||||
guint64 desc_size;
|
||||
guint64 type;
|
||||
const char *name;
|
||||
guint64 offset;
|
||||
|
||||
parser->checked_build_id = TRUE;
|
||||
|
||||
if (!build_id)
|
||||
return NULL;
|
||||
|
||||
offset = build_id->offset;
|
||||
|
||||
name_size = GET_FIELD (parser, offset, Nhdr, 0, n_namesz);
|
||||
desc_size = GET_FIELD (parser, offset, Nhdr, 0, n_descsz);
|
||||
type = GET_FIELD (parser, offset, Nhdr, 0, n_type);
|
||||
|
||||
offset += GET_SIZE (parser, Nhdr);
|
||||
|
||||
name = (char *)(parser->data + offset);
|
||||
|
||||
if (strncmp (name, ELF_NOTE_GNU, name_size) != 0 || type != NT_GNU_BUILD_ID)
|
||||
return NULL;
|
||||
|
||||
offset += strlen (name);
|
||||
|
||||
offset = (offset + 3) & (~0x3);
|
||||
|
||||
parser->build_id = make_hex_string (parser->data + offset, desc_size);
|
||||
}
|
||||
|
||||
return parser->build_id;
|
||||
}
|
||||
|
||||
const char *
|
||||
elf_parser_get_debug_link (ElfParser *parser, guint32 *crc32)
|
||||
{
|
||||
guint64 offset;
|
||||
const Section *debug_link = find_section (parser, ".gnu_debuglink",
|
||||
SHT_PROGBITS);
|
||||
const gchar *result;
|
||||
|
||||
if (!debug_link)
|
||||
return NULL;
|
||||
|
||||
offset = debug_link->offset;
|
||||
|
||||
result = (char *)(parser->data + offset);
|
||||
|
||||
if (crc32)
|
||||
{
|
||||
int len = strlen (result) + 1;
|
||||
offset = (offset + len + 3) & ~0x3;
|
||||
|
||||
*crc32 = GET_UINT32 (parser, offset);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const guchar *
|
||||
get_section (ElfParser *parser,
|
||||
const char *name)
|
||||
{
|
||||
const Section *section = find_section (parser, name, SHT_PROGBITS);
|
||||
|
||||
if (section)
|
||||
return parser->data + section->offset;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const guchar *
|
||||
elf_parser_get_eh_frame (ElfParser *parser)
|
||||
{
|
||||
return get_section (parser, ".eh_frame");
|
||||
}
|
||||
|
||||
const guchar *
|
||||
elf_parser_get_debug_frame (ElfParser *parser)
|
||||
{
|
||||
return get_section (parser, ".debug_frame");
|
||||
}
|
||||
|
||||
const char *
|
||||
elf_parser_get_sym_name (ElfParser *parser,
|
||||
const ElfSym *sym)
|
||||
{
|
||||
g_return_val_if_fail (parser != NULL, NULL);
|
||||
|
||||
return (char *)(parser->data + parser->sym_strings +
|
||||
st_name (parser, sym->table, sym->offset));
|
||||
}
|
||||
|
||||
gboolean
|
||||
elf_parser_owns_symbol (ElfParser *parser,
|
||||
const ElfSym *sym)
|
||||
{
|
||||
ElfSym *first, *last;
|
||||
|
||||
if (!parser->n_symbols)
|
||||
return FALSE;
|
||||
|
||||
first = parser->symbols;
|
||||
last = parser->symbols + parser->n_symbols - 1;
|
||||
|
||||
return first <= sym && sym <= last;
|
||||
}
|
||||
|
||||
gulong
|
||||
elf_parser_get_sym_address (ElfParser *parser,
|
||||
const ElfSym *sym)
|
||||
{
|
||||
return sym->address - parser->text_section->load_address;
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions
|
||||
*/
|
||||
57
src/libsysprof/elfparser.h
Normal file
57
src/libsysprof/elfparser.h
Normal file
@ -0,0 +1,57 @@
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2006, 2007, Soeren Sandmann
|
||||
*
|
||||
* 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#include <glib.h>
|
||||
|
||||
typedef struct ElfSym ElfSym;
|
||||
typedef struct ElfParser ElfParser;
|
||||
|
||||
ElfParser * elf_parser_new_from_data (const guchar *data,
|
||||
gsize length);
|
||||
ElfParser * elf_parser_new (const char *filename,
|
||||
GError **err);
|
||||
void elf_parser_free (ElfParser *parser);
|
||||
const char * elf_parser_get_debug_link (ElfParser *parser,
|
||||
guint32 *crc32);
|
||||
const gchar *elf_parser_get_build_id (ElfParser *parser);
|
||||
const guchar *elf_parser_get_eh_frame (ElfParser *parser);
|
||||
const guchar *elf_parser_get_debug_frame (ElfParser *parser);
|
||||
gulong elf_parser_get_text_offset (ElfParser *parser);
|
||||
|
||||
|
||||
/* Lookup a symbol in the file.
|
||||
*
|
||||
* The symbol returned is const, so don't free it. It is valid until
|
||||
* elf_parser_free() is called on the parser.
|
||||
*
|
||||
* The address should be given in "file coordinates". This means that
|
||||
* if the file is mapped at address m and offset o, then an address a
|
||||
* should be looked up as "a - (m - o)". (m - o) is where the start
|
||||
* of the file would have been mapped, so a - (m - o) is the position
|
||||
* in the file of a.
|
||||
*/
|
||||
const ElfSym *elf_parser_lookup_symbol (ElfParser *parser,
|
||||
gulong address);
|
||||
guint32 elf_parser_get_crc32 (ElfParser *parser);
|
||||
const char * elf_parser_get_sym_name (ElfParser *parser,
|
||||
const ElfSym *sym);
|
||||
gulong elf_parser_get_sym_address (ElfParser *parser,
|
||||
const ElfSym *sym);
|
||||
gboolean elf_parser_owns_symbol (ElfParser *parser,
|
||||
const ElfSym *sym);
|
||||
char * elf_demangle (const char *name);
|
||||
|
||||
97
src/libsysprof/meson.build
Normal file
97
src/libsysprof/meson.build
Normal file
@ -0,0 +1,97 @@
|
||||
libsysprof_public_sources = [
|
||||
'sp-callgraph-profile.c',
|
||||
'sp-capture-gobject.c',
|
||||
'sp-elf-symbol-resolver.c',
|
||||
'sp-gjs-source.c',
|
||||
'sp-hostinfo-source.c',
|
||||
'sp-jitmap-symbol-resolver.c',
|
||||
'sp-kernel-symbol.c',
|
||||
'sp-kernel-symbol-resolver.c',
|
||||
'sp-local-profiler.c',
|
||||
'sp-map-lookaside.c',
|
||||
'sp-memory-source.c',
|
||||
'sp-perf-counter.c',
|
||||
'sp-perf-source.c',
|
||||
'sp-process-model.c',
|
||||
'sp-process-model-item.c',
|
||||
'sp-proc-source.c',
|
||||
'sp-profile.c',
|
||||
'sp-profiler.c',
|
||||
'sp-selection.c',
|
||||
'sp-source.c',
|
||||
'sp-symbol-dirs.c',
|
||||
'sp-symbol-resolver.c',
|
||||
]
|
||||
|
||||
libsysprof_public_headers = [
|
||||
'sp-callgraph-profile.h',
|
||||
'sp-callgraph-profile-private.h',
|
||||
'sp-capture-gobject.h',
|
||||
'sp-elf-symbol-resolver.h',
|
||||
'sp-gjs-source.h',
|
||||
'sp-hostinfo-source.h',
|
||||
'sp-jitmap-symbol-resolver.h',
|
||||
'sp-kernel-symbol.h',
|
||||
'sp-kernel-symbol-resolver.h',
|
||||
'sp-local-profiler.h',
|
||||
'sp-map-lookaside.h',
|
||||
'sp-memory-source.h',
|
||||
'sp-perf-counter.h',
|
||||
'sp-perf-source.h',
|
||||
'sp-process-model.h',
|
||||
'sp-process-model-item.h',
|
||||
'sp-proc-source.h',
|
||||
'sp-profile.h',
|
||||
'sp-profiler.h',
|
||||
'sp-selection.h',
|
||||
'sp-source.h',
|
||||
'sp-symbol-dirs.h',
|
||||
'sp-symbol-resolver.h',
|
||||
'sysprof.h',
|
||||
]
|
||||
|
||||
libsysprof_private_sources = [
|
||||
'binfile.c',
|
||||
'demangle.cpp',
|
||||
'elfparser.c',
|
||||
'stackstash.c',
|
||||
'sp-source-util.c',
|
||||
]
|
||||
|
||||
libsysprof_deps = [
|
||||
cxx.find_library('stdc++'),
|
||||
dependency('gio-2.0', version: glib_req_version),
|
||||
dependency('gio-unix-2.0', version: glib_req_version),
|
||||
libsysprof_capture_dep,
|
||||
libshared_dep,
|
||||
]
|
||||
|
||||
if get_option('with_sysprofd') != 'none'
|
||||
libsysprof_deps += dependency('polkit-gobject-1')
|
||||
endif
|
||||
|
||||
libsysprof = shared_library('sysprof-@0@'.format(libsysprof_api_version),
|
||||
libsysprof_public_sources + libsysprof_private_sources,
|
||||
dependencies: libsysprof_deps,
|
||||
c_args: [ '-DSYSPROF_COMPILATION' ],
|
||||
install: true,
|
||||
install_dir: get_option('libdir'),
|
||||
)
|
||||
|
||||
libsysprof_dep = declare_dependency(
|
||||
link_with: libsysprof,
|
||||
dependencies: libsysprof_deps,
|
||||
include_directories: include_directories('.'),
|
||||
)
|
||||
|
||||
pkgconfig.generate(
|
||||
subdirs: [ sysprof_header_subdir ],
|
||||
version: meson.project_version(),
|
||||
name: 'sysprof-@0@'.format(libsysprof_api_version),
|
||||
filebase: 'sysprof-@0@'.format(libsysprof_api_version),
|
||||
description: 'The library for console applications embedding sysprof',
|
||||
install_dir: join_paths(get_option('libdir'), 'pkgconfig'),
|
||||
requires: [ 'gio-2.0' ],
|
||||
)
|
||||
|
||||
install_headers(libsysprof_public_headers, subdir: sysprof_header_subdir)
|
||||
31
src/libsysprof/sp-callgraph-profile-private.h
Normal file
31
src/libsysprof/sp-callgraph-profile-private.h
Normal file
@ -0,0 +1,31 @@
|
||||
/* sp-callgraph-profile-private.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_CALLGRAPH_PROFILE_PRIVATE_H
|
||||
#define SP_CALLGRAPH_PROFILE_PRIVATE_H
|
||||
|
||||
#include "sp-callgraph-profile.h"
|
||||
#include "stackstash.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
StackStash *sp_callgraph_profile_get_stash (SpCallgraphProfile *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_CALLGRAPH_PROFILE_PRIVATE_H */
|
||||
517
src/libsysprof/sp-callgraph-profile.c
Normal file
517
src/libsysprof/sp-callgraph-profile.c
Normal file
@ -0,0 +1,517 @@
|
||||
/* sp-callgraph-profile.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2009-2012 Soeren Sandmann and others
|
||||
*
|
||||
* 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 2 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <string.h>
|
||||
#include <sysprof-capture.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-callgraph-profile.h"
|
||||
#include "sp-callgraph-profile-private.h"
|
||||
#include "sp-capture-reader.h"
|
||||
#include "sp-elf-symbol-resolver.h"
|
||||
#include "sp-jitmap-symbol-resolver.h"
|
||||
#include "sp-map-lookaside.h"
|
||||
#include "sp-kernel-symbol-resolver.h"
|
||||
#include "sp-selection.h"
|
||||
#include "stackstash.h"
|
||||
|
||||
#define CHECK_CANCELLABLE_INTERVAL 100
|
||||
|
||||
struct _SpCallgraphProfile
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
SpCaptureReader *reader;
|
||||
SpSelection *selection;
|
||||
StackStash *stash;
|
||||
GStringChunk *symbols;
|
||||
GHashTable *tags;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureReader *reader;
|
||||
SpSelection *selection;
|
||||
} Generate;
|
||||
|
||||
static void profile_iface_init (SpProfileInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpCallgraphProfile, sp_callgraph_profile, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_PROFILE, profile_iface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_SELECTION,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
SpProfile *
|
||||
sp_callgraph_profile_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_CALLGRAPH_PROFILE, NULL);
|
||||
}
|
||||
|
||||
SpProfile *
|
||||
sp_callgraph_profile_new_with_selection (SpSelection *selection)
|
||||
{
|
||||
return g_object_new (SP_TYPE_CALLGRAPH_PROFILE,
|
||||
"selection", selection,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_finalize (GObject *object)
|
||||
{
|
||||
SpCallgraphProfile *self = (SpCallgraphProfile *)object;
|
||||
|
||||
g_clear_pointer (&self->symbols, g_string_chunk_free);
|
||||
g_clear_pointer (&self->stash, stack_stash_unref);
|
||||
g_clear_pointer (&self->reader, sp_capture_reader_unref);
|
||||
g_clear_pointer (&self->tags, g_hash_table_unref);
|
||||
g_clear_object (&self->selection);
|
||||
|
||||
G_OBJECT_CLASS (sp_callgraph_profile_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpCallgraphProfile *self = SP_CALLGRAPH_PROFILE (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_SELECTION:
|
||||
g_value_set_object (value, self->selection);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpCallgraphProfile *self = SP_CALLGRAPH_PROFILE (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_SELECTION:
|
||||
self->selection = g_value_dup_object (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_class_init (SpCallgraphProfileClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_callgraph_profile_finalize;
|
||||
object_class->get_property = sp_callgraph_profile_get_property;
|
||||
object_class->set_property = sp_callgraph_profile_set_property;
|
||||
|
||||
properties [PROP_SELECTION] =
|
||||
g_param_spec_object ("selection",
|
||||
"Selection",
|
||||
"The selection for filtering the callgraph",
|
||||
SP_TYPE_SELECTION,
|
||||
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_init (SpCallgraphProfile *self)
|
||||
{
|
||||
self->symbols = g_string_chunk_new (getpagesize ());
|
||||
self->tags = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_set_reader (SpProfile *profile,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
|
||||
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (reader != NULL);
|
||||
|
||||
g_clear_pointer (&self->reader, sp_capture_reader_unref);
|
||||
self->reader = sp_capture_reader_ref (reader);
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
sp_callgraph_profile_intern_string_take (SpCallgraphProfile *self,
|
||||
gchar *str)
|
||||
{
|
||||
const gchar *ret;
|
||||
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (str != NULL);
|
||||
|
||||
ret = g_string_chunk_insert_const (self->symbols, str);
|
||||
g_free (str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
sp_callgraph_profile_intern_string (SpCallgraphProfile *self,
|
||||
const gchar *str)
|
||||
{
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (str != NULL);
|
||||
|
||||
return g_string_chunk_insert_const (self->symbols, str);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_generate_worker (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
SpCallgraphProfile *self = source_object;
|
||||
Generate *gen = task_data;
|
||||
SpCaptureReader *reader;
|
||||
SpSelection *selection;
|
||||
g_autoptr(GArray) resolved = NULL;
|
||||
g_autoptr(GHashTable) maps_by_pid = NULL;
|
||||
g_autoptr(GHashTable) cmdlines = NULL;
|
||||
g_autoptr(GPtrArray) resolvers = NULL;
|
||||
SpCaptureFrameType type;
|
||||
StackStash *stash = NULL;
|
||||
StackStash *resolved_stash = NULL;
|
||||
guint count = 0;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
g_assert (gen != NULL);
|
||||
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
reader = gen->reader;
|
||||
selection = gen->selection;
|
||||
|
||||
maps_by_pid = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)sp_map_lookaside_free);
|
||||
cmdlines = g_hash_table_new (NULL, NULL);
|
||||
|
||||
stash = stack_stash_new (NULL);
|
||||
resolved_stash = stack_stash_new (NULL);
|
||||
|
||||
resolvers = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
g_ptr_array_add (resolvers, sp_kernel_symbol_resolver_new ());
|
||||
g_ptr_array_add (resolvers, sp_elf_symbol_resolver_new ());
|
||||
g_ptr_array_add (resolvers, sp_jitmap_symbol_resolver_new ());
|
||||
|
||||
for (guint j = 0; j < resolvers->len; j++)
|
||||
{
|
||||
SpSymbolResolver *resolver = g_ptr_array_index (resolvers, j);
|
||||
|
||||
sp_capture_reader_reset (reader);
|
||||
sp_symbol_resolver_load (resolver, reader);
|
||||
}
|
||||
|
||||
sp_capture_reader_reset (reader);
|
||||
|
||||
/*
|
||||
* The resolved pointer array is where we stash the names for the
|
||||
* instruction pointers to pass to the stash stack. All the strings
|
||||
* need to be deduplicated so that pointer comparison works as if we
|
||||
* did instruction-pointer comparison.
|
||||
*/
|
||||
resolved = g_array_new (FALSE, TRUE, sizeof (guint64));
|
||||
|
||||
while (sp_capture_reader_peek_type (reader, &type))
|
||||
{
|
||||
const SpCaptureProcess *pr;
|
||||
const gchar *cmdline;
|
||||
|
||||
if (type != SP_CAPTURE_FRAME_PROCESS)
|
||||
{
|
||||
if (!sp_capture_reader_skip (reader))
|
||||
goto failure;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NULL == (pr = sp_capture_reader_read_process (reader)))
|
||||
goto failure;
|
||||
|
||||
cmdline = g_strdup_printf ("[%s]", pr->cmdline);
|
||||
g_hash_table_insert (cmdlines,
|
||||
GINT_TO_POINTER (pr->frame.pid),
|
||||
(gchar *)sp_callgraph_profile_intern_string (self, cmdline));
|
||||
}
|
||||
|
||||
if (g_task_return_error_if_cancelled (task))
|
||||
goto cleanup;
|
||||
|
||||
sp_capture_reader_reset (reader);
|
||||
|
||||
/*
|
||||
* Walk through all of the sample events and resolve instruction-pointers
|
||||
* to symbol names by loading the particular map and extracting the symbol
|
||||
* name. If we wanted to support dynamic systems, we'd want to extend this
|
||||
* to parse information from captured data about the languages jit'd code.
|
||||
*/
|
||||
while (sp_capture_reader_peek_type (reader, &type))
|
||||
{
|
||||
SpAddressContext last_context = SP_ADDRESS_CONTEXT_NONE;
|
||||
const SpCaptureSample *sample;
|
||||
StackNode *node;
|
||||
StackNode *iter;
|
||||
const gchar *cmdline;
|
||||
guint len = 5;
|
||||
|
||||
if (type != SP_CAPTURE_FRAME_SAMPLE)
|
||||
{
|
||||
if (!sp_capture_reader_skip (reader))
|
||||
goto failure;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++count == CHECK_CANCELLABLE_INTERVAL)
|
||||
{
|
||||
if (g_task_return_error_if_cancelled (task))
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (NULL == (sample = sp_capture_reader_read_sample (reader)))
|
||||
goto failure;
|
||||
|
||||
if (!sp_selection_contains (selection, sample->frame.time))
|
||||
continue;
|
||||
|
||||
if (sample->n_addrs == 0)
|
||||
continue;
|
||||
|
||||
cmdline = g_hash_table_lookup (cmdlines, GINT_TO_POINTER (sample->frame.pid));
|
||||
|
||||
#if 0
|
||||
/* This assertion appears to hold true, but since we're taking in
|
||||
* untrusted data from capture files, it's not safe to assume. But in
|
||||
* practice it is.
|
||||
*/
|
||||
g_assert (sp_address_is_context_switch (sample->addrs[0], &last_context));
|
||||
last_context = SP_ADDRESS_CONTEXT_NONE;
|
||||
#endif
|
||||
|
||||
node = stack_stash_add_trace (stash, sample->addrs, sample->n_addrs, 1);
|
||||
|
||||
for (iter = node; iter != NULL; iter = iter->parent)
|
||||
len++;
|
||||
|
||||
if (G_UNLIKELY (resolved->len < len))
|
||||
g_array_set_size (resolved, len);
|
||||
|
||||
len = 0;
|
||||
|
||||
for (iter = node; iter != NULL; iter = iter->parent)
|
||||
{
|
||||
SpAddressContext context = SP_ADDRESS_CONTEXT_NONE;
|
||||
SpAddress address = iter->data;
|
||||
const gchar *symbol = NULL;
|
||||
|
||||
if (sp_address_is_context_switch (address, &context))
|
||||
{
|
||||
if (last_context)
|
||||
symbol = sp_address_context_to_string (last_context);
|
||||
else
|
||||
symbol = NULL;
|
||||
|
||||
last_context = context;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (guint j = 0; j < resolvers->len; j++)
|
||||
{
|
||||
SpSymbolResolver *resolver = g_ptr_array_index (resolvers, j);
|
||||
GQuark tag = 0;
|
||||
gchar *str;
|
||||
|
||||
str = sp_symbol_resolver_resolve_with_context (resolver,
|
||||
sample->frame.time,
|
||||
sample->frame.pid,
|
||||
last_context,
|
||||
address,
|
||||
&tag);
|
||||
|
||||
if (str != NULL)
|
||||
{
|
||||
symbol = sp_callgraph_profile_intern_string_take (self, str);
|
||||
if (tag != 0)
|
||||
g_hash_table_insert (self->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (symbol != NULL)
|
||||
g_array_index (resolved, SpAddress, len++) = POINTER_TO_U64 (symbol);
|
||||
}
|
||||
|
||||
if (last_context && last_context != SP_ADDRESS_CONTEXT_USER)
|
||||
{
|
||||
/* Kernel threads do not have a user part, so we end up here
|
||||
* without ever getting a user context. If this happens,
|
||||
* add the '- - kernel - - ' name, so that kernel threads
|
||||
* are properly blamed on the kernel
|
||||
*/
|
||||
const gchar *name = sp_address_context_to_string (last_context);
|
||||
g_array_index (resolved, SpAddress, len++) = POINTER_TO_U64 (name);
|
||||
}
|
||||
|
||||
if (cmdline != NULL)
|
||||
g_array_index (resolved, guint64, len++) = POINTER_TO_U64 (cmdline);
|
||||
|
||||
g_array_index (resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]");
|
||||
|
||||
stack_stash_add_trace (resolved_stash, (SpAddress *)(gpointer)resolved->data, len, 1);
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
|
||||
failure:
|
||||
|
||||
if (ret == FALSE)
|
||||
g_task_return_new_error (task,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"%s",
|
||||
_("Sysprof was unable to generate a callgraph from the system capture."));
|
||||
else
|
||||
g_task_return_pointer (task, g_steal_pointer (&resolved_stash), (GDestroyNotify)stack_stash_unref);
|
||||
|
||||
cleanup:
|
||||
g_clear_pointer (&resolved_stash, stack_stash_unref);
|
||||
g_clear_pointer (&stash, stack_stash_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
generate_free (Generate *generate)
|
||||
{
|
||||
sp_capture_reader_unref (generate->reader);
|
||||
g_clear_object (&generate->selection);
|
||||
g_slice_free (Generate, generate);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_generate (SpProfile *profile,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
|
||||
Generate *gen;
|
||||
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
gen = g_slice_new0 (Generate);
|
||||
gen->reader = sp_capture_reader_copy (self->reader);
|
||||
gen->selection = sp_selection_copy (self->selection);
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
g_task_set_task_data (task, gen, (GDestroyNotify)generate_free);
|
||||
g_task_run_in_thread (task, sp_callgraph_profile_generate_worker);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_callgraph_profile_generate_finish (SpProfile *profile,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
|
||||
StackStash *stash;
|
||||
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (G_IS_TASK (result));
|
||||
|
||||
stash = g_task_propagate_pointer (G_TASK (result), error);
|
||||
|
||||
if (stash != NULL)
|
||||
{
|
||||
if (stash != self->stash)
|
||||
{
|
||||
g_clear_pointer (&self->stash, stack_stash_unref);
|
||||
self->stash = g_steal_pointer (&stash);
|
||||
}
|
||||
|
||||
g_clear_pointer (&stash, stack_stash_unref);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
profile_iface_init (SpProfileInterface *iface)
|
||||
{
|
||||
iface->generate = sp_callgraph_profile_generate;
|
||||
iface->generate_finish = sp_callgraph_profile_generate_finish;
|
||||
iface->set_reader = sp_callgraph_profile_set_reader;
|
||||
}
|
||||
|
||||
StackStash *
|
||||
sp_callgraph_profile_get_stash (SpCallgraphProfile *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_CALLGRAPH_PROFILE (self), NULL);
|
||||
|
||||
return self->stash;
|
||||
}
|
||||
|
||||
GQuark
|
||||
sp_callgraph_profile_get_tag (SpCallgraphProfile *self,
|
||||
const gchar *symbol)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_CALLGRAPH_PROFILE (self), 0);
|
||||
|
||||
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tags, symbol));
|
||||
}
|
||||
38
src/libsysprof/sp-callgraph-profile.h
Normal file
38
src/libsysprof/sp-callgraph-profile.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* sp-callgraph-profile.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_CALLGRAPH_PROFILE_H
|
||||
#define SP_CALLGRAPH_PROFILE_H
|
||||
|
||||
#include "sp-profile.h"
|
||||
#include "sp-selection.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_CALLGRAPH_PROFILE (sp_callgraph_profile_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpCallgraphProfile, sp_callgraph_profile, SP, CALLGRAPH_PROFILE, GObject)
|
||||
|
||||
SpProfile *sp_callgraph_profile_new (void);
|
||||
SpProfile *sp_callgraph_profile_new_with_selection (SpSelection *selection);
|
||||
GQuark sp_callgraph_profile_get_tag (SpCallgraphProfile *self,
|
||||
const gchar *symbol);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_CALLGRAPH_PROFILE_H */
|
||||
25
src/libsysprof/sp-capture-gobject.c
Normal file
25
src/libsysprof/sp-capture-gobject.c
Normal file
@ -0,0 +1,25 @@
|
||||
/* sp-capture-gobject.c
|
||||
*
|
||||
* Copyright 2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "sp-capture-gobject.h"
|
||||
|
||||
G_DEFINE_BOXED_TYPE (SpCaptureReader, sp_capture_reader, (GBoxedCopyFunc)sp_capture_reader_ref, (GBoxedFreeFunc)sp_capture_reader_unref)
|
||||
G_DEFINE_BOXED_TYPE (SpCaptureWriter, sp_capture_writer, (GBoxedCopyFunc)sp_capture_writer_ref, (GBoxedFreeFunc)sp_capture_writer_unref)
|
||||
G_DEFINE_BOXED_TYPE (SpCaptureCursor, sp_capture_cursor, (GBoxedCopyFunc)sp_capture_cursor_ref, (GBoxedFreeFunc)sp_capture_cursor_unref)
|
||||
36
src/libsysprof/sp-capture-gobject.h
Normal file
36
src/libsysprof/sp-capture-gobject.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-capture-gobject.h
|
||||
*
|
||||
* Copyright 2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <sysprof-capture.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_CAPTURE_READER (sp_capture_reader_get_type())
|
||||
#define SP_TYPE_CAPTURE_WRITER (sp_capture_writer_get_type())
|
||||
#define SP_TYPE_CAPTURE_CURSOR (sp_capture_cursor_get_type())
|
||||
|
||||
GType sp_capture_reader_get_type (void);
|
||||
GType sp_capture_writer_get_type (void);
|
||||
GType sp_capture_cursor_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
310
src/libsysprof/sp-elf-symbol-resolver.c
Normal file
310
src/libsysprof/sp-elf-symbol-resolver.c
Normal file
@ -0,0 +1,310 @@
|
||||
/* sp-elf-symbol-resolver.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-elf-symbol-resolver.h"
|
||||
#include "binfile.h"
|
||||
#include "elfparser.h"
|
||||
#include "sp-map-lookaside.h"
|
||||
|
||||
struct _SpElfSymbolResolver
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GHashTable *lookasides;
|
||||
GHashTable *bin_files;
|
||||
GHashTable *tag_cache;
|
||||
};
|
||||
|
||||
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpElfSymbolResolver,
|
||||
sp_elf_symbol_resolver,
|
||||
G_TYPE_OBJECT,
|
||||
0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
|
||||
symbol_resolver_iface_init))
|
||||
|
||||
static void
|
||||
sp_elf_symbol_resolver_finalize (GObject *object)
|
||||
{
|
||||
SpElfSymbolResolver *self = (SpElfSymbolResolver *)object;
|
||||
|
||||
g_clear_pointer (&self->bin_files, g_hash_table_unref);
|
||||
g_clear_pointer (&self->lookasides, g_hash_table_unref);
|
||||
g_clear_pointer (&self->tag_cache, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_elf_symbol_resolver_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_elf_symbol_resolver_class_init (SpElfSymbolResolverClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_elf_symbol_resolver_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_elf_symbol_resolver_init (SpElfSymbolResolver *self)
|
||||
{
|
||||
self->lookasides = g_hash_table_new_full (NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
(GDestroyNotify)sp_map_lookaside_free);
|
||||
|
||||
self->bin_files = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
(GDestroyNotify)bin_file_free);
|
||||
|
||||
self->tag_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_elf_symbol_resolver_load (SpSymbolResolver *resolver,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpElfSymbolResolver *self = (SpElfSymbolResolver *)resolver;
|
||||
SpCaptureFrameType type;
|
||||
|
||||
g_assert (SP_IS_SYMBOL_RESOLVER (resolver));
|
||||
g_assert (reader != NULL);
|
||||
|
||||
sp_capture_reader_reset (reader);
|
||||
|
||||
while (sp_capture_reader_peek_type (reader, &type))
|
||||
{
|
||||
const SpCaptureMap *ev;
|
||||
SpMapLookaside *lookaside;
|
||||
SpMap map;
|
||||
|
||||
if (type != SP_CAPTURE_FRAME_MAP)
|
||||
{
|
||||
if (!sp_capture_reader_skip (reader))
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
ev = sp_capture_reader_read_map (reader);
|
||||
|
||||
map.start = ev->start;
|
||||
map.end = ev->end;
|
||||
map.offset = ev->offset;
|
||||
map.inode = ev->inode;
|
||||
map.filename = ev->filename;
|
||||
|
||||
lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (ev->frame.pid));
|
||||
|
||||
if (lookaside == NULL)
|
||||
{
|
||||
lookaside = sp_map_lookaside_new ();
|
||||
g_hash_table_insert (self->lookasides, GINT_TO_POINTER (ev->frame.pid), lookaside);
|
||||
}
|
||||
|
||||
sp_map_lookaside_insert (lookaside, &map);
|
||||
}
|
||||
}
|
||||
|
||||
static bin_file_t *
|
||||
sp_elf_symbol_resolver_get_bin_file (SpElfSymbolResolver *self,
|
||||
const gchar *filename)
|
||||
{
|
||||
bin_file_t *bin_file;
|
||||
|
||||
g_assert (SP_IS_ELF_SYMBOL_RESOLVER (self));
|
||||
|
||||
bin_file = g_hash_table_lookup (self->bin_files, filename);
|
||||
|
||||
if (bin_file == NULL)
|
||||
{
|
||||
const gchar *alternate = filename;
|
||||
|
||||
/*
|
||||
* If we are in a new mount namespace, then rely on the sp_symbol_dirs
|
||||
* to find us a locate to resolve the file where the CRC will match.
|
||||
*
|
||||
* TODO: We need to translate the path here so that we can locate the
|
||||
* binary behind it (which then has links to the debug file in
|
||||
* the section header).
|
||||
*/
|
||||
if (g_str_has_prefix (filename, "/newroot/"))
|
||||
alternate += strlen ("/newroot");
|
||||
|
||||
bin_file = bin_file_new (alternate);
|
||||
g_hash_table_insert (self->bin_files, g_strdup (filename), bin_file);
|
||||
}
|
||||
|
||||
return bin_file;
|
||||
}
|
||||
|
||||
static GQuark
|
||||
guess_tag (SpElfSymbolResolver *self,
|
||||
const SpMap *map)
|
||||
{
|
||||
g_assert (map != NULL);
|
||||
g_assert (map->filename != NULL);
|
||||
|
||||
if (!g_hash_table_contains (self->tag_cache, map->filename))
|
||||
{
|
||||
GQuark tag = 0;
|
||||
|
||||
if (strstr (map->filename, "/libgobject-2.0."))
|
||||
tag = g_quark_from_static_string ("GObject");
|
||||
|
||||
else if (strstr (map->filename, "/libglib-2.0."))
|
||||
tag = g_quark_from_static_string ("GLib");
|
||||
|
||||
else if (strstr (map->filename, "/libgio-2.0."))
|
||||
tag = g_quark_from_static_string ("Gio");
|
||||
|
||||
else if (strstr (map->filename, "/libgirepository-1.0."))
|
||||
tag = g_quark_from_static_string ("Introspection");
|
||||
|
||||
else if (strstr (map->filename, "/libgtk-3."))
|
||||
tag = g_quark_from_static_string ("Gtk+");
|
||||
|
||||
else if (strstr (map->filename, "/libgdk-3."))
|
||||
tag = g_quark_from_static_string ("Gdk");
|
||||
|
||||
else if (strstr (map->filename, "/libgtksourceview-3.0"))
|
||||
tag = g_quark_from_static_string ("GtkSourceView");
|
||||
|
||||
else if (strstr (map->filename, "/libpixman-1"))
|
||||
tag = g_quark_from_static_string ("Pixman");
|
||||
|
||||
else if (strstr (map->filename, "/libcairo."))
|
||||
tag = g_quark_from_static_string ("cairo");
|
||||
|
||||
else if (strstr (map->filename, "/libgstreamer-1."))
|
||||
tag = g_quark_from_static_string ("GStreamer");
|
||||
|
||||
else if (strstr (map->filename, "/libX11."))
|
||||
tag = g_quark_from_static_string ("X11");
|
||||
|
||||
else if (strstr (map->filename, "/libpango-1.0."))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libpangocairo-1.0."))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libpangomm-1.4."))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libpangoft2-1.0"))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libpangoxft-1.0."))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libclutter-"))
|
||||
tag = g_quark_from_static_string ("Clutter");
|
||||
|
||||
else if (strstr (map->filename, "/libcogl.") ||
|
||||
strstr (map->filename, "/libcogl-"))
|
||||
tag = g_quark_from_static_string ("Cogl");
|
||||
|
||||
else if (strstr (map->filename, "/libffi."))
|
||||
tag = g_quark_from_static_string ("libffi");
|
||||
|
||||
else if (strstr (map->filename, "/libwayland-"))
|
||||
tag = g_quark_from_static_string ("Wayland");
|
||||
|
||||
else if (strstr (map->filename, "/libinput."))
|
||||
tag = g_quark_from_static_string ("libinput");
|
||||
|
||||
else if (strstr (map->filename, "/libgjs."))
|
||||
tag = g_quark_from_static_string ("Gjs");
|
||||
|
||||
else if (strstr (map->filename, "/libmozjs-"))
|
||||
tag = g_quark_from_static_string ("MozJS");
|
||||
|
||||
else if (strstr (map->filename, "/libGL."))
|
||||
tag = g_quark_from_static_string ("GL");
|
||||
|
||||
else if (strstr (map->filename, "/libEGL."))
|
||||
tag = g_quark_from_static_string ("EGL");
|
||||
|
||||
g_hash_table_insert (self->tag_cache,
|
||||
g_strdup (map->filename),
|
||||
GSIZE_TO_POINTER (tag));
|
||||
}
|
||||
|
||||
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tag_cache, map->filename));
|
||||
}
|
||||
|
||||
static gchar *
|
||||
sp_elf_symbol_resolver_resolve_with_context (SpSymbolResolver *resolver,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpAddressContext context,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
SpElfSymbolResolver *self = (SpElfSymbolResolver *)resolver;
|
||||
const bin_symbol_t *bin_sym;
|
||||
SpMapLookaside *lookaside;
|
||||
const gchar *bin_sym_name;
|
||||
const SpMap *map;
|
||||
bin_file_t *bin_file;
|
||||
|
||||
g_assert (SP_IS_ELF_SYMBOL_RESOLVER (self));
|
||||
|
||||
if (context != SP_ADDRESS_CONTEXT_USER)
|
||||
return NULL;
|
||||
|
||||
lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (pid));
|
||||
if (lookaside == NULL)
|
||||
return NULL;
|
||||
|
||||
map = sp_map_lookaside_lookup (lookaside, address);
|
||||
if (map == NULL)
|
||||
return NULL;
|
||||
|
||||
address -= map->start;
|
||||
address += map->offset;
|
||||
|
||||
bin_file = sp_elf_symbol_resolver_get_bin_file (self, map->filename);
|
||||
|
||||
g_assert (bin_file != NULL);
|
||||
|
||||
if (map->inode && !bin_file_check_inode (bin_file, map->inode))
|
||||
return g_strdup_printf ("%s: inode mismatch", map->filename);
|
||||
|
||||
bin_sym = bin_file_lookup_symbol (bin_file, address);
|
||||
bin_sym_name = bin_symbol_get_name (bin_file, bin_sym);
|
||||
|
||||
if (map->filename)
|
||||
*tag = guess_tag (self, map);
|
||||
|
||||
return elf_demangle (bin_sym_name);
|
||||
}
|
||||
|
||||
static void
|
||||
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
|
||||
{
|
||||
iface->load = sp_elf_symbol_resolver_load;
|
||||
iface->resolve_with_context = sp_elf_symbol_resolver_resolve_with_context;
|
||||
}
|
||||
|
||||
SpSymbolResolver *
|
||||
sp_elf_symbol_resolver_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_ELF_SYMBOL_RESOLVER, NULL);
|
||||
}
|
||||
34
src/libsysprof/sp-elf-symbol-resolver.h
Normal file
34
src/libsysprof/sp-elf-symbol-resolver.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-elf-symbol-resolver.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_ELF_SYMBOL_RESOLVER_H
|
||||
#define SP_ELF_SYMBOL_RESOLVER_H
|
||||
|
||||
#include "sp-symbol-resolver.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_ELF_SYMBOL_RESOLVER (sp_elf_symbol_resolver_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpElfSymbolResolver, sp_elf_symbol_resolver, SP, ELF_SYMBOL_RESOLVER, GObject)
|
||||
|
||||
SpSymbolResolver *sp_elf_symbol_resolver_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_ELF_SYMBOL_RESOLVER_H */
|
||||
245
src/libsysprof/sp-gjs-source.c
Normal file
245
src/libsysprof/sp-gjs-source.c
Normal file
@ -0,0 +1,245 @@
|
||||
/* sp-gjs-source.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-capture-reader.h"
|
||||
#include "sp-gjs-source.h"
|
||||
|
||||
struct _SpGjsSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
SpCaptureWriter *writer;
|
||||
GArray *pids;
|
||||
GArray *enabled;
|
||||
};
|
||||
|
||||
#define ENABLE_PROFILER 0x1
|
||||
#define DISABLE_PROFILER 0x0
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpGjsSource, sp_gjs_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
static void
|
||||
sp_gjs_source_finalize (GObject *object)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)object;
|
||||
|
||||
g_clear_pointer (&self->pids, g_array_unref);
|
||||
g_clear_pointer (&self->enabled, g_array_unref);
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_gjs_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_class_init (SpGjsSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_gjs_source_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_init (SpGjsSource *self)
|
||||
{
|
||||
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
self->enabled = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_process_capture (SpGjsSource *self,
|
||||
GPid pid,
|
||||
const gchar *path)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(SpCaptureReader) reader = NULL;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
g_assert (path != NULL);
|
||||
|
||||
if (!(reader = sp_capture_reader_new (path, &error)))
|
||||
{
|
||||
g_warning ("Failed to load capture: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sp_capture_reader_splice (reader, self->writer, &error))
|
||||
{
|
||||
g_warning ("Failed to load capture: %s", error->message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_process_captures (SpGjsSource *self)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
for (i = 0; i < self->enabled->len; i++)
|
||||
{
|
||||
g_autofree gchar *filename = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
GPid pid = g_array_index (self->enabled, GPid, i);
|
||||
|
||||
filename = g_strdup_printf ("gjs-profile-%u", (guint)pid);
|
||||
path = g_build_filename (g_get_tmp_dir (), filename, NULL);
|
||||
|
||||
sp_gjs_source_process_capture (self, pid, path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pid_is_profileable (GPid pid)
|
||||
{
|
||||
g_autofree gchar *path = NULL;
|
||||
g_autofree gchar *contents = NULL;
|
||||
const gchar *libgjs;
|
||||
gsize len = 0;
|
||||
|
||||
g_assert (pid != -1);
|
||||
|
||||
/*
|
||||
* Make sure this process has linked in libgjs. No sense in sending it a
|
||||
* signal unless we know it can handle it.
|
||||
*/
|
||||
|
||||
path = g_strdup_printf ("/proc/%d/maps", pid);
|
||||
if (!g_file_get_contents (path, &contents, &len, NULL))
|
||||
return FALSE;
|
||||
|
||||
if (NULL == (libgjs = strstr (contents, "libgjs."G_MODULE_SUFFIX)))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_enable_pid (SpGjsSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
union sigval si;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid != -1);
|
||||
|
||||
si.sival_int = ENABLE_PROFILER;
|
||||
|
||||
if (0 != sigqueue (pid, SIGUSR2, si))
|
||||
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
|
||||
else
|
||||
g_array_append_val (self->enabled, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_disable_pid (SpGjsSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
union sigval si;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid != -1);
|
||||
|
||||
si.sival_int = DISABLE_PROFILER;
|
||||
|
||||
if (0 != sigqueue (pid, SIGUSR2, si))
|
||||
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_start (SpSource *source)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid pid = g_array_index (self->pids, GPid, i);
|
||||
|
||||
if (pid_is_profileable (pid))
|
||||
sp_gjs_source_enable_pid (self, pid);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_stop (SpSource *source)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid pid = g_array_index (self->pids, GPid, i);
|
||||
|
||||
if (pid_is_profileable (pid))
|
||||
sp_gjs_source_disable_pid (self, pid);
|
||||
}
|
||||
|
||||
sp_gjs_source_process_captures (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid > -1);
|
||||
|
||||
g_array_append_val (self->pids, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_gjs_source_set_writer;
|
||||
iface->start = sp_gjs_source_start;
|
||||
iface->stop = sp_gjs_source_stop;
|
||||
iface->add_pid = sp_gjs_source_add_pid;
|
||||
}
|
||||
|
||||
SpSource *
|
||||
sp_gjs_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_GJS_SOURCE, NULL);
|
||||
}
|
||||
34
src/libsysprof/sp-gjs-source.h
Normal file
34
src/libsysprof/sp-gjs-source.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-gjs-source.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_GJS_SOURCE_H
|
||||
#define SP_GJS_SOURCE_H
|
||||
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_GJS_SOURCE (sp_gjs_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpGjsSource, sp_gjs_source, SP, GJS_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_gjs_source_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_GJS_SOURCE_H */
|
||||
384
src/libsysprof/sp-hostinfo-source.c
Normal file
384
src/libsysprof/sp-hostinfo-source.c
Normal file
@ -0,0 +1,384 @@
|
||||
/* sp-hostinfo-source.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-hostinfo-source.h"
|
||||
|
||||
#define PROC_STAT_BUF_SIZE 4096
|
||||
|
||||
struct _SpHostinfoSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
guint handler;
|
||||
gint n_cpu;
|
||||
gint stat_fd;
|
||||
|
||||
SpCaptureWriter *writer;
|
||||
GArray *cpu_info;
|
||||
gchar *stat_buf;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gint counter_base;
|
||||
gdouble total;
|
||||
gdouble freq;
|
||||
glong last_user;
|
||||
glong last_idle;
|
||||
glong last_system;
|
||||
glong last_nice;
|
||||
glong last_iowait;
|
||||
glong last_irq;
|
||||
glong last_softirq;
|
||||
glong last_steal;
|
||||
glong last_guest;
|
||||
glong last_guest_nice;
|
||||
} CpuInfo;
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpHostinfoSource, sp_hostinfo_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
SpSource *
|
||||
sp_hostinfo_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_HOSTINFO_SOURCE, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
read_stat (SpHostinfoSource *self)
|
||||
{
|
||||
gssize len;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->stat_fd != -1);
|
||||
g_assert (self->stat_buf != NULL);
|
||||
|
||||
if (lseek (self->stat_fd, 0, SEEK_SET) != 0)
|
||||
return FALSE;
|
||||
|
||||
len = read (self->stat_fd, self->stat_buf, PROC_STAT_BUF_SIZE);
|
||||
if (len <= 0)
|
||||
return FALSE;
|
||||
|
||||
if (len < PROC_STAT_BUF_SIZE)
|
||||
self->stat_buf[len] = 0;
|
||||
else
|
||||
self->stat_buf[PROC_STAT_BUF_SIZE-1] = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
poll_cpu (SpHostinfoSource *self)
|
||||
{
|
||||
gchar cpu[64] = { 0 };
|
||||
glong user;
|
||||
glong sys;
|
||||
glong nice;
|
||||
glong idle;
|
||||
glong iowait;
|
||||
glong irq;
|
||||
glong softirq;
|
||||
glong steal;
|
||||
glong guest;
|
||||
glong guest_nice;
|
||||
glong user_calc;
|
||||
glong system_calc;
|
||||
glong nice_calc;
|
||||
glong idle_calc;
|
||||
glong iowait_calc;
|
||||
glong irq_calc;
|
||||
glong softirq_calc;
|
||||
glong steal_calc;
|
||||
glong guest_calc;
|
||||
glong guest_nice_calc;
|
||||
glong total;
|
||||
gchar *line;
|
||||
gint ret;
|
||||
gint id;
|
||||
|
||||
if (read_stat (self))
|
||||
{
|
||||
line = self->stat_buf;
|
||||
|
||||
for (gsize i = 0; self->stat_buf[i]; i++)
|
||||
{
|
||||
if (self->stat_buf[i] == '\n')
|
||||
{
|
||||
self->stat_buf[i] = '\0';
|
||||
|
||||
if (strncmp (line, "cpu", 3) == 0)
|
||||
{
|
||||
if (isdigit (line[3]))
|
||||
{
|
||||
CpuInfo *cpu_info;
|
||||
|
||||
user = nice = sys = idle = id = 0;
|
||||
ret = sscanf (line, "%s %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
|
||||
cpu, &user, &nice, &sys, &idle,
|
||||
&iowait, &irq, &softirq, &steal, &guest, &guest_nice);
|
||||
if (ret != 11)
|
||||
goto next;
|
||||
|
||||
ret = sscanf(cpu, "cpu%d", &id);
|
||||
|
||||
if (ret != 1 || id < 0 || id >= self->n_cpu)
|
||||
goto next;
|
||||
|
||||
cpu_info = &g_array_index (self->cpu_info, CpuInfo, id);
|
||||
|
||||
user_calc = user - cpu_info->last_user;
|
||||
nice_calc = nice - cpu_info->last_nice;
|
||||
system_calc = sys - cpu_info->last_system;
|
||||
idle_calc = idle - cpu_info->last_idle;
|
||||
iowait_calc = iowait - cpu_info->last_iowait;
|
||||
irq_calc = irq - cpu_info->last_irq;
|
||||
softirq_calc = softirq - cpu_info->last_softirq;
|
||||
steal_calc = steal - cpu_info->last_steal;
|
||||
guest_calc = guest - cpu_info->last_guest;
|
||||
guest_nice_calc = guest_nice - cpu_info->last_guest_nice;
|
||||
|
||||
total = user_calc + nice_calc + system_calc + idle_calc + iowait_calc + irq_calc + softirq_calc + steal_calc + guest_calc + guest_nice_calc;
|
||||
cpu_info->total = ((total - idle_calc) / (gdouble)total) * 100.0;
|
||||
|
||||
cpu_info->last_user = user;
|
||||
cpu_info->last_nice = nice;
|
||||
cpu_info->last_idle = idle;
|
||||
cpu_info->last_system = sys;
|
||||
cpu_info->last_iowait = iowait;
|
||||
cpu_info->last_irq = irq;
|
||||
cpu_info->last_softirq = softirq;
|
||||
cpu_info->last_steal = steal;
|
||||
cpu_info->last_guest = guest;
|
||||
cpu_info->last_guest_nice = guest_nice;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* CPU info comes first. Skip further lines. */
|
||||
break;
|
||||
}
|
||||
|
||||
next:
|
||||
line = &self->stat_buf[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
publish_cpu (SpHostinfoSource *self)
|
||||
{
|
||||
SpCaptureCounterValue *counter_values;
|
||||
guint *counter_ids;
|
||||
|
||||
counter_ids = alloca (sizeof *counter_ids * self->n_cpu * 2);
|
||||
counter_values = alloca (sizeof *counter_values * self->n_cpu * 2);
|
||||
|
||||
for (guint i = 0; i < self->n_cpu; i++)
|
||||
{
|
||||
CpuInfo *info = &g_array_index (self->cpu_info, CpuInfo, i);
|
||||
SpCaptureCounterValue *value = &counter_values[i*2];
|
||||
guint *id = &counter_ids[i*2];
|
||||
|
||||
*id = info->counter_base;
|
||||
value->vdbl = info->total;
|
||||
|
||||
id++;
|
||||
value++;
|
||||
|
||||
*id = info->counter_base + 1;
|
||||
value->vdbl = info->freq;
|
||||
}
|
||||
|
||||
sp_capture_writer_set_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
getpid (),
|
||||
counter_ids,
|
||||
counter_values,
|
||||
self->n_cpu * 2);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
collect_hostinfo_cb (gpointer data)
|
||||
{
|
||||
SpHostinfoSource *self = data;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
poll_cpu (self);
|
||||
publish_cpu (self);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_finalize (GObject *object)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)object;
|
||||
|
||||
if (self->handler)
|
||||
{
|
||||
g_source_remove (self->handler);
|
||||
self->handler = 0;
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->cpu_info, g_array_unref);
|
||||
g_clear_pointer (&self->stat_buf, g_free);
|
||||
|
||||
G_OBJECT_CLASS (sp_hostinfo_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_class_init (SpHostinfoSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_hostinfo_source_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_init (SpHostinfoSource *self)
|
||||
{
|
||||
self->stat_fd = -1;
|
||||
self->cpu_info = g_array_new (FALSE, TRUE, sizeof (CpuInfo));
|
||||
self->stat_buf = g_malloc (PROC_STAT_BUF_SIZE);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_start (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
/* 20 samples per second */
|
||||
self->handler = g_timeout_add (1000/20, collect_hostinfo_cb, self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_stop (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
g_source_remove (self->handler);
|
||||
self->handler = 0;
|
||||
|
||||
if (self->stat_fd != -1)
|
||||
{
|
||||
close (self->stat_fd);
|
||||
self->stat_fd = -1;
|
||||
}
|
||||
|
||||
sp_source_emit_finished (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_prepare (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
SpCaptureCounter *counters;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
self->stat_fd = open ("/proc/stat", O_RDONLY);
|
||||
self->n_cpu = g_get_num_processors ();
|
||||
|
||||
g_array_set_size (self->cpu_info, 0);
|
||||
|
||||
counters = alloca (sizeof *counters * self->n_cpu * 2);
|
||||
|
||||
for (guint i = 0; i < self->n_cpu; i++)
|
||||
{
|
||||
SpCaptureCounter *ctr = &counters[i*2];
|
||||
CpuInfo info = { 0 };
|
||||
|
||||
/*
|
||||
* Request 2 counter values.
|
||||
* One for CPU and one for Frequency.
|
||||
*/
|
||||
info.counter_base = sp_capture_writer_request_counter (self->writer, 2);
|
||||
|
||||
/*
|
||||
* Define counters for capture file.
|
||||
*/
|
||||
ctr->id = info.counter_base;
|
||||
ctr->type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
ctr->value.vdbl = 0;
|
||||
g_strlcpy (ctr->category, "CPU Percent", sizeof ctr->category);
|
||||
g_snprintf (ctr->name, sizeof ctr->name, "Total CPU %d", i);
|
||||
g_snprintf (ctr->description, sizeof ctr->description,
|
||||
"Total CPU usage %d", i);
|
||||
|
||||
ctr++;
|
||||
|
||||
ctr->id = info.counter_base + 1;
|
||||
ctr->type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
ctr->value.vdbl = 0;
|
||||
g_strlcpy (ctr->category, "CPU Frequency", sizeof ctr->category);
|
||||
g_snprintf (ctr->name, sizeof ctr->name, "CPU %d", i);
|
||||
g_snprintf (ctr->description, sizeof ctr->description,
|
||||
"Frequency of CPU %d", i);
|
||||
|
||||
g_array_append_val (self->cpu_info, info);
|
||||
}
|
||||
|
||||
sp_capture_writer_define_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
getpid (),
|
||||
counters,
|
||||
self->n_cpu * 2);
|
||||
|
||||
sp_source_emit_ready (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_hostinfo_source_set_writer;
|
||||
iface->prepare = sp_hostinfo_source_prepare;
|
||||
iface->start = sp_hostinfo_source_start;
|
||||
iface->stop = sp_hostinfo_source_stop;
|
||||
}
|
||||
35
src/libsysprof/sp-hostinfo-source.h
Normal file
35
src/libsysprof/sp-hostinfo-source.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* sp-hostinfo-source.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_HOSTINFO_SOURCE_H
|
||||
#define SP_HOSTINFO_SOURCE_H
|
||||
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_HOSTINFO_SOURCE (sp_hostinfo_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpHostinfoSource, sp_hostinfo_source, SP, HOSTINFO_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_hostinfo_source_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_HOSTINFO_SOURCE_H */
|
||||
|
||||
121
src/libsysprof/sp-jitmap-symbol-resolver.c
Normal file
121
src/libsysprof/sp-jitmap-symbol-resolver.c
Normal file
@ -0,0 +1,121 @@
|
||||
/* sp-jitmap-symbol-resolver.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "sp-kernel-symbol.h"
|
||||
#include "sp-jitmap-symbol-resolver.h"
|
||||
|
||||
struct _SpJitmapSymbolResolver
|
||||
{
|
||||
GObject parent_instance;
|
||||
GHashTable *jitmap;
|
||||
};
|
||||
|
||||
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpJitmapSymbolResolver,
|
||||
sp_jitmap_symbol_resolver,
|
||||
G_TYPE_OBJECT,
|
||||
0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
|
||||
symbol_resolver_iface_init))
|
||||
|
||||
static void
|
||||
sp_jitmap_symbol_resolver_finalize (GObject *object)
|
||||
{
|
||||
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)object;
|
||||
|
||||
g_clear_pointer (&self->jitmap, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_jitmap_symbol_resolver_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_jitmap_symbol_resolver_class_init (SpJitmapSymbolResolverClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_jitmap_symbol_resolver_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_jitmap_symbol_resolver_init (SpJitmapSymbolResolver *self)
|
||||
{
|
||||
self->jitmap = g_hash_table_new_full (NULL, NULL, NULL, g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_jitmap_symbol_resolver_load (SpSymbolResolver *resolver,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)resolver;
|
||||
SpCaptureFrameType type;
|
||||
|
||||
g_assert (SP_IS_JITMAP_SYMBOL_RESOLVER (self));
|
||||
g_assert (reader != NULL);
|
||||
|
||||
while (sp_capture_reader_peek_type (reader, &type))
|
||||
{
|
||||
g_autoptr(GHashTable) jitmap = NULL;
|
||||
GHashTableIter iter;
|
||||
SpCaptureAddress addr;
|
||||
const gchar *str;
|
||||
|
||||
if (type != SP_CAPTURE_FRAME_JITMAP)
|
||||
{
|
||||
if (!sp_capture_reader_skip (reader))
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(jitmap = sp_capture_reader_read_jitmap (reader)))
|
||||
return;
|
||||
|
||||
g_hash_table_iter_init (&iter, jitmap);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&addr, (gpointer *)&str))
|
||||
g_hash_table_insert (self->jitmap, GSIZE_TO_POINTER (addr), g_strdup (str));
|
||||
}
|
||||
}
|
||||
|
||||
static gchar *
|
||||
sp_jitmap_symbol_resolver_resolve (SpSymbolResolver *resolver,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)resolver;
|
||||
|
||||
g_assert (SP_IS_JITMAP_SYMBOL_RESOLVER (self));
|
||||
|
||||
*tag = 0;
|
||||
|
||||
return g_strdup (g_hash_table_lookup (self->jitmap, GSIZE_TO_POINTER (address)));
|
||||
}
|
||||
|
||||
static void
|
||||
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
|
||||
{
|
||||
iface->load = sp_jitmap_symbol_resolver_load;
|
||||
iface->resolve = sp_jitmap_symbol_resolver_resolve;
|
||||
}
|
||||
|
||||
SpSymbolResolver *
|
||||
sp_jitmap_symbol_resolver_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_JITMAP_SYMBOL_RESOLVER, NULL);
|
||||
}
|
||||
34
src/libsysprof/sp-jitmap-symbol-resolver.h
Normal file
34
src/libsysprof/sp-jitmap-symbol-resolver.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-jitmap-symbol-resolver.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_JITMAP_SYMBOL_RESOLVER_H
|
||||
#define SP_JITMAP_SYMBOL_RESOLVER_H
|
||||
|
||||
#include "sp-symbol-resolver.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_JITMAP_SYMBOL_RESOLVER (sp_jitmap_symbol_resolver_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpJitmapSymbolResolver, sp_jitmap_symbol_resolver, SP, JITMAP_SYMBOL_RESOLVER, GObject)
|
||||
|
||||
SpSymbolResolver *sp_jitmap_symbol_resolver_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_JITMAP_SYMBOL_RESOLVER_H */
|
||||
82
src/libsysprof/sp-kernel-symbol-resolver.c
Normal file
82
src/libsysprof/sp-kernel-symbol-resolver.c
Normal file
@ -0,0 +1,82 @@
|
||||
/* sp-kernel-symbol-resolver.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "sp-kernel-symbol.h"
|
||||
#include "sp-kernel-symbol-resolver.h"
|
||||
|
||||
struct _SpKernelSymbolResolver
|
||||
{
|
||||
GObject parent_instance;
|
||||
};
|
||||
|
||||
static GQuark linux_quark;
|
||||
|
||||
static gchar *
|
||||
sp_kernel_symbol_resolver_resolve_with_context (SpSymbolResolver *resolver,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpAddressContext context,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
const SpKernelSymbol *sym;
|
||||
|
||||
g_assert (SP_IS_SYMBOL_RESOLVER (resolver));
|
||||
|
||||
if (context != SP_ADDRESS_CONTEXT_KERNEL)
|
||||
return NULL;
|
||||
|
||||
sym = sp_kernel_symbol_from_address (address);
|
||||
|
||||
if (sym != NULL)
|
||||
{
|
||||
*tag = linux_quark;
|
||||
return g_strdup (sym->name);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
|
||||
{
|
||||
iface->resolve_with_context = sp_kernel_symbol_resolver_resolve_with_context;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (SpKernelSymbolResolver,
|
||||
sp_kernel_symbol_resolver,
|
||||
G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
|
||||
symbol_resolver_iface_init))
|
||||
|
||||
static void
|
||||
sp_kernel_symbol_resolver_class_init (SpKernelSymbolResolverClass *klass)
|
||||
{
|
||||
linux_quark = g_quark_from_static_string ("Kernel");
|
||||
}
|
||||
|
||||
static void
|
||||
sp_kernel_symbol_resolver_init (SpKernelSymbolResolver *skernel)
|
||||
{
|
||||
}
|
||||
|
||||
SpSymbolResolver *
|
||||
sp_kernel_symbol_resolver_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_KERNEL_SYMBOL_RESOLVER, NULL);
|
||||
}
|
||||
34
src/libsysprof/sp-kernel-symbol-resolver.h
Normal file
34
src/libsysprof/sp-kernel-symbol-resolver.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-kernel-symbol-resolver.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_KERNEL_SYMBOL_RESOLVER_H
|
||||
#define SP_KERNEL_SYMBOL_RESOLVER_H
|
||||
|
||||
#include "sp-symbol-resolver.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_KERNEL_SYMBOL_RESOLVER (sp_kernel_symbol_resolver_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpKernelSymbolResolver, sp_kernel_symbol_resolver, SP, KERNEL_SYMBOL_RESOLVER, GObject)
|
||||
|
||||
SpSymbolResolver *sp_kernel_symbol_resolver_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_KERNEL_SYMBOL_RESOLVER_H */
|
||||
328
src/libsysprof/sp-kernel-symbol.c
Normal file
328
src/libsysprof/sp-kernel-symbol.c
Normal file
@ -0,0 +1,328 @@
|
||||
/* sp-kernel-symbol.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-kernel-symbol"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#ifdef ENABLE_POLKIT
|
||||
# include <polkit/polkit.h>
|
||||
#endif
|
||||
#include <sysprof-capture.h>
|
||||
|
||||
#include "sp-kallsyms.h"
|
||||
#include "sp-line-reader.h"
|
||||
#include "sp-kernel-symbol.h"
|
||||
|
||||
static GArray *kernel_symbols;
|
||||
static GStringChunk *kernel_symbol_strs;
|
||||
static GHashTable *kernel_symbols_skip_hash;
|
||||
static const gchar *kernel_symbols_skip[] = {
|
||||
/* IRQ stack */
|
||||
"common_interrupt",
|
||||
"apic_timer_interrupt",
|
||||
"smp_apic_timer_interrupt",
|
||||
"hrtimer_interrupt",
|
||||
"__run_hrtimer",
|
||||
"perf_swevent_hrtimer",
|
||||
"perf_event_overflow",
|
||||
"__perf_event_overflow",
|
||||
"perf_prepare_sample",
|
||||
"perf_callchain",
|
||||
"perf_swcounter_hrtimer",
|
||||
"perf_counter_overflow",
|
||||
"__perf_counter_overflow",
|
||||
"perf_counter_output",
|
||||
|
||||
/* NMI stack */
|
||||
"nmi_stack_correct",
|
||||
"do_nmi",
|
||||
"notify_die",
|
||||
"atomic_notifier_call_chain",
|
||||
"notifier_call_chain",
|
||||
"perf_event_nmi_handler",
|
||||
"perf_counter_nmi_handler",
|
||||
"intel_pmu_handle_irq",
|
||||
"perf_event_overflow",
|
||||
"perf_counter_overflow",
|
||||
"__perf_event_overflow",
|
||||
"perf_prepare_sample",
|
||||
"perf_callchain",
|
||||
};
|
||||
|
||||
static gint
|
||||
sp_kernel_symbol_compare (gconstpointer a,
|
||||
gconstpointer b)
|
||||
{
|
||||
const SpKernelSymbol *syma = a;
|
||||
const SpKernelSymbol *symb = b;
|
||||
|
||||
if (syma->address > symb->address)
|
||||
return 1;
|
||||
else if (syma->address == symb->address)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
type_is_ignored (guint8 type)
|
||||
{
|
||||
/* Only allow symbols in the text (code) section */
|
||||
return (type != 't' && type != 'T');
|
||||
}
|
||||
|
||||
static gboolean
|
||||
authorize_proxy (GDBusConnection *conn)
|
||||
{
|
||||
#ifdef ENABLE_POLKIT
|
||||
PolkitSubject *subject = NULL;
|
||||
GPermission *permission = NULL;
|
||||
const gchar *name;
|
||||
|
||||
g_assert (G_IS_DBUS_CONNECTION (conn));
|
||||
|
||||
name = g_dbus_connection_get_unique_name (conn);
|
||||
if (name == NULL)
|
||||
goto failure;
|
||||
|
||||
subject = polkit_system_bus_name_new (name);
|
||||
if (subject == NULL)
|
||||
goto failure;
|
||||
|
||||
permission = polkit_permission_new_sync ("org.gnome.sysprof2.get-kernel-symbols", subject, NULL, NULL);
|
||||
if (permission == NULL)
|
||||
goto failure;
|
||||
|
||||
if (!g_permission_acquire (permission, NULL, NULL))
|
||||
goto failure;
|
||||
|
||||
return TRUE;
|
||||
|
||||
failure:
|
||||
g_clear_object (&subject);
|
||||
g_clear_object (&permission);
|
||||
#endif
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_kernel_symbol_load_from_sysprofd (void)
|
||||
{
|
||||
g_autoptr(GDBusConnection) conn = NULL;
|
||||
g_autoptr(GVariant) ret = NULL;
|
||||
g_autoptr(GVariant) results = NULL;
|
||||
g_autoptr(GArray) ar = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
GVariantIter iter;
|
||||
const gchar *name;
|
||||
guint64 addr;
|
||||
guint8 type;
|
||||
|
||||
if (!(conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL)))
|
||||
return FALSE;
|
||||
|
||||
if (!authorize_proxy (conn))
|
||||
{
|
||||
g_warning ("Failed to acquire sufficient credentials to read kernel symbols");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ret = g_dbus_connection_call_sync (conn,
|
||||
"org.gnome.Sysprof2",
|
||||
"/org/gnome/Sysprof2",
|
||||
"org.gnome.Sysprof2",
|
||||
"GetKernelSymbols",
|
||||
NULL,
|
||||
G_VARIANT_TYPE ("(a(tys))"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
&error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_warning ("Failed to load symbols from sysprofd: %s", error->message);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ar = g_array_new (FALSE, TRUE, sizeof (SpKernelSymbol));
|
||||
|
||||
results = g_variant_get_child_value (ret, 0);
|
||||
g_variant_iter_init (&iter, results);
|
||||
while (g_variant_iter_loop (&iter, "(ty&s)", &addr, &type, &name))
|
||||
{
|
||||
SpKernelSymbol sym;
|
||||
|
||||
if (type_is_ignored (type))
|
||||
continue;
|
||||
|
||||
sym.address = addr;
|
||||
sym.name = g_string_chunk_insert_const (kernel_symbol_strs, name);
|
||||
|
||||
g_array_append_val (ar, sym);
|
||||
}
|
||||
|
||||
g_array_sort (ar, sp_kernel_symbol_compare);
|
||||
|
||||
#if 0
|
||||
g_print ("First: 0x%lx Last: 0x%lx\n",
|
||||
g_array_index (ar, SpKernelSymbol, 0).address,
|
||||
g_array_index (ar, SpKernelSymbol, ar->len - 1).address);
|
||||
#endif
|
||||
|
||||
kernel_symbols = g_steal_pointer (&ar);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_kernel_symbol_load (void)
|
||||
{
|
||||
g_autoptr(GHashTable) skip = NULL;
|
||||
g_autoptr(SpKallsyms) kallsyms = NULL;
|
||||
g_autoptr(GArray) ar = NULL;
|
||||
const gchar *name;
|
||||
guint64 addr;
|
||||
guint8 type;
|
||||
|
||||
skip = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
for (guint i = 0; i < G_N_ELEMENTS (kernel_symbols_skip); i++)
|
||||
g_hash_table_insert (skip, (gchar *)kernel_symbols_skip[i], NULL);
|
||||
kernel_symbols_skip_hash = g_steal_pointer (&skip);
|
||||
|
||||
kernel_symbol_strs = g_string_chunk_new (4096);
|
||||
ar = g_array_new (FALSE, TRUE, sizeof (SpKernelSymbol));
|
||||
|
||||
if (!(kallsyms = sp_kallsyms_new (NULL)))
|
||||
goto query_daemon;
|
||||
|
||||
while (sp_kallsyms_next (kallsyms, &name, &addr, &type))
|
||||
{
|
||||
SpKernelSymbol sym;
|
||||
|
||||
if (type_is_ignored (type))
|
||||
continue;
|
||||
|
||||
sym.address = addr;
|
||||
sym.name = g_string_chunk_insert_const (kernel_symbol_strs, name);
|
||||
|
||||
g_array_append_val (ar, sym);
|
||||
}
|
||||
|
||||
if (ar->len == 0)
|
||||
goto query_daemon;
|
||||
|
||||
g_array_sort (ar, sp_kernel_symbol_compare);
|
||||
kernel_symbols = g_steal_pointer (&ar);
|
||||
|
||||
return TRUE;
|
||||
|
||||
query_daemon:
|
||||
if (sp_kernel_symbol_load_from_sysprofd ())
|
||||
return TRUE;
|
||||
|
||||
g_warning ("Kernel symbols will not be available.");
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static const SpKernelSymbol *
|
||||
sp_kernel_symbol_lookup (SpKernelSymbol *symbols,
|
||||
SpCaptureAddress address,
|
||||
guint first,
|
||||
guint last)
|
||||
{
|
||||
if (address >= symbols [last].address)
|
||||
{
|
||||
return &symbols [last];
|
||||
}
|
||||
else if (last - first < 3)
|
||||
{
|
||||
while (last >= first)
|
||||
{
|
||||
if (address >= symbols[last].address)
|
||||
return &symbols [last];
|
||||
|
||||
last--;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
int mid = (first + last) / 2;
|
||||
|
||||
if (symbols [mid].address > address)
|
||||
return sp_kernel_symbol_lookup (symbols, address, first, mid);
|
||||
else
|
||||
return sp_kernel_symbol_lookup (symbols, address, mid, last);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_kernel_symbol_from_address:
|
||||
* @address: the address of the instruction pointer
|
||||
*
|
||||
* Locates the kernel symbol that contains @address.
|
||||
*
|
||||
* Returns: (transfer none): An #SpKernelSymbol or %NULL.
|
||||
*/
|
||||
const SpKernelSymbol *
|
||||
sp_kernel_symbol_from_address (SpCaptureAddress address)
|
||||
{
|
||||
const SpKernelSymbol *first;
|
||||
const SpKernelSymbol *ret;
|
||||
|
||||
if G_UNLIKELY (kernel_symbols == NULL)
|
||||
{
|
||||
static gboolean failed;
|
||||
|
||||
if (failed)
|
||||
return NULL;
|
||||
|
||||
if (!sp_kernel_symbol_load ())
|
||||
{
|
||||
failed = TRUE;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
g_assert (kernel_symbols != NULL);
|
||||
g_assert (kernel_symbols->len > 0);
|
||||
|
||||
/* Short circuit if this is out of range */
|
||||
first = &g_array_index (kernel_symbols, SpKernelSymbol, 0);
|
||||
if (address < first->address)
|
||||
return NULL;
|
||||
|
||||
ret = sp_kernel_symbol_lookup ((SpKernelSymbol *)(gpointer)kernel_symbols->data,
|
||||
address,
|
||||
0,
|
||||
kernel_symbols->len - 1);
|
||||
|
||||
/* We resolve all symbols, including ignored symbols so that we
|
||||
* don't give back the wrong function juxtapose an ignored func.
|
||||
*/
|
||||
if (ret != NULL && g_hash_table_contains (kernel_symbols_skip_hash, ret->name))
|
||||
return NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
36
src/libsysprof/sp-kernel-symbol.h
Normal file
36
src/libsysprof/sp-kernel-symbol.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-kernel-symbol.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_KERNEL_SYMBOL_H
|
||||
#define SP_KERNEL_SYMBOL_H
|
||||
|
||||
#include "sp-capture-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureAddress address;
|
||||
const gchar *name;
|
||||
} SpKernelSymbol;
|
||||
|
||||
const SpKernelSymbol *sp_kernel_symbol_from_address (SpCaptureAddress address);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_KERNEL_SYMBOL_H */
|
||||
824
src/libsysprof/sp-local-profiler.c
Normal file
824
src/libsysprof/sp-local-profiler.c
Normal file
@ -0,0 +1,824 @@
|
||||
/* sp-local-profiler.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-local-profiler.h"
|
||||
#include "sp-platform.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureWriter *writer;
|
||||
|
||||
/* All sources added */
|
||||
GPtrArray *sources;
|
||||
|
||||
/* Array of GError failures */
|
||||
GPtrArray *failures;
|
||||
|
||||
/* Sources currently starting */
|
||||
GPtrArray *starting;
|
||||
|
||||
/* Sources currently stopping */
|
||||
GPtrArray *stopping;
|
||||
|
||||
/* Sources that have failed or finished */
|
||||
GPtrArray *finished_or_failed;
|
||||
|
||||
/* Pids to notify children about before prepare */
|
||||
GArray *pids;
|
||||
|
||||
/* Timer for simple time tracking */
|
||||
GTimer *timer;
|
||||
guint timer_notify_source;
|
||||
|
||||
/* Arguments and environment variables for spawning */
|
||||
gchar **spawn_argv;
|
||||
gchar **spawn_env;
|
||||
|
||||
/* State flags */
|
||||
guint is_running : 1;
|
||||
guint is_stopping : 1;
|
||||
guint is_starting : 1;
|
||||
|
||||
/*
|
||||
* If we should spawn argv when starting up. This allows UI to set
|
||||
* spawn argv/env but enable disable with a toggle.
|
||||
*/
|
||||
guint spawn : 1;
|
||||
|
||||
/* If we should inherit the environment when spawning */
|
||||
guint spawn_inherit_environ : 1;
|
||||
|
||||
/*
|
||||
* If we should profile the entire system. Setting this results in pids
|
||||
* being ignored. This is primarily useful for UI to toggle on/off the
|
||||
* feature of per-process vs whole-system.
|
||||
*/
|
||||
guint whole_system : 1;
|
||||
|
||||
/*
|
||||
* If we got a stop request after calling start() but before we have had
|
||||
* a chance to settle, then we need to stop immediately after starting.
|
||||
* We do this to avoid a more complex state machine (for now).
|
||||
*/
|
||||
guint stop_after_starting : 1;
|
||||
} SpLocalProfilerPrivate;
|
||||
|
||||
static void profiler_iface_init (SpProfilerInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpLocalProfiler, sp_local_profiler, G_TYPE_OBJECT, 0,
|
||||
G_ADD_PRIVATE (SpLocalProfiler)
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_PROFILER, profiler_iface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
N_PROPS,
|
||||
|
||||
PROP_ELAPSED,
|
||||
PROP_IS_MUTABLE,
|
||||
PROP_IS_RUNNING,
|
||||
PROP_SPAWN,
|
||||
PROP_SPAWN_ARGV,
|
||||
PROP_SPAWN_ENV,
|
||||
PROP_SPAWN_INHERIT_ENVIRON,
|
||||
PROP_WHOLE_SYSTEM,
|
||||
};
|
||||
|
||||
static inline gint
|
||||
_g_ptr_array_find (GPtrArray *ar,
|
||||
gpointer item)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < ar->len; i++)
|
||||
{
|
||||
if (item == g_ptr_array_index (ar, i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
_g_ptr_array_contains (GPtrArray *ar,
|
||||
gpointer item)
|
||||
{
|
||||
return (-1 != _g_ptr_array_find (ar, item));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_clear_timer (SpLocalProfiler *self)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
|
||||
g_clear_pointer (&priv->timer, g_timer_destroy);
|
||||
|
||||
if (priv->timer_notify_source != 0)
|
||||
{
|
||||
g_source_remove (priv->timer_notify_source);
|
||||
priv->timer_notify_source = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_real_stopped (SpProfiler *profiler)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
|
||||
sp_local_profiler_clear_timer (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_local_profiler_notify_elapsed_cb (gpointer data)
|
||||
{
|
||||
SpLocalProfiler *self = data;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
|
||||
g_object_notify (G_OBJECT (self), "elapsed");
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_finish_stopping (SpLocalProfiler *self)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (priv->is_starting == FALSE);
|
||||
g_assert (priv->is_stopping == TRUE);
|
||||
g_assert (priv->stopping->len == 0);
|
||||
|
||||
if (priv->failures->len > 0)
|
||||
{
|
||||
const GError *error = g_ptr_array_index (priv->failures, 0);
|
||||
|
||||
sp_profiler_emit_failed (SP_PROFILER (self), error);
|
||||
}
|
||||
|
||||
priv->is_running = FALSE;
|
||||
priv->is_stopping = FALSE;
|
||||
|
||||
sp_profiler_emit_stopped (SP_PROFILER (self));
|
||||
|
||||
g_object_notify (G_OBJECT (self), "is-mutable");
|
||||
g_object_notify (G_OBJECT (self), "is-running");
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_stop (SpProfiler *profiler)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
|
||||
if (priv->is_starting)
|
||||
{
|
||||
priv->stop_after_starting = TRUE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (priv->is_stopping || !priv->is_running)
|
||||
return;
|
||||
|
||||
priv->is_stopping = TRUE;
|
||||
|
||||
/*
|
||||
* First we add everything to the stopping list, so that we can
|
||||
* be notified of when they have completed. If everything stopped
|
||||
* synchronously, the stopping list will be empty after calling
|
||||
* sp_source_stop() for every source. Otherwise, we need to delay
|
||||
* stopping for a little bit.
|
||||
*/
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
|
||||
if (!_g_ptr_array_contains (priv->finished_or_failed, source))
|
||||
g_ptr_array_add (priv->stopping, g_object_ref (source));
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
|
||||
sp_source_stop (source);
|
||||
}
|
||||
|
||||
if (priv->is_stopping && priv->stopping->len == 0)
|
||||
sp_local_profiler_finish_stopping (self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
sp_local_profiler_dispose (GObject *object)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)object;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
if (priv->is_running || priv->is_starting)
|
||||
{
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
return;
|
||||
}
|
||||
|
||||
sp_local_profiler_clear_timer (self);
|
||||
|
||||
G_OBJECT_CLASS (sp_local_profiler_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_finalize (GObject *object)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)object;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&priv->sources, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->starting, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->stopping, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->failures, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->finished_or_failed, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->pids, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_local_profiler_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpLocalProfiler *self = SP_LOCAL_PROFILER (object);
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_ELAPSED:
|
||||
g_value_set_double (value, priv->timer ? g_timer_elapsed (priv->timer, NULL) : 0.0);
|
||||
break;
|
||||
|
||||
case PROP_IS_MUTABLE:
|
||||
g_value_set_boolean (value, !(priv->is_starting || priv->is_starting || priv->is_running));
|
||||
break;
|
||||
|
||||
case PROP_IS_RUNNING:
|
||||
g_value_set_boolean (value, priv->is_running);
|
||||
break;
|
||||
|
||||
case PROP_WHOLE_SYSTEM:
|
||||
g_value_set_boolean (value, priv->whole_system);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN:
|
||||
g_value_set_boolean (value, priv->spawn);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_INHERIT_ENVIRON:
|
||||
g_value_set_boolean (value, priv->spawn_inherit_environ);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_ARGV:
|
||||
g_value_set_boxed (value, priv->spawn_argv);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_ENV:
|
||||
g_value_set_boxed (value, priv->spawn_env);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpLocalProfiler *self = SP_LOCAL_PROFILER (object);
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_WHOLE_SYSTEM:
|
||||
priv->whole_system = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN:
|
||||
priv->spawn = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_INHERIT_ENVIRON:
|
||||
priv->spawn_inherit_environ = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_ARGV:
|
||||
g_strfreev (priv->spawn_argv);
|
||||
priv->spawn_argv = g_value_dup_boxed (value);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_ENV:
|
||||
g_strfreev (priv->spawn_env);
|
||||
priv->spawn_env = g_value_dup_boxed (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_class_init (SpLocalProfilerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = sp_local_profiler_dispose;
|
||||
object_class->finalize = sp_local_profiler_finalize;
|
||||
object_class->get_property = sp_local_profiler_get_property;
|
||||
object_class->set_property = sp_local_profiler_set_property;
|
||||
|
||||
g_object_class_override_property (object_class, PROP_ELAPSED, "elapsed");
|
||||
g_object_class_override_property (object_class, PROP_IS_MUTABLE, "is-mutable");
|
||||
g_object_class_override_property (object_class, PROP_IS_RUNNING, "is-running");
|
||||
g_object_class_override_property (object_class, PROP_SPAWN, "spawn");
|
||||
g_object_class_override_property (object_class, PROP_SPAWN_ARGV, "spawn-argv");
|
||||
g_object_class_override_property (object_class, PROP_SPAWN_ENV, "spawn-env");
|
||||
g_object_class_override_property (object_class, PROP_SPAWN_INHERIT_ENVIRON, "spawn-inherit-environ");
|
||||
g_object_class_override_property (object_class, PROP_WHOLE_SYSTEM, "whole-system");
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_init (SpLocalProfiler *self)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
priv->whole_system = TRUE;
|
||||
|
||||
priv->failures = g_ptr_array_new_with_free_func ((GDestroyNotify)g_error_free);
|
||||
priv->sources = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
priv->starting = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
priv->stopping = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
priv->finished_or_failed = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
priv->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
}
|
||||
|
||||
SpProfiler *
|
||||
sp_local_profiler_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_LOCAL_PROFILER, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_finish_startup (SpLocalProfiler *self)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (priv->is_starting == TRUE);
|
||||
g_assert (priv->starting->len == 0);
|
||||
|
||||
sp_local_profiler_clear_timer (self);
|
||||
|
||||
priv->timer = g_timer_new ();
|
||||
|
||||
/*
|
||||
* Add a source to update our watchers of elapsed time.
|
||||
* We use 1000 instead of add_seconds(1) so that we are
|
||||
* not subject to as much drift.
|
||||
*/
|
||||
priv->timer_notify_source =
|
||||
g_timeout_add (1000,
|
||||
sp_local_profiler_notify_elapsed_cb,
|
||||
self);
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
|
||||
sp_source_start (source);
|
||||
}
|
||||
|
||||
priv->is_starting = FALSE;
|
||||
|
||||
/*
|
||||
* If any of the sources failed during startup, we will have a non-empty
|
||||
* failures list.
|
||||
*/
|
||||
if (priv->failures->len > 0)
|
||||
{
|
||||
const GError *error = g_ptr_array_index (priv->failures, 0);
|
||||
|
||||
g_object_ref (self);
|
||||
sp_profiler_emit_failed (SP_PROFILER (self), error);
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
g_object_unref (self);
|
||||
return;
|
||||
}
|
||||
|
||||
priv->is_running = TRUE;
|
||||
|
||||
g_object_notify (G_OBJECT (self), "is-mutable");
|
||||
g_object_notify (G_OBJECT (self), "is-running");
|
||||
|
||||
/*
|
||||
* If all the sources are transient (in that they just generate information
|
||||
* and then exit), we could be finished as soon as we complete startup.
|
||||
*
|
||||
* If we detect this, we stop immediately.
|
||||
*/
|
||||
if (priv->finished_or_failed->len == priv->sources->len || priv->stop_after_starting)
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_start (SpProfiler *profiler)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
g_return_if_fail (priv->is_starting == FALSE);
|
||||
|
||||
g_clear_pointer (&priv->timer, g_timer_destroy);
|
||||
g_object_notify (G_OBJECT (self), "elapsed");
|
||||
|
||||
if (priv->writer == NULL)
|
||||
{
|
||||
SpCaptureWriter *writer;
|
||||
int fd;
|
||||
|
||||
if ((-1 == (fd = sp_memfd_create ("[sysprof]"))) ||
|
||||
(NULL == (writer = sp_capture_writer_new_from_fd (fd, 0))))
|
||||
{
|
||||
const GError error = {
|
||||
G_FILE_ERROR,
|
||||
g_file_error_from_errno (errno),
|
||||
(gchar *)g_strerror (errno)
|
||||
};
|
||||
|
||||
if (fd != -1)
|
||||
close (fd);
|
||||
|
||||
sp_profiler_emit_failed (SP_PROFILER (self), &error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sp_profiler_set_writer (SP_PROFILER (self), writer);
|
||||
g_clear_pointer (&writer, sp_capture_writer_unref);
|
||||
}
|
||||
|
||||
priv->is_running = TRUE;
|
||||
priv->is_starting = TRUE;
|
||||
|
||||
if (priv->failures->len > 0)
|
||||
g_ptr_array_remove_range (priv->failures, 0, priv->failures->len);
|
||||
|
||||
if (priv->spawn && priv->spawn_argv && priv->spawn_argv[0])
|
||||
{
|
||||
g_autoptr(GPtrArray) ar = g_ptr_array_new_with_free_func (g_free);
|
||||
GPid pid;
|
||||
GError *error = NULL;
|
||||
|
||||
if (priv->spawn_inherit_environ)
|
||||
{
|
||||
gchar **environ = g_get_environ ();
|
||||
|
||||
for (i = 0; environ[i]; i++)
|
||||
g_ptr_array_add (ar, environ[i]);
|
||||
g_free (environ);
|
||||
}
|
||||
|
||||
if (priv->spawn_env)
|
||||
{
|
||||
for (i = 0; priv->spawn_env[i]; i++)
|
||||
g_ptr_array_add (ar, g_strdup (priv->spawn_env[i]));
|
||||
}
|
||||
|
||||
g_ptr_array_add (ar, NULL);
|
||||
|
||||
if (!g_spawn_async (g_get_home_dir (),
|
||||
priv->spawn_argv,
|
||||
(gchar **)ar->pdata,
|
||||
(G_SPAWN_SEARCH_PATH |
|
||||
G_SPAWN_STDOUT_TO_DEV_NULL |
|
||||
G_SPAWN_STDOUT_TO_DEV_NULL),
|
||||
NULL,
|
||||
NULL,
|
||||
&pid,
|
||||
&error))
|
||||
g_ptr_array_add (priv->failures, error);
|
||||
else
|
||||
g_array_append_val (priv->pids, pid);
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
guint j;
|
||||
|
||||
if (priv->whole_system == FALSE)
|
||||
{
|
||||
for (j = 0; j < priv->pids->len; j++)
|
||||
{
|
||||
GPid pid = g_array_index (priv->pids, GPid, j);
|
||||
|
||||
sp_source_add_pid (source, pid);
|
||||
}
|
||||
}
|
||||
|
||||
sp_source_set_writer (source, priv->writer);
|
||||
sp_source_prepare (source);
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
|
||||
if (!sp_source_get_is_ready (source))
|
||||
g_ptr_array_add (priv->starting, g_object_ref (source));
|
||||
}
|
||||
|
||||
if (priv->starting->len == 0)
|
||||
sp_local_profiler_finish_startup (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_set_writer (SpProfiler *profiler,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
g_return_if_fail (writer != NULL);
|
||||
|
||||
if (priv->writer != writer)
|
||||
{
|
||||
g_clear_pointer (&priv->writer, sp_capture_writer_unref);
|
||||
|
||||
if (writer != NULL)
|
||||
priv->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_track_completed (SpLocalProfiler *self,
|
||||
SpSource *source)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
gint i;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (SP_IS_SOURCE (source));
|
||||
|
||||
if (!_g_ptr_array_contains (priv->finished_or_failed, source))
|
||||
g_ptr_array_add (priv->finished_or_failed, g_object_ref (source));
|
||||
|
||||
if (priv->is_starting)
|
||||
{
|
||||
i = _g_ptr_array_find (priv->starting, source);
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
g_ptr_array_remove_index (priv->starting, i);
|
||||
if (priv->starting->len == 0)
|
||||
sp_local_profiler_finish_startup (self);
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->is_stopping)
|
||||
{
|
||||
i = _g_ptr_array_find (priv->stopping, source);
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
g_ptr_array_remove_index_fast (priv->stopping, i);
|
||||
|
||||
if ((priv->is_stopping == TRUE) && (priv->stopping->len == 0))
|
||||
sp_local_profiler_finish_stopping (self);
|
||||
}
|
||||
}
|
||||
|
||||
if (!priv->is_starting)
|
||||
{
|
||||
if (priv->finished_or_failed->len == priv->sources->len)
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_source_finished (SpLocalProfiler *self,
|
||||
SpSource *source)
|
||||
{
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (SP_IS_SOURCE (source));
|
||||
|
||||
sp_local_profiler_track_completed (self, source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_source_ready (SpLocalProfiler *self,
|
||||
SpSource *source)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (SP_IS_SOURCE (source));
|
||||
|
||||
for (i = 0; i < priv->starting->len; i++)
|
||||
{
|
||||
SpSource *ele = g_ptr_array_index (priv->starting, i);
|
||||
|
||||
if (ele == source)
|
||||
{
|
||||
g_ptr_array_remove_index_fast (priv->starting, i);
|
||||
|
||||
if ((priv->is_starting == TRUE) && (priv->starting->len == 0))
|
||||
sp_local_profiler_finish_startup (self);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_source_failed (SpLocalProfiler *self,
|
||||
const GError *reason,
|
||||
SpSource *source)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (reason != NULL);
|
||||
g_assert (SP_IS_SOURCE (source));
|
||||
|
||||
sp_local_profiler_track_completed (self, source);
|
||||
|
||||
/* Failure emitted out of band */
|
||||
if (!priv->is_starting && !priv->is_stopping && !priv->is_running)
|
||||
return;
|
||||
|
||||
g_ptr_array_add (priv->failures, g_error_copy (reason));
|
||||
|
||||
/* Ignore during start/stop, we handle this in other places */
|
||||
if (priv->is_starting || priv->is_stopping)
|
||||
return;
|
||||
|
||||
if (priv->is_running)
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_add_source (SpProfiler *profiler,
|
||||
SpSource *source)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (SP_IS_SOURCE (source));
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
g_return_if_fail (priv->is_starting == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
|
||||
g_signal_connect_object (source,
|
||||
"failed",
|
||||
G_CALLBACK (sp_local_profiler_source_failed),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (source,
|
||||
"finished",
|
||||
G_CALLBACK (sp_local_profiler_source_finished),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (source,
|
||||
"ready",
|
||||
G_CALLBACK (sp_local_profiler_source_ready),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
|
||||
g_ptr_array_add (priv->sources, g_object_ref (source));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_add_pid (SpProfiler *profiler,
|
||||
GPid pid)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (pid > -1);
|
||||
g_return_if_fail (priv->is_starting == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
|
||||
g_array_append_val (priv->pids, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_remove_pid (SpProfiler *profiler,
|
||||
GPid pid)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (pid > -1);
|
||||
g_return_if_fail (priv->is_starting == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
|
||||
for (i = 0; i < priv->pids->len; i++)
|
||||
{
|
||||
GPid ele = g_array_index (priv->pids, GPid, i);
|
||||
|
||||
if (ele == pid)
|
||||
{
|
||||
g_array_remove_index_fast (priv->pids, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const GPid *
|
||||
sp_local_profiler_get_pids (SpProfiler *profiler,
|
||||
guint *n_pids)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_LOCAL_PROFILER (self), NULL);
|
||||
g_return_val_if_fail (n_pids != NULL, NULL);
|
||||
|
||||
*n_pids = priv->pids->len;
|
||||
|
||||
return (GPid *)(gpointer)priv->pids->data;
|
||||
}
|
||||
|
||||
static SpCaptureWriter *
|
||||
sp_local_profiler_get_writer (SpProfiler *profiler)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_LOCAL_PROFILER (self), NULL);
|
||||
|
||||
return priv->writer;
|
||||
}
|
||||
|
||||
static void
|
||||
profiler_iface_init (SpProfilerInterface *iface)
|
||||
{
|
||||
iface->add_pid = sp_local_profiler_add_pid;
|
||||
iface->add_source = sp_local_profiler_add_source;
|
||||
iface->get_pids = sp_local_profiler_get_pids;
|
||||
iface->get_writer = sp_local_profiler_get_writer;
|
||||
iface->remove_pid = sp_local_profiler_remove_pid;
|
||||
iface->set_writer = sp_local_profiler_set_writer;
|
||||
iface->start = sp_local_profiler_start;
|
||||
iface->stop = sp_local_profiler_stop;
|
||||
iface->stopped = sp_local_profiler_real_stopped;
|
||||
}
|
||||
40
src/libsysprof/sp-local-profiler.h
Normal file
40
src/libsysprof/sp-local-profiler.h
Normal file
@ -0,0 +1,40 @@
|
||||
/* sp-local-profiler.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_LOCAL_PROFILER_H
|
||||
#define SP_LOCAL_PROFILER_H
|
||||
|
||||
#include "sp-profiler.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_LOCAL_PROFILER (sp_local_profiler_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpLocalProfiler, sp_local_profiler, SP, LOCAL_PROFILER, GObject)
|
||||
|
||||
struct _SpLocalProfilerClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
gpointer padding[8];
|
||||
};
|
||||
|
||||
SpProfiler *sp_local_profiler_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_LOCAL_PROFILER_H */
|
||||
119
src/libsysprof/sp-map-lookaside.c
Normal file
119
src/libsysprof/sp-map-lookaside.c
Normal file
@ -0,0 +1,119 @@
|
||||
/* sp-map-lookaside.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "sp-map-lookaside.h"
|
||||
|
||||
struct _SpMapLookaside
|
||||
{
|
||||
GSequence *seq;
|
||||
GStringChunk *chunk;
|
||||
};
|
||||
|
||||
static gint
|
||||
sp_map_compare (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
const SpMap *map_a = a;
|
||||
const SpMap *map_b = b;
|
||||
|
||||
return sp_capture_address_compare (map_a->start, map_b->start);
|
||||
}
|
||||
|
||||
static gint
|
||||
sp_map_compare_in_range (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
const SpMap *map_a = a;
|
||||
const SpMap *map_b = b;
|
||||
|
||||
/*
|
||||
* map_b is the needle for the search.
|
||||
* Only map_b->start is set.
|
||||
*/
|
||||
|
||||
if ((map_b->start >= map_a->start) && (map_b->start < map_a->end))
|
||||
return 0;
|
||||
|
||||
return sp_capture_address_compare (map_a->start, map_b->start);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_map_free (gpointer data)
|
||||
{
|
||||
SpMap *map = data;
|
||||
|
||||
g_slice_free (SpMap, map);
|
||||
}
|
||||
|
||||
SpMapLookaside *
|
||||
sp_map_lookaside_new (void)
|
||||
{
|
||||
SpMapLookaside *ret;
|
||||
|
||||
ret = g_slice_new (SpMapLookaside);
|
||||
ret->seq = g_sequence_new (sp_map_free);
|
||||
ret->chunk = g_string_chunk_new (4096);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
sp_map_lookaside_free (SpMapLookaside *self)
|
||||
{
|
||||
g_sequence_free (self->seq);
|
||||
g_string_chunk_free (self->chunk);
|
||||
g_slice_free (SpMapLookaside, self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_map_lookaside_insert (SpMapLookaside *self,
|
||||
const SpMap *map)
|
||||
{
|
||||
SpMap *copy;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (map != NULL);
|
||||
|
||||
copy = g_slice_new (SpMap);
|
||||
copy->start = map->start;
|
||||
copy->end = map->end;
|
||||
copy->offset = map->offset;
|
||||
copy->inode = map->inode;
|
||||
copy->filename = g_string_chunk_insert_const (self->chunk, map->filename);
|
||||
|
||||
g_sequence_insert_sorted (self->seq, copy, sp_map_compare, NULL);
|
||||
}
|
||||
|
||||
const SpMap *
|
||||
sp_map_lookaside_lookup (SpMapLookaside *self,
|
||||
SpCaptureAddress address)
|
||||
{
|
||||
SpMap map = { address };
|
||||
GSequenceIter *iter;
|
||||
|
||||
g_assert (self != NULL);
|
||||
|
||||
iter = g_sequence_lookup (self->seq, &map, sp_map_compare_in_range, NULL);
|
||||
|
||||
if (iter != NULL)
|
||||
return g_sequence_get (iter);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
50
src/libsysprof/sp-map-lookaside.h
Normal file
50
src/libsysprof/sp-map-lookaside.h
Normal file
@ -0,0 +1,50 @@
|
||||
/* sp-map-lookaside.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_MAP_LOOKASIDE_H
|
||||
#define SP_MAP_LOOKASIDE_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "sp-capture-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _SpMapLookaside SpMapLookaside;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureAddress start;
|
||||
SpCaptureAddress end;
|
||||
off_t offset;
|
||||
ino_t inode;
|
||||
const gchar *filename;
|
||||
} SpMap;
|
||||
|
||||
SpMapLookaside *sp_map_lookaside_new (void);
|
||||
void sp_map_lookaside_insert (SpMapLookaside *self,
|
||||
const SpMap *map);
|
||||
const SpMap *sp_map_lookaside_lookup (SpMapLookaside *self,
|
||||
SpCaptureAddress address);
|
||||
void sp_map_lookaside_free (SpMapLookaside *self);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpMapLookaside, sp_map_lookaside_free)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_MAP_LOOKASIDE_H */
|
||||
467
src/libsysprof/sp-memory-source.c
Normal file
467
src/libsysprof/sp-memory-source.c
Normal file
@ -0,0 +1,467 @@
|
||||
/* sp-memory-source.c
|
||||
*
|
||||
* Copyright 2018 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define G_LOG_DOMAIN "sp-memory-source"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sysprof-capture.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-memory-source.h"
|
||||
|
||||
#define BUF_SIZE 4096
|
||||
|
||||
struct _SpMemorySource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
/* Capture writer to deliver samples */
|
||||
SpCaptureWriter *writer;
|
||||
|
||||
/* 4k stat buffer for reading proc */
|
||||
gchar *stat_buf;
|
||||
|
||||
/* Array of MemStat */
|
||||
GArray *mem_stats;
|
||||
|
||||
/* Timeout to perform polling */
|
||||
guint timer_source;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GPid pid;
|
||||
int stat_fd;
|
||||
|
||||
union {
|
||||
struct {
|
||||
SpCaptureCounterValue used;
|
||||
gint64 total;
|
||||
gint64 avail;
|
||||
gint64 free;
|
||||
} sys;
|
||||
struct {
|
||||
SpCaptureCounterValue used;
|
||||
gint64 size;
|
||||
gint64 resident;
|
||||
gint64 shared;
|
||||
gint64 text;
|
||||
gint64 data;
|
||||
} proc;
|
||||
};
|
||||
|
||||
guint counter_ids[1];
|
||||
} MemStat;
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (SpMemorySource, sp_memory_source, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
static GHashTable *keys;
|
||||
|
||||
static void
|
||||
mem_stat_open (MemStat *st)
|
||||
{
|
||||
g_assert (st != NULL);
|
||||
g_assert (st->stat_fd == -1);
|
||||
|
||||
if (st->pid != -1)
|
||||
{
|
||||
g_autofree gchar *path = g_strdup_printf ("/proc/%d/statm", st->pid);
|
||||
|
||||
if (-1 == (st->stat_fd = open (path, O_RDONLY)))
|
||||
g_warning ("Failed to access statm for pid %d", st->pid);
|
||||
}
|
||||
else
|
||||
{
|
||||
st->stat_fd = open ("/proc/meminfo", O_RDONLY);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_close (MemStat *st)
|
||||
{
|
||||
g_assert (st != NULL);
|
||||
|
||||
if (st->stat_fd != -1)
|
||||
{
|
||||
close (st->stat_fd);
|
||||
st->stat_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_parse_statm (MemStat *st,
|
||||
gchar *buf)
|
||||
{
|
||||
g_assert (st != NULL);
|
||||
g_assert (buf != NULL);
|
||||
|
||||
sscanf (buf,
|
||||
"%"G_GINT64_FORMAT" "
|
||||
"%"G_GINT64_FORMAT" "
|
||||
"%"G_GINT64_FORMAT" "
|
||||
"%"G_GINT64_FORMAT" "
|
||||
"%*1c "
|
||||
"%"G_GINT64_FORMAT,
|
||||
&st->proc.size,
|
||||
&st->proc.resident,
|
||||
&st->proc.shared,
|
||||
&st->proc.text,
|
||||
&st->proc.data);
|
||||
|
||||
st->proc.used.vdbl = st->proc.size - st->proc.shared - st->proc.text - st->proc.data;
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_parse_meminfo (MemStat *st,
|
||||
gchar *buf)
|
||||
{
|
||||
gchar *bufptr = buf;
|
||||
gchar *save = NULL;
|
||||
|
||||
g_assert (st != NULL);
|
||||
g_assert (buf != NULL);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
goffset off;
|
||||
gchar *key;
|
||||
gchar *value;
|
||||
gchar *unit;
|
||||
gint64 v64;
|
||||
gint64 *v64ptr;
|
||||
|
||||
/* Get the data key name */
|
||||
if (!(key = strtok_r (bufptr, " \n\t:", &save)))
|
||||
break;
|
||||
|
||||
bufptr = NULL;
|
||||
|
||||
/* Offset from self to save value. Stop after getting to
|
||||
* last value we care about.
|
||||
*/
|
||||
if (!(off = GPOINTER_TO_UINT (g_hash_table_lookup (keys, key))))
|
||||
break;
|
||||
|
||||
/* Get the data value */
|
||||
if (!(value = strtok_r (bufptr, " \n\t:", &save)))
|
||||
break;
|
||||
|
||||
/* Parse the numeric value of this column */
|
||||
v64 = g_ascii_strtoll (value, NULL, 10);
|
||||
if ((v64 == G_MININT64 || v64 == G_MAXINT64) && errno == ERANGE)
|
||||
break;
|
||||
|
||||
/* Get the data unit */
|
||||
unit = strtok_r (bufptr, " \n\t:", &save);
|
||||
|
||||
if (g_strcmp0 (unit, "kB") == 0)
|
||||
v64 *= 1024;
|
||||
else if (g_strcmp0 (unit, "mB") == 0)
|
||||
v64 *= 1024 * 1024;
|
||||
|
||||
v64ptr = (gint64 *)(gpointer)(((gchar *)st) + off);
|
||||
|
||||
*v64ptr = v64;
|
||||
}
|
||||
|
||||
/* Create pre-compiled value for used to simplify display */
|
||||
st->sys.used.vdbl = (gdouble)st->sys.total - (gdouble)st->sys.avail;
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_poll (MemStat *st,
|
||||
gchar *stat_buf)
|
||||
{
|
||||
gssize r;
|
||||
|
||||
g_assert (st != NULL);
|
||||
g_assert (st->stat_fd != -1);
|
||||
|
||||
/* Seek to begin of FD to reset the proc read */
|
||||
if ((r = lseek (st->stat_fd, 0, SEEK_SET)) < 0)
|
||||
return;
|
||||
|
||||
/* Now read the updated proc buffer */
|
||||
if ((r = read (st->stat_fd, stat_buf, BUF_SIZE)) < 0)
|
||||
return;
|
||||
|
||||
if (r < BUF_SIZE)
|
||||
stat_buf[r] = '\0';
|
||||
stat_buf[BUF_SIZE-1] = '\0';
|
||||
|
||||
if (st->pid == -1)
|
||||
mem_stat_parse_meminfo (st, stat_buf);
|
||||
else
|
||||
mem_stat_parse_statm (st, stat_buf);
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_publish (MemStat *st,
|
||||
SpCaptureWriter *writer,
|
||||
gint64 current_time)
|
||||
{
|
||||
g_assert (st != NULL);
|
||||
g_assert (writer != NULL);
|
||||
|
||||
sp_capture_writer_set_counters (writer,
|
||||
current_time,
|
||||
-1,
|
||||
st->pid,
|
||||
st->counter_ids,
|
||||
st->pid == -1 ? &st->sys.used : &st->proc.used,
|
||||
1);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_memory_source_new:
|
||||
*
|
||||
* Create a new #SpMemorySource.
|
||||
*
|
||||
* Use this source to record basic memory statistics about the system
|
||||
* such as Available, Free, and Total Memory.
|
||||
*
|
||||
* Returns: (transfer full): a newly created #SpMemorySource
|
||||
|
||||
* Since: 3.32
|
||||
*/
|
||||
SpSource *
|
||||
sp_memory_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_MEMORY_SOURCE, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_finalize (GObject *object)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)object;
|
||||
|
||||
if (self->timer_source != 0)
|
||||
{
|
||||
g_source_remove (self->timer_source);
|
||||
self->timer_source = 0;
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->stat_buf, g_free);
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->mem_stats, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_memory_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_class_init (SpMemorySourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_memory_source_finalize;
|
||||
|
||||
keys = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
|
||||
#define ADD_OFFSET(n,o) \
|
||||
g_hash_table_insert (keys, (gchar *)n, GUINT_TO_POINTER (o))
|
||||
ADD_OFFSET ("MemTotal", G_STRUCT_OFFSET (MemStat, sys.total));
|
||||
ADD_OFFSET ("MemFree", G_STRUCT_OFFSET (MemStat, sys.free));
|
||||
ADD_OFFSET ("MemAvailable", G_STRUCT_OFFSET (MemStat, sys.avail));
|
||||
#undef ADD_OFFSET
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_init (SpMemorySource *self)
|
||||
{
|
||||
self->stat_buf = g_malloc (BUF_SIZE);
|
||||
self->mem_stats = g_array_new (FALSE, FALSE, sizeof (MemStat));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
|
||||
g_assert (SP_IS_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
g_assert (self->writer == NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_prepare (SpSource *source)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
/* If no pids are registered, setup a system memory stat */
|
||||
if (self->mem_stats->len == 0)
|
||||
{
|
||||
MemStat st = {0};
|
||||
|
||||
st.pid = -1;
|
||||
st.stat_fd = -1;
|
||||
|
||||
g_array_append_val (self->mem_stats, st);
|
||||
}
|
||||
|
||||
/* Open FDs to all the necessary files in proc. We will re-use
|
||||
* them to avoid re-opening files later.
|
||||
*/
|
||||
for (guint i = 0; i < self->mem_stats->len; i++)
|
||||
{
|
||||
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
|
||||
SpCaptureCounter counters[5];
|
||||
guint base;
|
||||
|
||||
mem_stat_open (st);
|
||||
|
||||
if (st->pid == -1)
|
||||
{
|
||||
base = sp_capture_writer_request_counter (self->writer, 1);
|
||||
|
||||
g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category);
|
||||
g_strlcpy (counters[0].name, "Used", sizeof counters[0].name);
|
||||
g_strlcpy (counters[0].description, "Memory used by system", sizeof counters[0].description);
|
||||
|
||||
counters[0].id = st->counter_ids[0] = base;
|
||||
counters[0].type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
counters[0].value.vdbl = 0;
|
||||
|
||||
sp_capture_writer_define_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
-1,
|
||||
counters,
|
||||
1);
|
||||
}
|
||||
else
|
||||
{
|
||||
base = sp_capture_writer_request_counter (self->writer, 1);
|
||||
|
||||
g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category);
|
||||
g_strlcpy (counters[0].name, "Used", sizeof counters[0].name);
|
||||
g_strlcpy (counters[0].description, "Memory used by process", sizeof counters[0].description);
|
||||
|
||||
counters[0].id = st->counter_ids[0] = base;
|
||||
counters[0].type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
counters[0].value.vdbl = 0;
|
||||
|
||||
sp_capture_writer_define_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
st->pid,
|
||||
counters,
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
sp_source_emit_ready (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_memory_source_timer_cb (SpMemorySource *self)
|
||||
{
|
||||
gint64 current_time;
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
current_time = sp_clock_get_current_time ();
|
||||
|
||||
for (guint i = 0; i < self->mem_stats->len; i++)
|
||||
{
|
||||
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
|
||||
|
||||
mem_stat_poll (st, self->stat_buf);
|
||||
mem_stat_publish (st, self->writer, current_time);
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_start (SpSource *source)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
|
||||
/* Poll 4x/sec for memory stats */
|
||||
self->timer_source = g_timeout_add_full (G_PRIORITY_HIGH,
|
||||
1000 / 4,
|
||||
(GSourceFunc)sp_memory_source_timer_cb,
|
||||
self,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_stop (SpSource *source)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
|
||||
if (self->timer_source != 0)
|
||||
{
|
||||
g_source_remove (self->timer_source);
|
||||
self->timer_source = 0;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < self->mem_stats->len; i++)
|
||||
{
|
||||
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
|
||||
|
||||
mem_stat_close (st);
|
||||
}
|
||||
|
||||
sp_source_emit_finished (source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
MemStat st = {0};
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
|
||||
st.pid = pid;
|
||||
st.stat_fd = -1;
|
||||
|
||||
g_array_append_val (self->mem_stats, st);
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_memory_source_set_writer;
|
||||
iface->prepare = sp_memory_source_prepare;
|
||||
iface->start = sp_memory_source_start;
|
||||
iface->stop = sp_memory_source_stop;
|
||||
iface->add_pid = sp_memory_source_add_pid;
|
||||
}
|
||||
33
src/libsysprof/sp-memory-source.h
Normal file
33
src/libsysprof/sp-memory-source.h
Normal file
@ -0,0 +1,33 @@
|
||||
/* sp-memory-source.h
|
||||
*
|
||||
* Copyright 2018 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_MEMORY_SOURCE (sp_memory_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpMemorySource, sp_memory_source, SP, MEMORY_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_memory_source_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user