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:
Christian Hergert
2019-05-07 20:52:05 -07:00
parent 5323cffdb3
commit 1708ad1b48
193 changed files with 1400 additions and 1136 deletions

View 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' ],
)

View 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;
}

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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

View 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

View 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;
}

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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)))

View 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);
}

View 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);
}

View 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);
}

View 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>

View 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)

View 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);
}

View 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 */

View 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;
}

View 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

File diff suppressed because it is too large Load Diff

View 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 */

View 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]);
}
}

View 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 */

View 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;
}

View 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 */

View 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);
}

View 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 */

View 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, &param, &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);
}

View 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 */

View 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));
}

View 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 */

View 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;
}
}
}

View 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 */

View 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]);
}
}

View 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

View 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);
}

View 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 */

File diff suppressed because it is too large Load Diff

View 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 */

View 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);
}

View 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 */

View 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);
}

View 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 */

View 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);
}
}
}

View 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 */

View 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;
}
}
}

View 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 */

View 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;
}

View 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 */

View 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 */

View 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);
}
}

View 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 */

View 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));
}
}

View 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 */

View 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;
}

View 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 */

View 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;
}

View 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 */

View 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 */

View 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>

View 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 &lt;b&gt;Record&lt;/b&gt; 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>

View 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>

View 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>

View 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>

View 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 &lt;a href="help:sysprof"&gt;sysprof-cli&lt;/a&gt; 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>

View 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
View 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
View 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

View 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
View 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
View 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
*/

View 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);

View 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)

View 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 */

View 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));
}

View 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 */

View 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)

View 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

View 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);
}

View 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 */

View 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);
}

View 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 */

View 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;
}

View 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 */

View 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);
}

View 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 */

View 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);
}

View 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 */

View 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;
}

View 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 */

View 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;
}

View 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 */

View 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;
}

View 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 */

View 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;
}

View 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