mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
tree: start on massive tree refactor
The big thing going on here is that we are going to split up the libraries a bit better, and remove GObject from the capture library. The libsysprof library will bring in the capture library statically, so we can export the symbols we want. Eventually, we will bump the version to sysprof-3, but not yet.
This commit is contained in:
532
src/libsysprof/binfile.c
Normal file
532
src/libsysprof/binfile.c
Normal file
@ -0,0 +1,532 @@
|
||||
/* MemProf -- memory profiler and leak detector
|
||||
* Copyright 1999, 2000, 2001, Red Hat, Inc.
|
||||
* Copyright 2002, Kristian Rietveld
|
||||
*
|
||||
* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2004, 2005, 2006, 2007, Soeren Sandmann
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/* Most interesting code in this file is lifted from bfdutils.c
|
||||
* and process.c from Memprof,
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "binfile.h"
|
||||
#include "elfparser.h"
|
||||
#include "sp-symbol-dirs.h"
|
||||
|
||||
struct bin_file_t
|
||||
{
|
||||
int ref_count;
|
||||
|
||||
GList * elf_files;
|
||||
|
||||
char * filename;
|
||||
|
||||
char * undefined_name;
|
||||
|
||||
gulong text_offset;
|
||||
|
||||
gboolean inode_check;
|
||||
ino_t inode;
|
||||
};
|
||||
|
||||
static ino_t
|
||||
read_inode (const char *filename)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (strcmp (filename, "[vdso]") == 0)
|
||||
return (ino_t)0;
|
||||
|
||||
if (stat (filename, &statbuf) < 0)
|
||||
return (ino_t)-1;
|
||||
|
||||
return statbuf.st_ino;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
already_warned (const char *name)
|
||||
{
|
||||
static GPtrArray *warnings;
|
||||
guint i;
|
||||
|
||||
if (!warnings)
|
||||
warnings = g_ptr_array_new ();
|
||||
|
||||
for (i = 0; i < warnings->len; ++i)
|
||||
{
|
||||
if (strcmp (warnings->pdata[i], name) == 0)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_ptr_array_add (warnings, g_strdup (name));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static ElfParser *
|
||||
get_build_id_file (ElfParser *elf)
|
||||
{
|
||||
const char *build_id;
|
||||
GList *tries = NULL, *list;
|
||||
char *init, *rest;
|
||||
ElfParser *result = NULL;
|
||||
char *tmp;
|
||||
|
||||
build_id = elf_parser_get_build_id (elf);
|
||||
|
||||
if (!build_id)
|
||||
return NULL;
|
||||
|
||||
if (strlen (build_id) < 4)
|
||||
return NULL;
|
||||
|
||||
init = g_strndup (build_id, 2);
|
||||
rest = g_strdup_printf ("%s%s", build_id + 2, ".debug");
|
||||
|
||||
tmp = g_build_filename (
|
||||
"/usr", "lib", "debug", ".build-id", init, rest, NULL);
|
||||
tries = g_list_append (tries, tmp);
|
||||
|
||||
tmp = g_build_filename (DEBUGDIR, ".build-id", init, rest, NULL);
|
||||
tries = g_list_append (tries, tmp);
|
||||
|
||||
for (list = tries; list != NULL; list = list->next)
|
||||
{
|
||||
char *name = list->data;
|
||||
ElfParser *parser = elf_parser_new (name, NULL);
|
||||
|
||||
if (parser)
|
||||
{
|
||||
const char *file_id = elf_parser_get_build_id (parser);
|
||||
|
||||
if (file_id && strcmp (build_id, file_id) == 0)
|
||||
{
|
||||
result = parser;
|
||||
break;
|
||||
}
|
||||
|
||||
elf_parser_free (parser);
|
||||
}
|
||||
}
|
||||
|
||||
g_list_foreach (tries, (GFunc)g_free, NULL);
|
||||
g_list_free (tries);
|
||||
|
||||
g_free (init);
|
||||
g_free (rest);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static ElfParser *
|
||||
get_debuglink_file (ElfParser *elf,
|
||||
const char *filename,
|
||||
char **new_name)
|
||||
{
|
||||
const char *basename;
|
||||
char *dir;
|
||||
guint32 crc32;
|
||||
gchar **tries;
|
||||
ElfParser *result = NULL;
|
||||
const char *build_id;
|
||||
guint i;
|
||||
|
||||
if (!elf)
|
||||
return NULL;
|
||||
|
||||
basename = elf_parser_get_debug_link (elf, &crc32);
|
||||
|
||||
build_id = elf_parser_get_build_id (elf);
|
||||
|
||||
#if 0
|
||||
g_print (" debug link for %s is %s\n", filename, basename);
|
||||
#endif
|
||||
|
||||
if (!basename)
|
||||
return NULL;
|
||||
|
||||
dir = g_path_get_dirname (filename);
|
||||
|
||||
tries = sp_symbol_dirs_get_paths (dir, basename);
|
||||
|
||||
for (i = 0; tries[i]; i++)
|
||||
{
|
||||
const char *name = tries[i];
|
||||
ElfParser *parser = elf_parser_new (name, NULL);
|
||||
guint32 file_crc;
|
||||
const char *file_build_id;
|
||||
|
||||
if (parser)
|
||||
{
|
||||
/* If both files have build ids, and they don't match,
|
||||
* there is no point computing a CRC32 that we know
|
||||
* will fail
|
||||
*/
|
||||
file_build_id = elf_parser_get_build_id (parser);
|
||||
if (build_id && file_build_id && strcmp (build_id, file_build_id) != 0)
|
||||
goto skip;
|
||||
|
||||
file_crc = elf_parser_get_crc32 (parser);
|
||||
|
||||
if (file_crc == crc32)
|
||||
{
|
||||
result = parser;
|
||||
*new_name = g_strdup (name);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!already_warned (name))
|
||||
{
|
||||
g_print ("warning: %s has wrong crc %x, %s has crc %x)\n",
|
||||
name, file_crc, filename, crc32);
|
||||
}
|
||||
}
|
||||
|
||||
skip:
|
||||
elf_parser_free (parser);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (dir);
|
||||
g_strfreev (tries);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static GList *
|
||||
get_debug_binaries (GList *files,
|
||||
ElfParser *elf,
|
||||
const char *filename)
|
||||
{
|
||||
ElfParser *build_id_file;
|
||||
GHashTable *seen_names;
|
||||
GList *free_us = NULL;
|
||||
|
||||
build_id_file = get_build_id_file (elf);
|
||||
|
||||
if (build_id_file)
|
||||
return g_list_prepend (files, build_id_file);
|
||||
|
||||
/* .gnu_debuglink is actually a chain of debuglinks, and
|
||||
* there have been real-world cases where following it was
|
||||
* necessary to get useful debug information.
|
||||
*/
|
||||
seen_names = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
|
||||
while (elf)
|
||||
{
|
||||
char *debug_name;
|
||||
|
||||
if (g_hash_table_lookup (seen_names, filename))
|
||||
break;
|
||||
|
||||
g_hash_table_insert (seen_names, (char *)filename, (char *)filename);
|
||||
|
||||
elf = get_debuglink_file (elf, filename, &debug_name);
|
||||
|
||||
if (elf)
|
||||
{
|
||||
files = g_list_prepend (files, elf);
|
||||
free_us = g_list_prepend (free_us, debug_name);
|
||||
filename = debug_name;
|
||||
}
|
||||
}
|
||||
|
||||
g_list_foreach (free_us, (GFunc)g_free, NULL);
|
||||
g_list_free (free_us);
|
||||
|
||||
g_hash_table_destroy (seen_names);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
static char **
|
||||
get_lines (const char *format,
|
||||
...)
|
||||
G_GNUC_PRINTF (1, 2);
|
||||
|
||||
static char **
|
||||
get_lines (const char *format,
|
||||
...)
|
||||
{
|
||||
va_list args;
|
||||
char *filename;
|
||||
char **result = NULL;
|
||||
char *contents;
|
||||
|
||||
va_start (args, format);
|
||||
filename = g_strdup_vprintf (format, args);
|
||||
va_end (args);
|
||||
|
||||
if (g_file_get_contents (filename, &contents, NULL, NULL))
|
||||
{
|
||||
result = g_strsplit (contents, "\n", -1);
|
||||
|
||||
g_free (contents);
|
||||
}
|
||||
|
||||
g_free (filename);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const uint8_t *
|
||||
get_vdso_bytes (size_t *length)
|
||||
{
|
||||
static const uint8_t *bytes = NULL;
|
||||
static size_t n_bytes = 0;
|
||||
static gboolean has_data;
|
||||
|
||||
if (!has_data)
|
||||
{
|
||||
char **lines = get_lines ("/proc/%d/maps", getpid());
|
||||
int i;
|
||||
|
||||
for (i = 0; lines[i] != NULL; ++i)
|
||||
{
|
||||
char file[256];
|
||||
gulong start;
|
||||
gulong end;
|
||||
int count = sscanf (
|
||||
lines[i], "%lx-%lx %*15s %*x %*x:%*x %*u %255s",
|
||||
&start, &end, file);
|
||||
|
||||
if (count == 3 && strcmp (file, "[vdso]") == 0)
|
||||
{
|
||||
n_bytes = end - start;
|
||||
|
||||
/* Dup the memory here so that valgrind will only
|
||||
* report one 1 byte invalid read instead of
|
||||
* a ton when the elf parser scans the vdso
|
||||
*
|
||||
* The reason we get a spurious invalid read from
|
||||
* valgrind is that we are getting the address directly
|
||||
* from /proc/maps, and valgrind knows that its mmap()
|
||||
* wrapper never returned that address. But since it
|
||||
* is a legal mapping, it is legal to read it.
|
||||
*/
|
||||
bytes = g_memdup ((uint8_t *)start, n_bytes);
|
||||
|
||||
has_data = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (length)
|
||||
*length = n_bytes;
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bin_file_t *
|
||||
bin_file_new (const char *filename)
|
||||
{
|
||||
ElfParser *elf = NULL;
|
||||
bin_file_t *bf;
|
||||
|
||||
bf = g_new0 (bin_file_t, 1);
|
||||
|
||||
bf->inode_check = FALSE;
|
||||
bf->filename = g_strdup (filename);
|
||||
bf->undefined_name = g_strdup_printf ("In file %s", filename);
|
||||
bf->ref_count = 1;
|
||||
bf->elf_files = NULL;
|
||||
|
||||
if (strcmp (filename, "[vdso]") == 0)
|
||||
{
|
||||
const guint8 *vdso_bytes;
|
||||
gsize length;
|
||||
|
||||
vdso_bytes = get_vdso_bytes (&length);
|
||||
|
||||
if (vdso_bytes)
|
||||
elf = elf_parser_new_from_data (vdso_bytes, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
elf = elf_parser_new (filename, NULL);
|
||||
}
|
||||
|
||||
if (elf)
|
||||
{
|
||||
/* We need the text offset of the actual binary, not the
|
||||
* (potential) debug binaries
|
||||
*/
|
||||
bf->text_offset = elf_parser_get_text_offset (elf);
|
||||
|
||||
bf->elf_files = get_debug_binaries (bf->elf_files, elf, filename);
|
||||
bf->elf_files = g_list_append (bf->elf_files, elf);
|
||||
|
||||
bf->inode = read_inode (filename);
|
||||
}
|
||||
|
||||
return bf;
|
||||
}
|
||||
|
||||
void
|
||||
bin_file_free (bin_file_t *bin_file)
|
||||
{
|
||||
if (--bin_file->ref_count == 0)
|
||||
{
|
||||
g_list_foreach (bin_file->elf_files, (GFunc)elf_parser_free, NULL);
|
||||
g_list_free (bin_file->elf_files);
|
||||
|
||||
g_free (bin_file->filename);
|
||||
g_free (bin_file->undefined_name);
|
||||
g_free (bin_file);
|
||||
}
|
||||
}
|
||||
|
||||
const bin_symbol_t *
|
||||
bin_file_lookup_symbol (bin_file_t *bin_file,
|
||||
gulong address)
|
||||
{
|
||||
GList *list;
|
||||
|
||||
#if 0
|
||||
g_print ("-=-=-=- \n");
|
||||
|
||||
g_print ("bin file lookup lookup %d\n", address);
|
||||
#endif
|
||||
|
||||
address -= bin_file->text_offset;
|
||||
|
||||
#if 0
|
||||
g_print ("lookup %d in %s\n", address, bin_file->filename);
|
||||
#endif
|
||||
|
||||
for (list = bin_file->elf_files; list != NULL; list = list->next)
|
||||
{
|
||||
ElfParser *elf = list->data;
|
||||
const ElfSym *sym = elf_parser_lookup_symbol (elf, address);
|
||||
|
||||
if (sym)
|
||||
{
|
||||
#if 0
|
||||
g_print ("found %lx => %s\n", address,
|
||||
bin_symbol_get_name (bin_file, sym));
|
||||
#endif
|
||||
return (const bin_symbol_t *)sym;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
g_print ("%lx undefined in %s (textoffset %x)\n",
|
||||
address + bin_file->text_offset,
|
||||
bin_file->filename,
|
||||
bin_file->text_offset);
|
||||
#endif
|
||||
|
||||
return (const bin_symbol_t *)bin_file->undefined_name;
|
||||
}
|
||||
|
||||
gboolean
|
||||
bin_file_check_inode (bin_file_t *bin_file,
|
||||
ino_t inode)
|
||||
{
|
||||
if (bin_file->inode == inode)
|
||||
return TRUE;
|
||||
|
||||
if (!bin_file->elf_files)
|
||||
return FALSE;
|
||||
|
||||
if (!bin_file->inode_check)
|
||||
{
|
||||
g_print ("warning: Inode mismatch for %s (disk: %"G_GUINT64_FORMAT", memory: %"G_GUINT64_FORMAT")\n",
|
||||
bin_file->filename, (guint64)bin_file->inode, (guint64)inode);
|
||||
|
||||
bin_file->inode_check = TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static const ElfSym *
|
||||
get_elf_sym (bin_file_t *file,
|
||||
const bin_symbol_t *symbol,
|
||||
ElfParser **elf_ret)
|
||||
{
|
||||
GList *list;
|
||||
|
||||
for (list = file->elf_files; list != NULL; list = list->next)
|
||||
{
|
||||
const ElfSym *sym = (const ElfSym *)symbol;
|
||||
ElfParser *elf = list->data;
|
||||
|
||||
if (elf_parser_owns_symbol (elf, sym))
|
||||
{
|
||||
*elf_ret = elf;
|
||||
return sym;
|
||||
}
|
||||
}
|
||||
|
||||
g_critical ("Internal error: unrecognized symbol pointer");
|
||||
|
||||
*elf_ret = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *
|
||||
bin_symbol_get_name (bin_file_t *file,
|
||||
const bin_symbol_t *symbol)
|
||||
{
|
||||
if (file->undefined_name == (char *)symbol)
|
||||
{
|
||||
return file->undefined_name;
|
||||
}
|
||||
else
|
||||
{
|
||||
ElfParser *elf;
|
||||
const ElfSym *sym;
|
||||
|
||||
sym = get_elf_sym (file, symbol, &elf);
|
||||
|
||||
return elf_parser_get_sym_name (elf, sym);
|
||||
}
|
||||
}
|
||||
|
||||
gulong
|
||||
bin_symbol_get_address (bin_file_t *file,
|
||||
const bin_symbol_t *symbol)
|
||||
{
|
||||
if (file->undefined_name == (char *)symbol)
|
||||
{
|
||||
return 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ElfParser *elf;
|
||||
const ElfSym *sym;
|
||||
|
||||
sym = get_elf_sym (file, symbol, &elf);
|
||||
|
||||
return elf_parser_get_sym_address (elf, sym);
|
||||
}
|
||||
}
|
||||
46
src/libsysprof/binfile.h
Normal file
46
src/libsysprof/binfile.h
Normal file
@ -0,0 +1,46 @@
|
||||
/* MemProf -- memory profiler and leak detector
|
||||
* Copyright 1999, 2000, 2001, Red Hat, Inc.
|
||||
* Copyright 2002, Kristian Rietveld
|
||||
*
|
||||
* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2004, Red Hat, Inc.
|
||||
* Copyright 2004, 2005, Soeren Sandmann
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef BIN_FILE_H
|
||||
#define BIN_FILE_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef struct bin_file_t bin_file_t;
|
||||
typedef struct bin_symbol_t bin_symbol_t;
|
||||
|
||||
/* Binary File */
|
||||
|
||||
bin_file_t * bin_file_new (const char *filename);
|
||||
void bin_file_free (bin_file_t *bin_file);
|
||||
const bin_symbol_t *bin_file_lookup_symbol (bin_file_t *bin_file,
|
||||
gulong address);
|
||||
gboolean bin_file_check_inode (bin_file_t *bin_file,
|
||||
ino_t inode);
|
||||
const char * bin_symbol_get_name (bin_file_t *bin_file,
|
||||
const bin_symbol_t *symbol);
|
||||
gulong bin_symbol_get_address (bin_file_t *bin_file,
|
||||
const bin_symbol_t *symbol);
|
||||
|
||||
#endif
|
||||
40
src/libsysprof/demangle.cpp
Normal file
40
src/libsysprof/demangle.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
/* demangle.cpp
|
||||
*
|
||||
* Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "demangle.h"
|
||||
|
||||
gchar *
|
||||
sysprof_cplus_demangle (const gchar *name)
|
||||
{
|
||||
char *real_name;
|
||||
gchar *ret;
|
||||
int status;
|
||||
|
||||
real_name = abi::__cxa_demangle (name, 0, 0, &status);
|
||||
|
||||
if (real_name == NULL)
|
||||
return NULL;
|
||||
|
||||
ret = g_strdup (real_name);
|
||||
free (real_name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
30
src/libsysprof/demangle.h
Normal file
30
src/libsysprof/demangle.h
Normal file
@ -0,0 +1,30 @@
|
||||
/* demangle.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DEMANGLE_H
|
||||
#define DEMANGLE_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gchar *sysprof_cplus_demangle (const gchar *name);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* DEMANGLE_H */
|
||||
811
src/libsysprof/elfparser.c
Normal file
811
src/libsysprof/elfparser.c
Normal file
@ -0,0 +1,811 @@
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2006, 2007, Soeren Sandmann
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <elf.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "demangle.h"
|
||||
#include "elfparser.h"
|
||||
|
||||
typedef struct Section Section;
|
||||
|
||||
struct ElfSym
|
||||
{
|
||||
gulong table;
|
||||
gulong offset;
|
||||
gulong address;
|
||||
};
|
||||
|
||||
struct Section
|
||||
{
|
||||
const gchar * name;
|
||||
gsize offset;
|
||||
gsize size;
|
||||
gboolean allocated;
|
||||
gulong load_address;
|
||||
guint type;
|
||||
};
|
||||
|
||||
struct ElfParser
|
||||
{
|
||||
gboolean is_64;
|
||||
const guchar * data;
|
||||
gsize length;
|
||||
|
||||
guint n_sections;
|
||||
Section ** sections;
|
||||
|
||||
guint n_symbols;
|
||||
ElfSym * symbols;
|
||||
gsize sym_strings;
|
||||
|
||||
GMappedFile * file;
|
||||
|
||||
char * filename;
|
||||
|
||||
gboolean checked_build_id;
|
||||
char * build_id;
|
||||
|
||||
const Section * text_section;
|
||||
};
|
||||
|
||||
/* FIXME: All of these should in principle do endian swapping,
|
||||
* but sysprof never has to deal with binaries of a different
|
||||
* endianness than sysprof itself
|
||||
*/
|
||||
#define GET_FIELD(parser, offset, struct_name, idx, field_name) \
|
||||
(((parser))->is_64? \
|
||||
((Elf64_ ## struct_name *)(gpointer)(((parser)->data + offset)) + (idx))->field_name : \
|
||||
((Elf32_ ## struct_name *)(gpointer)(((parser)->data + offset)) + (idx))->field_name)
|
||||
|
||||
#define GET_UINT32(parser, offset) \
|
||||
*((uint32_t *)(gpointer)(parser->data + offset)) \
|
||||
|
||||
#define GET_SIZE(parser, struct_name) \
|
||||
(((parser)->is_64? \
|
||||
sizeof (Elf64_ ## struct_name) : \
|
||||
sizeof (Elf32_ ## struct_name)))
|
||||
|
||||
#define MAKE_ELF_UINT_ACCESSOR(field_name) \
|
||||
static uint64_t field_name (ElfParser *parser) \
|
||||
{ \
|
||||
return GET_FIELD (parser, 0, Ehdr, 0, field_name); \
|
||||
}
|
||||
|
||||
MAKE_ELF_UINT_ACCESSOR (e_shoff)
|
||||
MAKE_ELF_UINT_ACCESSOR (e_shnum)
|
||||
MAKE_ELF_UINT_ACCESSOR (e_shstrndx)
|
||||
|
||||
#define MAKE_SECTION_HEADER_ACCESSOR(field_name) \
|
||||
static uint64_t field_name (ElfParser *parser, int nth_section) \
|
||||
{ \
|
||||
gsize offset = e_shoff (parser); \
|
||||
\
|
||||
return GET_FIELD (parser, offset, Shdr, nth_section, field_name); \
|
||||
}
|
||||
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_name);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_type);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_flags);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_addr);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_offset);
|
||||
MAKE_SECTION_HEADER_ACCESSOR (sh_size);
|
||||
|
||||
#define MAKE_SYMBOL_ACCESSOR(field_name) \
|
||||
static uint64_t field_name (ElfParser *parser, gulong offset, gulong nth) \
|
||||
{ \
|
||||
return GET_FIELD (parser, offset, Sym, nth, field_name); \
|
||||
}
|
||||
|
||||
MAKE_SYMBOL_ACCESSOR(st_name);
|
||||
MAKE_SYMBOL_ACCESSOR(st_info);
|
||||
MAKE_SYMBOL_ACCESSOR(st_value);
|
||||
MAKE_SYMBOL_ACCESSOR(st_size);
|
||||
MAKE_SYMBOL_ACCESSOR(st_shndx);
|
||||
|
||||
static void
|
||||
section_free (Section *section)
|
||||
{
|
||||
g_free (section);
|
||||
}
|
||||
|
||||
static const Section *
|
||||
find_section (ElfParser *parser,
|
||||
const char *name,
|
||||
guint type)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < parser->n_sections; ++i)
|
||||
{
|
||||
Section *section = parser->sections[i];
|
||||
|
||||
if (strcmp (section->name, name) == 0 && section->type == type)
|
||||
return section;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
parse_elf_signature (const guchar *data,
|
||||
gsize length,
|
||||
gboolean *is_64,
|
||||
gboolean *is_be)
|
||||
{
|
||||
/* FIXME: this function should be able to return an error */
|
||||
if (length < EI_NIDENT)
|
||||
{
|
||||
/* FIXME set error */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (data[EI_CLASS] != ELFCLASS32 &&
|
||||
data[EI_CLASS] != ELFCLASS64)
|
||||
{
|
||||
/* FIXME set error */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (data[EI_DATA] != ELFDATA2LSB &&
|
||||
data[EI_DATA] != ELFDATA2MSB)
|
||||
{
|
||||
/* FIXME set error */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (is_64)
|
||||
*is_64 = (data[EI_CLASS] == ELFCLASS64);
|
||||
|
||||
if (is_be)
|
||||
*is_be = (data[EI_DATA] == ELFDATA2MSB);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
ElfParser *
|
||||
elf_parser_new_from_data (const guchar *data,
|
||||
gsize length)
|
||||
{
|
||||
ElfParser *parser;
|
||||
gboolean is_64, is_big_endian;
|
||||
int section_names_idx;
|
||||
const guchar *section_names;
|
||||
G_GNUC_UNUSED gsize section_headers;
|
||||
guint i;
|
||||
|
||||
if (!parse_elf_signature (data, length, &is_64, &is_big_endian))
|
||||
{
|
||||
/* FIXME: set error */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
parser = g_new0 (ElfParser, 1);
|
||||
|
||||
parser->is_64 = is_64;
|
||||
parser->data = data;
|
||||
parser->length = length;
|
||||
|
||||
#if 0
|
||||
g_print (" new parser : %p\n", parser);
|
||||
#endif
|
||||
|
||||
/* Read ELF header */
|
||||
|
||||
parser->n_sections = e_shnum (parser);
|
||||
section_names_idx = e_shstrndx (parser);
|
||||
section_headers = e_shoff (parser);
|
||||
|
||||
/* Read section headers */
|
||||
parser->sections = g_new0 (Section *, parser->n_sections);
|
||||
|
||||
section_names = parser->data + sh_offset (parser, section_names_idx);
|
||||
|
||||
for (i = 0; i < parser->n_sections; ++i)
|
||||
{
|
||||
Section *section = g_new (Section, 1);
|
||||
|
||||
section->name = (char *)(section_names + sh_name (parser, i));
|
||||
section->size = sh_size (parser, i);
|
||||
section->offset = sh_offset (parser, i);
|
||||
section->allocated = !!(sh_flags (parser, i) & SHF_ALLOC);
|
||||
|
||||
if (section->allocated)
|
||||
section->load_address = sh_addr (parser, i);
|
||||
else
|
||||
section->load_address = 0;
|
||||
|
||||
section->type = sh_type (parser, i);
|
||||
|
||||
parser->sections[i] = section;
|
||||
}
|
||||
|
||||
/* Cache the text section */
|
||||
parser->text_section = find_section (parser, ".text", SHT_PROGBITS);
|
||||
if (!parser->text_section)
|
||||
parser->text_section = find_section (parser, ".text", SHT_NOBITS);
|
||||
|
||||
parser->filename = NULL;
|
||||
parser->build_id = NULL;
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
ElfParser *
|
||||
elf_parser_new (const char *filename,
|
||||
GError **err)
|
||||
{
|
||||
const guchar *data;
|
||||
gsize length;
|
||||
ElfParser *parser;
|
||||
|
||||
GMappedFile *file = g_mapped_file_new (filename, FALSE, NULL);
|
||||
|
||||
if (!file)
|
||||
return NULL;
|
||||
|
||||
#if 0
|
||||
g_print ("elf parser new : %s\n", filename);
|
||||
#endif
|
||||
|
||||
data = (guchar *)g_mapped_file_get_contents (file);
|
||||
length = g_mapped_file_get_length (file);
|
||||
|
||||
#if 0
|
||||
g_print ("data %p: for %s\n", data, filename);
|
||||
#endif
|
||||
|
||||
parser = elf_parser_new_from_data (data, length);
|
||||
|
||||
#if 0
|
||||
g_print ("Parser for %s: %p\n", filename, parser);
|
||||
#endif
|
||||
|
||||
if (!parser)
|
||||
{
|
||||
g_mapped_file_unref (file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
parser->filename = g_strdup (filename);
|
||||
|
||||
parser->file = file;
|
||||
|
||||
#if 0
|
||||
g_print ("Elf file: %s (debug: %s)\n",
|
||||
filename, elf_parser_get_debug_link (parser, NULL));
|
||||
|
||||
if (!parser->symbols)
|
||||
g_print ("at this point %s has no symbols\n", filename);
|
||||
#endif
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
guint32
|
||||
elf_parser_get_crc32 (ElfParser *parser)
|
||||
{
|
||||
static const unsigned long crc32_table[256] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
};
|
||||
const guchar *data;
|
||||
gsize length;
|
||||
gulong crc;
|
||||
gsize i;
|
||||
|
||||
data = parser->data;
|
||||
length = parser->length;
|
||||
|
||||
crc = 0xffffffff;
|
||||
|
||||
madvise ((char *)data, length, MADV_SEQUENTIAL);
|
||||
|
||||
for (i = 0; i < length; ++i)
|
||||
crc = crc32_table[(crc ^ data[i]) & 0xff] ^ (crc >> 8);
|
||||
|
||||
/* We just read the entire file into memory, but we only really
|
||||
* need the symbol table, so swap the whole thing out.
|
||||
*
|
||||
* We could be more exact here, but it's only a few minor
|
||||
* pagefaults.
|
||||
*/
|
||||
if (parser->file)
|
||||
madvise ((char *)data, length, MADV_DONTNEED);
|
||||
|
||||
return ~crc & 0xffffffff;
|
||||
}
|
||||
|
||||
void
|
||||
elf_parser_free (ElfParser *parser)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < parser->n_sections; ++i)
|
||||
section_free (parser->sections[i]);
|
||||
g_free (parser->sections);
|
||||
|
||||
if (parser->file)
|
||||
g_mapped_file_unref (parser->file);
|
||||
|
||||
g_free (parser->symbols);
|
||||
|
||||
if (parser->filename)
|
||||
g_free (parser->filename);
|
||||
|
||||
if (parser->build_id)
|
||||
g_free (parser->build_id);
|
||||
|
||||
g_free (parser);
|
||||
}
|
||||
|
||||
gchar *
|
||||
elf_demangle (const char *name)
|
||||
{
|
||||
gchar *demangled = sysprof_cplus_demangle (name);
|
||||
|
||||
if (demangled)
|
||||
return demangled;
|
||||
else
|
||||
return g_strdup (name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Looking up symbols
|
||||
*/
|
||||
static int
|
||||
compare_sym (const void *a, const void *b)
|
||||
{
|
||||
const ElfSym *sym_a = a;
|
||||
const ElfSym *sym_b = b;
|
||||
|
||||
if (sym_a->address < sym_b->address)
|
||||
return -1;
|
||||
else if (sym_a->address == sym_b->address)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
dump_symbols (ElfParser *parser, ElfSym *syms, guint n_syms)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n_syms; ++i)
|
||||
{
|
||||
ElfSym *s = &(syms[i]);
|
||||
|
||||
g_print (" %s: %lx\n", elf_parser_get_sym_name (parser, s), s->address);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
read_table (ElfParser *parser,
|
||||
const Section *sym_table,
|
||||
const Section *str_table)
|
||||
{
|
||||
int sym_size = GET_SIZE (parser, Sym);
|
||||
guint i, n_symbols;
|
||||
|
||||
#if 0
|
||||
g_print ("elf: Reading table for %s\n", parser->filename? parser->filename : "<unknown>");
|
||||
#endif
|
||||
|
||||
parser->n_symbols = sym_table->size / sym_size;
|
||||
parser->symbols = g_new (ElfSym, parser->n_symbols);
|
||||
|
||||
#if 0
|
||||
g_print ("sym table offset: %d\n", sym_table->offset);
|
||||
#endif
|
||||
|
||||
n_symbols = 0;
|
||||
#if 0
|
||||
g_print ("n syms: %d\n", parser->n_symbols);
|
||||
#endif
|
||||
for (i = 0; i < parser->n_symbols; ++i)
|
||||
{
|
||||
guint info;
|
||||
gulong addr;
|
||||
gulong shndx;
|
||||
|
||||
info = st_info (parser, sym_table->offset, i);
|
||||
addr = st_value (parser, sym_table->offset, i);
|
||||
shndx = st_shndx (parser, sym_table->offset, i);
|
||||
|
||||
#if 0
|
||||
g_print ("read symbol: %s (section: %d)\n", get_string_indirct (parser->parser,
|
||||
parser->sym_format, "st_name",
|
||||
str_table->offset),
|
||||
shndx);
|
||||
#endif
|
||||
|
||||
if (addr != 0 &&
|
||||
shndx < parser->n_sections &&
|
||||
parser->sections[shndx] == parser->text_section &&
|
||||
(info & 0xf) == STT_FUNC &&
|
||||
((info >> 4) == STB_GLOBAL ||
|
||||
(info >> 4) == STB_LOCAL ||
|
||||
(info >> 4) == STB_WEAK))
|
||||
{
|
||||
parser->symbols[n_symbols].address = addr;
|
||||
parser->symbols[n_symbols].table = sym_table->offset;
|
||||
parser->symbols[n_symbols].offset = i;
|
||||
|
||||
n_symbols++;
|
||||
|
||||
#if 0
|
||||
g_print (" symbol: %s: %lx\n",
|
||||
get_string_indirect (parser->parser,
|
||||
parser->sym_format, "st_name",
|
||||
str_table->offset),
|
||||
addr - parser->text_section->load_address);
|
||||
g_print (" sym %d in %p (info: %d:%d) (func:global %d:%d)\n",
|
||||
addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL);
|
||||
#endif
|
||||
}
|
||||
else if (addr != 0)
|
||||
{
|
||||
#if 0
|
||||
g_print (" rejecting %d in %p (info: %d:%d) (func:global %d:%d)\n",
|
||||
addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
parser->sym_strings = str_table->offset;
|
||||
parser->n_symbols = n_symbols;
|
||||
|
||||
/* Allocate space for at least one symbol, so that parser->symbols will be
|
||||
* non-NULL. If it ends up being NULL, we will be parsing the file over and
|
||||
* over.
|
||||
*/
|
||||
parser->symbols = g_renew (ElfSym, parser->symbols, parser->n_symbols + 1);
|
||||
|
||||
qsort (parser->symbols, parser->n_symbols, sizeof (ElfSym), compare_sym);
|
||||
}
|
||||
|
||||
static void
|
||||
read_symbols (ElfParser *parser)
|
||||
{
|
||||
const Section *symtab = find_section (parser, ".symtab", SHT_SYMTAB);
|
||||
const Section *strtab = find_section (parser, ".strtab", SHT_STRTAB);
|
||||
const Section *dynsym = find_section (parser, ".dynsym", SHT_DYNSYM);
|
||||
const Section *dynstr = find_section (parser, ".dynstr", SHT_STRTAB);
|
||||
|
||||
if (symtab && strtab)
|
||||
{
|
||||
#if 0
|
||||
g_print ("reading symbol table of %s\n", parser->filename);
|
||||
#endif
|
||||
read_table (parser, symtab, strtab);
|
||||
}
|
||||
else if (dynsym && dynstr)
|
||||
{
|
||||
#if 0
|
||||
g_print ("reading dynamic symbol table of %s\n", parser->filename);
|
||||
#endif
|
||||
read_table (parser, dynsym, dynstr);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* To make sure parser->symbols is non-NULL */
|
||||
parser->n_symbols = 0;
|
||||
parser->symbols = g_new (ElfSym, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static ElfSym *
|
||||
do_lookup (ElfSym *symbols,
|
||||
gulong address,
|
||||
int first,
|
||||
int last)
|
||||
{
|
||||
if (address >= symbols[last].address)
|
||||
{
|
||||
return &(symbols[last]);
|
||||
}
|
||||
else if (last - first < 3)
|
||||
{
|
||||
while (last >= first)
|
||||
{
|
||||
if (address >= symbols[last].address)
|
||||
return &(symbols[last]);
|
||||
|
||||
last--;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
int mid = (first + last) / 2;
|
||||
|
||||
if (symbols[mid].address > address)
|
||||
return do_lookup (symbols, address, first, mid);
|
||||
else
|
||||
return do_lookup (symbols, address, mid, last);
|
||||
}
|
||||
}
|
||||
|
||||
/* Address should be given in 'offset into text segment' */
|
||||
const ElfSym *
|
||||
elf_parser_lookup_symbol (ElfParser *parser,
|
||||
gulong address)
|
||||
{
|
||||
const ElfSym *result;
|
||||
|
||||
if (!parser->symbols)
|
||||
{
|
||||
#if 0
|
||||
g_print ("reading symbols at %p\n", parser);
|
||||
#endif
|
||||
read_symbols (parser);
|
||||
}
|
||||
|
||||
if (parser->n_symbols == 0)
|
||||
return NULL;
|
||||
|
||||
if (!parser->text_section)
|
||||
return NULL;
|
||||
|
||||
address += parser->text_section->load_address;
|
||||
|
||||
#if 0
|
||||
g_print ("elf: the address we are looking up is %p\n", address);
|
||||
#endif
|
||||
|
||||
result = do_lookup (parser->symbols, address, 0, parser->n_symbols - 1);
|
||||
|
||||
#if 0
|
||||
if (result)
|
||||
{
|
||||
g_print (" elf: found %s at %lx\n", elf_parser_get_sym_name (parser, result), result->address);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_print ("elf: not found\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (result)
|
||||
{
|
||||
gulong size = st_size (parser, result->table, result->offset);
|
||||
|
||||
if (size > 0 && result->address + size <= address)
|
||||
{
|
||||
#if 0
|
||||
g_print (" elf: ends at %lx, so rejecting\n",
|
||||
result->address + size);
|
||||
#endif
|
||||
result = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (result)
|
||||
{
|
||||
/* Reject the symbols if the address is outside the text section */
|
||||
if (address > parser->text_section->load_address + parser->text_section->size)
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
gulong
|
||||
elf_parser_get_text_offset (ElfParser *parser)
|
||||
{
|
||||
g_return_val_if_fail (parser != NULL, (gulong)-1);
|
||||
|
||||
if (!parser->text_section)
|
||||
return (gulong)-1;
|
||||
|
||||
return parser->text_section->offset;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
make_hex_string (const guchar *data, int n_bytes)
|
||||
{
|
||||
static const char hex_digits[] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7',
|
||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
||||
};
|
||||
GString *string = g_string_new (NULL);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n_bytes; ++i)
|
||||
{
|
||||
char c = data[i];
|
||||
|
||||
g_string_append_c (string, hex_digits[(c & 0xf0) >> 4]);
|
||||
g_string_append_c (string, hex_digits[(c & 0x0f)]);
|
||||
}
|
||||
|
||||
return g_string_free (string, FALSE);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
elf_parser_get_build_id (ElfParser *parser)
|
||||
{
|
||||
if (!parser->checked_build_id)
|
||||
{
|
||||
const Section *build_id =
|
||||
find_section (parser, ".note.gnu.build-id", SHT_NOTE);
|
||||
guint64 name_size;
|
||||
guint64 desc_size;
|
||||
guint64 type;
|
||||
const char *name;
|
||||
guint64 offset;
|
||||
|
||||
parser->checked_build_id = TRUE;
|
||||
|
||||
if (!build_id)
|
||||
return NULL;
|
||||
|
||||
offset = build_id->offset;
|
||||
|
||||
name_size = GET_FIELD (parser, offset, Nhdr, 0, n_namesz);
|
||||
desc_size = GET_FIELD (parser, offset, Nhdr, 0, n_descsz);
|
||||
type = GET_FIELD (parser, offset, Nhdr, 0, n_type);
|
||||
|
||||
offset += GET_SIZE (parser, Nhdr);
|
||||
|
||||
name = (char *)(parser->data + offset);
|
||||
|
||||
if (strncmp (name, ELF_NOTE_GNU, name_size) != 0 || type != NT_GNU_BUILD_ID)
|
||||
return NULL;
|
||||
|
||||
offset += strlen (name);
|
||||
|
||||
offset = (offset + 3) & (~0x3);
|
||||
|
||||
parser->build_id = make_hex_string (parser->data + offset, desc_size);
|
||||
}
|
||||
|
||||
return parser->build_id;
|
||||
}
|
||||
|
||||
const char *
|
||||
elf_parser_get_debug_link (ElfParser *parser, guint32 *crc32)
|
||||
{
|
||||
guint64 offset;
|
||||
const Section *debug_link = find_section (parser, ".gnu_debuglink",
|
||||
SHT_PROGBITS);
|
||||
const gchar *result;
|
||||
|
||||
if (!debug_link)
|
||||
return NULL;
|
||||
|
||||
offset = debug_link->offset;
|
||||
|
||||
result = (char *)(parser->data + offset);
|
||||
|
||||
if (crc32)
|
||||
{
|
||||
int len = strlen (result) + 1;
|
||||
offset = (offset + len + 3) & ~0x3;
|
||||
|
||||
*crc32 = GET_UINT32 (parser, offset);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const guchar *
|
||||
get_section (ElfParser *parser,
|
||||
const char *name)
|
||||
{
|
||||
const Section *section = find_section (parser, name, SHT_PROGBITS);
|
||||
|
||||
if (section)
|
||||
return parser->data + section->offset;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const guchar *
|
||||
elf_parser_get_eh_frame (ElfParser *parser)
|
||||
{
|
||||
return get_section (parser, ".eh_frame");
|
||||
}
|
||||
|
||||
const guchar *
|
||||
elf_parser_get_debug_frame (ElfParser *parser)
|
||||
{
|
||||
return get_section (parser, ".debug_frame");
|
||||
}
|
||||
|
||||
const char *
|
||||
elf_parser_get_sym_name (ElfParser *parser,
|
||||
const ElfSym *sym)
|
||||
{
|
||||
g_return_val_if_fail (parser != NULL, NULL);
|
||||
|
||||
return (char *)(parser->data + parser->sym_strings +
|
||||
st_name (parser, sym->table, sym->offset));
|
||||
}
|
||||
|
||||
gboolean
|
||||
elf_parser_owns_symbol (ElfParser *parser,
|
||||
const ElfSym *sym)
|
||||
{
|
||||
ElfSym *first, *last;
|
||||
|
||||
if (!parser->n_symbols)
|
||||
return FALSE;
|
||||
|
||||
first = parser->symbols;
|
||||
last = parser->symbols + parser->n_symbols - 1;
|
||||
|
||||
return first <= sym && sym <= last;
|
||||
}
|
||||
|
||||
gulong
|
||||
elf_parser_get_sym_address (ElfParser *parser,
|
||||
const ElfSym *sym)
|
||||
{
|
||||
return sym->address - parser->text_section->load_address;
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions
|
||||
*/
|
||||
57
src/libsysprof/elfparser.h
Normal file
57
src/libsysprof/elfparser.h
Normal file
@ -0,0 +1,57 @@
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2006, 2007, Soeren Sandmann
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#include <glib.h>
|
||||
|
||||
typedef struct ElfSym ElfSym;
|
||||
typedef struct ElfParser ElfParser;
|
||||
|
||||
ElfParser * elf_parser_new_from_data (const guchar *data,
|
||||
gsize length);
|
||||
ElfParser * elf_parser_new (const char *filename,
|
||||
GError **err);
|
||||
void elf_parser_free (ElfParser *parser);
|
||||
const char * elf_parser_get_debug_link (ElfParser *parser,
|
||||
guint32 *crc32);
|
||||
const gchar *elf_parser_get_build_id (ElfParser *parser);
|
||||
const guchar *elf_parser_get_eh_frame (ElfParser *parser);
|
||||
const guchar *elf_parser_get_debug_frame (ElfParser *parser);
|
||||
gulong elf_parser_get_text_offset (ElfParser *parser);
|
||||
|
||||
|
||||
/* Lookup a symbol in the file.
|
||||
*
|
||||
* The symbol returned is const, so don't free it. It is valid until
|
||||
* elf_parser_free() is called on the parser.
|
||||
*
|
||||
* The address should be given in "file coordinates". This means that
|
||||
* if the file is mapped at address m and offset o, then an address a
|
||||
* should be looked up as "a - (m - o)". (m - o) is where the start
|
||||
* of the file would have been mapped, so a - (m - o) is the position
|
||||
* in the file of a.
|
||||
*/
|
||||
const ElfSym *elf_parser_lookup_symbol (ElfParser *parser,
|
||||
gulong address);
|
||||
guint32 elf_parser_get_crc32 (ElfParser *parser);
|
||||
const char * elf_parser_get_sym_name (ElfParser *parser,
|
||||
const ElfSym *sym);
|
||||
gulong elf_parser_get_sym_address (ElfParser *parser,
|
||||
const ElfSym *sym);
|
||||
gboolean elf_parser_owns_symbol (ElfParser *parser,
|
||||
const ElfSym *sym);
|
||||
char * elf_demangle (const char *name);
|
||||
|
||||
97
src/libsysprof/meson.build
Normal file
97
src/libsysprof/meson.build
Normal file
@ -0,0 +1,97 @@
|
||||
libsysprof_public_sources = [
|
||||
'sp-callgraph-profile.c',
|
||||
'sp-capture-gobject.c',
|
||||
'sp-elf-symbol-resolver.c',
|
||||
'sp-gjs-source.c',
|
||||
'sp-hostinfo-source.c',
|
||||
'sp-jitmap-symbol-resolver.c',
|
||||
'sp-kernel-symbol.c',
|
||||
'sp-kernel-symbol-resolver.c',
|
||||
'sp-local-profiler.c',
|
||||
'sp-map-lookaside.c',
|
||||
'sp-memory-source.c',
|
||||
'sp-perf-counter.c',
|
||||
'sp-perf-source.c',
|
||||
'sp-process-model.c',
|
||||
'sp-process-model-item.c',
|
||||
'sp-proc-source.c',
|
||||
'sp-profile.c',
|
||||
'sp-profiler.c',
|
||||
'sp-selection.c',
|
||||
'sp-source.c',
|
||||
'sp-symbol-dirs.c',
|
||||
'sp-symbol-resolver.c',
|
||||
]
|
||||
|
||||
libsysprof_public_headers = [
|
||||
'sp-callgraph-profile.h',
|
||||
'sp-callgraph-profile-private.h',
|
||||
'sp-capture-gobject.h',
|
||||
'sp-elf-symbol-resolver.h',
|
||||
'sp-gjs-source.h',
|
||||
'sp-hostinfo-source.h',
|
||||
'sp-jitmap-symbol-resolver.h',
|
||||
'sp-kernel-symbol.h',
|
||||
'sp-kernel-symbol-resolver.h',
|
||||
'sp-local-profiler.h',
|
||||
'sp-map-lookaside.h',
|
||||
'sp-memory-source.h',
|
||||
'sp-perf-counter.h',
|
||||
'sp-perf-source.h',
|
||||
'sp-process-model.h',
|
||||
'sp-process-model-item.h',
|
||||
'sp-proc-source.h',
|
||||
'sp-profile.h',
|
||||
'sp-profiler.h',
|
||||
'sp-selection.h',
|
||||
'sp-source.h',
|
||||
'sp-symbol-dirs.h',
|
||||
'sp-symbol-resolver.h',
|
||||
'sysprof.h',
|
||||
]
|
||||
|
||||
libsysprof_private_sources = [
|
||||
'binfile.c',
|
||||
'demangle.cpp',
|
||||
'elfparser.c',
|
||||
'stackstash.c',
|
||||
'sp-source-util.c',
|
||||
]
|
||||
|
||||
libsysprof_deps = [
|
||||
cxx.find_library('stdc++'),
|
||||
dependency('gio-2.0', version: glib_req_version),
|
||||
dependency('gio-unix-2.0', version: glib_req_version),
|
||||
libsysprof_capture_dep,
|
||||
libshared_dep,
|
||||
]
|
||||
|
||||
if get_option('with_sysprofd') != 'none'
|
||||
libsysprof_deps += dependency('polkit-gobject-1')
|
||||
endif
|
||||
|
||||
libsysprof = shared_library('sysprof-@0@'.format(libsysprof_api_version),
|
||||
libsysprof_public_sources + libsysprof_private_sources,
|
||||
dependencies: libsysprof_deps,
|
||||
c_args: [ '-DSYSPROF_COMPILATION' ],
|
||||
install: true,
|
||||
install_dir: get_option('libdir'),
|
||||
)
|
||||
|
||||
libsysprof_dep = declare_dependency(
|
||||
link_with: libsysprof,
|
||||
dependencies: libsysprof_deps,
|
||||
include_directories: include_directories('.'),
|
||||
)
|
||||
|
||||
pkgconfig.generate(
|
||||
subdirs: [ sysprof_header_subdir ],
|
||||
version: meson.project_version(),
|
||||
name: 'sysprof-@0@'.format(libsysprof_api_version),
|
||||
filebase: 'sysprof-@0@'.format(libsysprof_api_version),
|
||||
description: 'The library for console applications embedding sysprof',
|
||||
install_dir: join_paths(get_option('libdir'), 'pkgconfig'),
|
||||
requires: [ 'gio-2.0' ],
|
||||
)
|
||||
|
||||
install_headers(libsysprof_public_headers, subdir: sysprof_header_subdir)
|
||||
31
src/libsysprof/sp-callgraph-profile-private.h
Normal file
31
src/libsysprof/sp-callgraph-profile-private.h
Normal file
@ -0,0 +1,31 @@
|
||||
/* sp-callgraph-profile-private.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_CALLGRAPH_PROFILE_PRIVATE_H
|
||||
#define SP_CALLGRAPH_PROFILE_PRIVATE_H
|
||||
|
||||
#include "sp-callgraph-profile.h"
|
||||
#include "stackstash.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
StackStash *sp_callgraph_profile_get_stash (SpCallgraphProfile *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_CALLGRAPH_PROFILE_PRIVATE_H */
|
||||
517
src/libsysprof/sp-callgraph-profile.c
Normal file
517
src/libsysprof/sp-callgraph-profile.c
Normal file
@ -0,0 +1,517 @@
|
||||
/* sp-callgraph-profile.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* Sysprof -- Sampling, systemwide CPU profiler
|
||||
* Copyright 2009-2012 Soeren Sandmann and others
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <string.h>
|
||||
#include <sysprof-capture.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-callgraph-profile.h"
|
||||
#include "sp-callgraph-profile-private.h"
|
||||
#include "sp-capture-reader.h"
|
||||
#include "sp-elf-symbol-resolver.h"
|
||||
#include "sp-jitmap-symbol-resolver.h"
|
||||
#include "sp-map-lookaside.h"
|
||||
#include "sp-kernel-symbol-resolver.h"
|
||||
#include "sp-selection.h"
|
||||
#include "stackstash.h"
|
||||
|
||||
#define CHECK_CANCELLABLE_INTERVAL 100
|
||||
|
||||
struct _SpCallgraphProfile
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
SpCaptureReader *reader;
|
||||
SpSelection *selection;
|
||||
StackStash *stash;
|
||||
GStringChunk *symbols;
|
||||
GHashTable *tags;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureReader *reader;
|
||||
SpSelection *selection;
|
||||
} Generate;
|
||||
|
||||
static void profile_iface_init (SpProfileInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpCallgraphProfile, sp_callgraph_profile, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_PROFILE, profile_iface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_SELECTION,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
SpProfile *
|
||||
sp_callgraph_profile_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_CALLGRAPH_PROFILE, NULL);
|
||||
}
|
||||
|
||||
SpProfile *
|
||||
sp_callgraph_profile_new_with_selection (SpSelection *selection)
|
||||
{
|
||||
return g_object_new (SP_TYPE_CALLGRAPH_PROFILE,
|
||||
"selection", selection,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_finalize (GObject *object)
|
||||
{
|
||||
SpCallgraphProfile *self = (SpCallgraphProfile *)object;
|
||||
|
||||
g_clear_pointer (&self->symbols, g_string_chunk_free);
|
||||
g_clear_pointer (&self->stash, stack_stash_unref);
|
||||
g_clear_pointer (&self->reader, sp_capture_reader_unref);
|
||||
g_clear_pointer (&self->tags, g_hash_table_unref);
|
||||
g_clear_object (&self->selection);
|
||||
|
||||
G_OBJECT_CLASS (sp_callgraph_profile_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpCallgraphProfile *self = SP_CALLGRAPH_PROFILE (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_SELECTION:
|
||||
g_value_set_object (value, self->selection);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpCallgraphProfile *self = SP_CALLGRAPH_PROFILE (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_SELECTION:
|
||||
self->selection = g_value_dup_object (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_class_init (SpCallgraphProfileClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_callgraph_profile_finalize;
|
||||
object_class->get_property = sp_callgraph_profile_get_property;
|
||||
object_class->set_property = sp_callgraph_profile_set_property;
|
||||
|
||||
properties [PROP_SELECTION] =
|
||||
g_param_spec_object ("selection",
|
||||
"Selection",
|
||||
"The selection for filtering the callgraph",
|
||||
SP_TYPE_SELECTION,
|
||||
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_init (SpCallgraphProfile *self)
|
||||
{
|
||||
self->symbols = g_string_chunk_new (getpagesize ());
|
||||
self->tags = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_set_reader (SpProfile *profile,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
|
||||
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (reader != NULL);
|
||||
|
||||
g_clear_pointer (&self->reader, sp_capture_reader_unref);
|
||||
self->reader = sp_capture_reader_ref (reader);
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
sp_callgraph_profile_intern_string_take (SpCallgraphProfile *self,
|
||||
gchar *str)
|
||||
{
|
||||
const gchar *ret;
|
||||
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (str != NULL);
|
||||
|
||||
ret = g_string_chunk_insert_const (self->symbols, str);
|
||||
g_free (str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
sp_callgraph_profile_intern_string (SpCallgraphProfile *self,
|
||||
const gchar *str)
|
||||
{
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (str != NULL);
|
||||
|
||||
return g_string_chunk_insert_const (self->symbols, str);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_generate_worker (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
SpCallgraphProfile *self = source_object;
|
||||
Generate *gen = task_data;
|
||||
SpCaptureReader *reader;
|
||||
SpSelection *selection;
|
||||
g_autoptr(GArray) resolved = NULL;
|
||||
g_autoptr(GHashTable) maps_by_pid = NULL;
|
||||
g_autoptr(GHashTable) cmdlines = NULL;
|
||||
g_autoptr(GPtrArray) resolvers = NULL;
|
||||
SpCaptureFrameType type;
|
||||
StackStash *stash = NULL;
|
||||
StackStash *resolved_stash = NULL;
|
||||
guint count = 0;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
g_assert (gen != NULL);
|
||||
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
reader = gen->reader;
|
||||
selection = gen->selection;
|
||||
|
||||
maps_by_pid = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)sp_map_lookaside_free);
|
||||
cmdlines = g_hash_table_new (NULL, NULL);
|
||||
|
||||
stash = stack_stash_new (NULL);
|
||||
resolved_stash = stack_stash_new (NULL);
|
||||
|
||||
resolvers = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
g_ptr_array_add (resolvers, sp_kernel_symbol_resolver_new ());
|
||||
g_ptr_array_add (resolvers, sp_elf_symbol_resolver_new ());
|
||||
g_ptr_array_add (resolvers, sp_jitmap_symbol_resolver_new ());
|
||||
|
||||
for (guint j = 0; j < resolvers->len; j++)
|
||||
{
|
||||
SpSymbolResolver *resolver = g_ptr_array_index (resolvers, j);
|
||||
|
||||
sp_capture_reader_reset (reader);
|
||||
sp_symbol_resolver_load (resolver, reader);
|
||||
}
|
||||
|
||||
sp_capture_reader_reset (reader);
|
||||
|
||||
/*
|
||||
* The resolved pointer array is where we stash the names for the
|
||||
* instruction pointers to pass to the stash stack. All the strings
|
||||
* need to be deduplicated so that pointer comparison works as if we
|
||||
* did instruction-pointer comparison.
|
||||
*/
|
||||
resolved = g_array_new (FALSE, TRUE, sizeof (guint64));
|
||||
|
||||
while (sp_capture_reader_peek_type (reader, &type))
|
||||
{
|
||||
const SpCaptureProcess *pr;
|
||||
const gchar *cmdline;
|
||||
|
||||
if (type != SP_CAPTURE_FRAME_PROCESS)
|
||||
{
|
||||
if (!sp_capture_reader_skip (reader))
|
||||
goto failure;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (NULL == (pr = sp_capture_reader_read_process (reader)))
|
||||
goto failure;
|
||||
|
||||
cmdline = g_strdup_printf ("[%s]", pr->cmdline);
|
||||
g_hash_table_insert (cmdlines,
|
||||
GINT_TO_POINTER (pr->frame.pid),
|
||||
(gchar *)sp_callgraph_profile_intern_string (self, cmdline));
|
||||
}
|
||||
|
||||
if (g_task_return_error_if_cancelled (task))
|
||||
goto cleanup;
|
||||
|
||||
sp_capture_reader_reset (reader);
|
||||
|
||||
/*
|
||||
* Walk through all of the sample events and resolve instruction-pointers
|
||||
* to symbol names by loading the particular map and extracting the symbol
|
||||
* name. If we wanted to support dynamic systems, we'd want to extend this
|
||||
* to parse information from captured data about the languages jit'd code.
|
||||
*/
|
||||
while (sp_capture_reader_peek_type (reader, &type))
|
||||
{
|
||||
SpAddressContext last_context = SP_ADDRESS_CONTEXT_NONE;
|
||||
const SpCaptureSample *sample;
|
||||
StackNode *node;
|
||||
StackNode *iter;
|
||||
const gchar *cmdline;
|
||||
guint len = 5;
|
||||
|
||||
if (type != SP_CAPTURE_FRAME_SAMPLE)
|
||||
{
|
||||
if (!sp_capture_reader_skip (reader))
|
||||
goto failure;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++count == CHECK_CANCELLABLE_INTERVAL)
|
||||
{
|
||||
if (g_task_return_error_if_cancelled (task))
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (NULL == (sample = sp_capture_reader_read_sample (reader)))
|
||||
goto failure;
|
||||
|
||||
if (!sp_selection_contains (selection, sample->frame.time))
|
||||
continue;
|
||||
|
||||
if (sample->n_addrs == 0)
|
||||
continue;
|
||||
|
||||
cmdline = g_hash_table_lookup (cmdlines, GINT_TO_POINTER (sample->frame.pid));
|
||||
|
||||
#if 0
|
||||
/* This assertion appears to hold true, but since we're taking in
|
||||
* untrusted data from capture files, it's not safe to assume. But in
|
||||
* practice it is.
|
||||
*/
|
||||
g_assert (sp_address_is_context_switch (sample->addrs[0], &last_context));
|
||||
last_context = SP_ADDRESS_CONTEXT_NONE;
|
||||
#endif
|
||||
|
||||
node = stack_stash_add_trace (stash, sample->addrs, sample->n_addrs, 1);
|
||||
|
||||
for (iter = node; iter != NULL; iter = iter->parent)
|
||||
len++;
|
||||
|
||||
if (G_UNLIKELY (resolved->len < len))
|
||||
g_array_set_size (resolved, len);
|
||||
|
||||
len = 0;
|
||||
|
||||
for (iter = node; iter != NULL; iter = iter->parent)
|
||||
{
|
||||
SpAddressContext context = SP_ADDRESS_CONTEXT_NONE;
|
||||
SpAddress address = iter->data;
|
||||
const gchar *symbol = NULL;
|
||||
|
||||
if (sp_address_is_context_switch (address, &context))
|
||||
{
|
||||
if (last_context)
|
||||
symbol = sp_address_context_to_string (last_context);
|
||||
else
|
||||
symbol = NULL;
|
||||
|
||||
last_context = context;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (guint j = 0; j < resolvers->len; j++)
|
||||
{
|
||||
SpSymbolResolver *resolver = g_ptr_array_index (resolvers, j);
|
||||
GQuark tag = 0;
|
||||
gchar *str;
|
||||
|
||||
str = sp_symbol_resolver_resolve_with_context (resolver,
|
||||
sample->frame.time,
|
||||
sample->frame.pid,
|
||||
last_context,
|
||||
address,
|
||||
&tag);
|
||||
|
||||
if (str != NULL)
|
||||
{
|
||||
symbol = sp_callgraph_profile_intern_string_take (self, str);
|
||||
if (tag != 0)
|
||||
g_hash_table_insert (self->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (symbol != NULL)
|
||||
g_array_index (resolved, SpAddress, len++) = POINTER_TO_U64 (symbol);
|
||||
}
|
||||
|
||||
if (last_context && last_context != SP_ADDRESS_CONTEXT_USER)
|
||||
{
|
||||
/* Kernel threads do not have a user part, so we end up here
|
||||
* without ever getting a user context. If this happens,
|
||||
* add the '- - kernel - - ' name, so that kernel threads
|
||||
* are properly blamed on the kernel
|
||||
*/
|
||||
const gchar *name = sp_address_context_to_string (last_context);
|
||||
g_array_index (resolved, SpAddress, len++) = POINTER_TO_U64 (name);
|
||||
}
|
||||
|
||||
if (cmdline != NULL)
|
||||
g_array_index (resolved, guint64, len++) = POINTER_TO_U64 (cmdline);
|
||||
|
||||
g_array_index (resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]");
|
||||
|
||||
stack_stash_add_trace (resolved_stash, (SpAddress *)(gpointer)resolved->data, len, 1);
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
|
||||
failure:
|
||||
|
||||
if (ret == FALSE)
|
||||
g_task_return_new_error (task,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"%s",
|
||||
_("Sysprof was unable to generate a callgraph from the system capture."));
|
||||
else
|
||||
g_task_return_pointer (task, g_steal_pointer (&resolved_stash), (GDestroyNotify)stack_stash_unref);
|
||||
|
||||
cleanup:
|
||||
g_clear_pointer (&resolved_stash, stack_stash_unref);
|
||||
g_clear_pointer (&stash, stack_stash_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
generate_free (Generate *generate)
|
||||
{
|
||||
sp_capture_reader_unref (generate->reader);
|
||||
g_clear_object (&generate->selection);
|
||||
g_slice_free (Generate, generate);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_callgraph_profile_generate (SpProfile *profile,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
|
||||
Generate *gen;
|
||||
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
gen = g_slice_new0 (Generate);
|
||||
gen->reader = sp_capture_reader_copy (self->reader);
|
||||
gen->selection = sp_selection_copy (self->selection);
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
g_task_set_task_data (task, gen, (GDestroyNotify)generate_free);
|
||||
g_task_run_in_thread (task, sp_callgraph_profile_generate_worker);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_callgraph_profile_generate_finish (SpProfile *profile,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
|
||||
StackStash *stash;
|
||||
|
||||
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
|
||||
g_assert (G_IS_TASK (result));
|
||||
|
||||
stash = g_task_propagate_pointer (G_TASK (result), error);
|
||||
|
||||
if (stash != NULL)
|
||||
{
|
||||
if (stash != self->stash)
|
||||
{
|
||||
g_clear_pointer (&self->stash, stack_stash_unref);
|
||||
self->stash = g_steal_pointer (&stash);
|
||||
}
|
||||
|
||||
g_clear_pointer (&stash, stack_stash_unref);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
profile_iface_init (SpProfileInterface *iface)
|
||||
{
|
||||
iface->generate = sp_callgraph_profile_generate;
|
||||
iface->generate_finish = sp_callgraph_profile_generate_finish;
|
||||
iface->set_reader = sp_callgraph_profile_set_reader;
|
||||
}
|
||||
|
||||
StackStash *
|
||||
sp_callgraph_profile_get_stash (SpCallgraphProfile *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_CALLGRAPH_PROFILE (self), NULL);
|
||||
|
||||
return self->stash;
|
||||
}
|
||||
|
||||
GQuark
|
||||
sp_callgraph_profile_get_tag (SpCallgraphProfile *self,
|
||||
const gchar *symbol)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_CALLGRAPH_PROFILE (self), 0);
|
||||
|
||||
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tags, symbol));
|
||||
}
|
||||
38
src/libsysprof/sp-callgraph-profile.h
Normal file
38
src/libsysprof/sp-callgraph-profile.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* sp-callgraph-profile.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_CALLGRAPH_PROFILE_H
|
||||
#define SP_CALLGRAPH_PROFILE_H
|
||||
|
||||
#include "sp-profile.h"
|
||||
#include "sp-selection.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_CALLGRAPH_PROFILE (sp_callgraph_profile_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpCallgraphProfile, sp_callgraph_profile, SP, CALLGRAPH_PROFILE, GObject)
|
||||
|
||||
SpProfile *sp_callgraph_profile_new (void);
|
||||
SpProfile *sp_callgraph_profile_new_with_selection (SpSelection *selection);
|
||||
GQuark sp_callgraph_profile_get_tag (SpCallgraphProfile *self,
|
||||
const gchar *symbol);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_CALLGRAPH_PROFILE_H */
|
||||
25
src/libsysprof/sp-capture-gobject.c
Normal file
25
src/libsysprof/sp-capture-gobject.c
Normal file
@ -0,0 +1,25 @@
|
||||
/* sp-capture-gobject.c
|
||||
*
|
||||
* Copyright 2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "sp-capture-gobject.h"
|
||||
|
||||
G_DEFINE_BOXED_TYPE (SpCaptureReader, sp_capture_reader, (GBoxedCopyFunc)sp_capture_reader_ref, (GBoxedFreeFunc)sp_capture_reader_unref)
|
||||
G_DEFINE_BOXED_TYPE (SpCaptureWriter, sp_capture_writer, (GBoxedCopyFunc)sp_capture_writer_ref, (GBoxedFreeFunc)sp_capture_writer_unref)
|
||||
G_DEFINE_BOXED_TYPE (SpCaptureCursor, sp_capture_cursor, (GBoxedCopyFunc)sp_capture_cursor_ref, (GBoxedFreeFunc)sp_capture_cursor_unref)
|
||||
36
src/libsysprof/sp-capture-gobject.h
Normal file
36
src/libsysprof/sp-capture-gobject.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-capture-gobject.h
|
||||
*
|
||||
* Copyright 2019 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <sysprof-capture.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_CAPTURE_READER (sp_capture_reader_get_type())
|
||||
#define SP_TYPE_CAPTURE_WRITER (sp_capture_writer_get_type())
|
||||
#define SP_TYPE_CAPTURE_CURSOR (sp_capture_cursor_get_type())
|
||||
|
||||
GType sp_capture_reader_get_type (void);
|
||||
GType sp_capture_writer_get_type (void);
|
||||
GType sp_capture_cursor_get_type (void);
|
||||
|
||||
G_END_DECLS
|
||||
310
src/libsysprof/sp-elf-symbol-resolver.c
Normal file
310
src/libsysprof/sp-elf-symbol-resolver.c
Normal file
@ -0,0 +1,310 @@
|
||||
/* sp-elf-symbol-resolver.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-elf-symbol-resolver.h"
|
||||
#include "binfile.h"
|
||||
#include "elfparser.h"
|
||||
#include "sp-map-lookaside.h"
|
||||
|
||||
struct _SpElfSymbolResolver
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
GHashTable *lookasides;
|
||||
GHashTable *bin_files;
|
||||
GHashTable *tag_cache;
|
||||
};
|
||||
|
||||
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpElfSymbolResolver,
|
||||
sp_elf_symbol_resolver,
|
||||
G_TYPE_OBJECT,
|
||||
0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
|
||||
symbol_resolver_iface_init))
|
||||
|
||||
static void
|
||||
sp_elf_symbol_resolver_finalize (GObject *object)
|
||||
{
|
||||
SpElfSymbolResolver *self = (SpElfSymbolResolver *)object;
|
||||
|
||||
g_clear_pointer (&self->bin_files, g_hash_table_unref);
|
||||
g_clear_pointer (&self->lookasides, g_hash_table_unref);
|
||||
g_clear_pointer (&self->tag_cache, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_elf_symbol_resolver_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_elf_symbol_resolver_class_init (SpElfSymbolResolverClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_elf_symbol_resolver_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_elf_symbol_resolver_init (SpElfSymbolResolver *self)
|
||||
{
|
||||
self->lookasides = g_hash_table_new_full (NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
(GDestroyNotify)sp_map_lookaside_free);
|
||||
|
||||
self->bin_files = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
(GDestroyNotify)bin_file_free);
|
||||
|
||||
self->tag_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_elf_symbol_resolver_load (SpSymbolResolver *resolver,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpElfSymbolResolver *self = (SpElfSymbolResolver *)resolver;
|
||||
SpCaptureFrameType type;
|
||||
|
||||
g_assert (SP_IS_SYMBOL_RESOLVER (resolver));
|
||||
g_assert (reader != NULL);
|
||||
|
||||
sp_capture_reader_reset (reader);
|
||||
|
||||
while (sp_capture_reader_peek_type (reader, &type))
|
||||
{
|
||||
const SpCaptureMap *ev;
|
||||
SpMapLookaside *lookaside;
|
||||
SpMap map;
|
||||
|
||||
if (type != SP_CAPTURE_FRAME_MAP)
|
||||
{
|
||||
if (!sp_capture_reader_skip (reader))
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
ev = sp_capture_reader_read_map (reader);
|
||||
|
||||
map.start = ev->start;
|
||||
map.end = ev->end;
|
||||
map.offset = ev->offset;
|
||||
map.inode = ev->inode;
|
||||
map.filename = ev->filename;
|
||||
|
||||
lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (ev->frame.pid));
|
||||
|
||||
if (lookaside == NULL)
|
||||
{
|
||||
lookaside = sp_map_lookaside_new ();
|
||||
g_hash_table_insert (self->lookasides, GINT_TO_POINTER (ev->frame.pid), lookaside);
|
||||
}
|
||||
|
||||
sp_map_lookaside_insert (lookaside, &map);
|
||||
}
|
||||
}
|
||||
|
||||
static bin_file_t *
|
||||
sp_elf_symbol_resolver_get_bin_file (SpElfSymbolResolver *self,
|
||||
const gchar *filename)
|
||||
{
|
||||
bin_file_t *bin_file;
|
||||
|
||||
g_assert (SP_IS_ELF_SYMBOL_RESOLVER (self));
|
||||
|
||||
bin_file = g_hash_table_lookup (self->bin_files, filename);
|
||||
|
||||
if (bin_file == NULL)
|
||||
{
|
||||
const gchar *alternate = filename;
|
||||
|
||||
/*
|
||||
* If we are in a new mount namespace, then rely on the sp_symbol_dirs
|
||||
* to find us a locate to resolve the file where the CRC will match.
|
||||
*
|
||||
* TODO: We need to translate the path here so that we can locate the
|
||||
* binary behind it (which then has links to the debug file in
|
||||
* the section header).
|
||||
*/
|
||||
if (g_str_has_prefix (filename, "/newroot/"))
|
||||
alternate += strlen ("/newroot");
|
||||
|
||||
bin_file = bin_file_new (alternate);
|
||||
g_hash_table_insert (self->bin_files, g_strdup (filename), bin_file);
|
||||
}
|
||||
|
||||
return bin_file;
|
||||
}
|
||||
|
||||
static GQuark
|
||||
guess_tag (SpElfSymbolResolver *self,
|
||||
const SpMap *map)
|
||||
{
|
||||
g_assert (map != NULL);
|
||||
g_assert (map->filename != NULL);
|
||||
|
||||
if (!g_hash_table_contains (self->tag_cache, map->filename))
|
||||
{
|
||||
GQuark tag = 0;
|
||||
|
||||
if (strstr (map->filename, "/libgobject-2.0."))
|
||||
tag = g_quark_from_static_string ("GObject");
|
||||
|
||||
else if (strstr (map->filename, "/libglib-2.0."))
|
||||
tag = g_quark_from_static_string ("GLib");
|
||||
|
||||
else if (strstr (map->filename, "/libgio-2.0."))
|
||||
tag = g_quark_from_static_string ("Gio");
|
||||
|
||||
else if (strstr (map->filename, "/libgirepository-1.0."))
|
||||
tag = g_quark_from_static_string ("Introspection");
|
||||
|
||||
else if (strstr (map->filename, "/libgtk-3."))
|
||||
tag = g_quark_from_static_string ("Gtk+");
|
||||
|
||||
else if (strstr (map->filename, "/libgdk-3."))
|
||||
tag = g_quark_from_static_string ("Gdk");
|
||||
|
||||
else if (strstr (map->filename, "/libgtksourceview-3.0"))
|
||||
tag = g_quark_from_static_string ("GtkSourceView");
|
||||
|
||||
else if (strstr (map->filename, "/libpixman-1"))
|
||||
tag = g_quark_from_static_string ("Pixman");
|
||||
|
||||
else if (strstr (map->filename, "/libcairo."))
|
||||
tag = g_quark_from_static_string ("cairo");
|
||||
|
||||
else if (strstr (map->filename, "/libgstreamer-1."))
|
||||
tag = g_quark_from_static_string ("GStreamer");
|
||||
|
||||
else if (strstr (map->filename, "/libX11."))
|
||||
tag = g_quark_from_static_string ("X11");
|
||||
|
||||
else if (strstr (map->filename, "/libpango-1.0."))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libpangocairo-1.0."))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libpangomm-1.4."))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libpangoft2-1.0"))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libpangoxft-1.0."))
|
||||
tag = g_quark_from_static_string ("Pango");
|
||||
|
||||
else if (strstr (map->filename, "/libclutter-"))
|
||||
tag = g_quark_from_static_string ("Clutter");
|
||||
|
||||
else if (strstr (map->filename, "/libcogl.") ||
|
||||
strstr (map->filename, "/libcogl-"))
|
||||
tag = g_quark_from_static_string ("Cogl");
|
||||
|
||||
else if (strstr (map->filename, "/libffi."))
|
||||
tag = g_quark_from_static_string ("libffi");
|
||||
|
||||
else if (strstr (map->filename, "/libwayland-"))
|
||||
tag = g_quark_from_static_string ("Wayland");
|
||||
|
||||
else if (strstr (map->filename, "/libinput."))
|
||||
tag = g_quark_from_static_string ("libinput");
|
||||
|
||||
else if (strstr (map->filename, "/libgjs."))
|
||||
tag = g_quark_from_static_string ("Gjs");
|
||||
|
||||
else if (strstr (map->filename, "/libmozjs-"))
|
||||
tag = g_quark_from_static_string ("MozJS");
|
||||
|
||||
else if (strstr (map->filename, "/libGL."))
|
||||
tag = g_quark_from_static_string ("GL");
|
||||
|
||||
else if (strstr (map->filename, "/libEGL."))
|
||||
tag = g_quark_from_static_string ("EGL");
|
||||
|
||||
g_hash_table_insert (self->tag_cache,
|
||||
g_strdup (map->filename),
|
||||
GSIZE_TO_POINTER (tag));
|
||||
}
|
||||
|
||||
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tag_cache, map->filename));
|
||||
}
|
||||
|
||||
static gchar *
|
||||
sp_elf_symbol_resolver_resolve_with_context (SpSymbolResolver *resolver,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpAddressContext context,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
SpElfSymbolResolver *self = (SpElfSymbolResolver *)resolver;
|
||||
const bin_symbol_t *bin_sym;
|
||||
SpMapLookaside *lookaside;
|
||||
const gchar *bin_sym_name;
|
||||
const SpMap *map;
|
||||
bin_file_t *bin_file;
|
||||
|
||||
g_assert (SP_IS_ELF_SYMBOL_RESOLVER (self));
|
||||
|
||||
if (context != SP_ADDRESS_CONTEXT_USER)
|
||||
return NULL;
|
||||
|
||||
lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (pid));
|
||||
if (lookaside == NULL)
|
||||
return NULL;
|
||||
|
||||
map = sp_map_lookaside_lookup (lookaside, address);
|
||||
if (map == NULL)
|
||||
return NULL;
|
||||
|
||||
address -= map->start;
|
||||
address += map->offset;
|
||||
|
||||
bin_file = sp_elf_symbol_resolver_get_bin_file (self, map->filename);
|
||||
|
||||
g_assert (bin_file != NULL);
|
||||
|
||||
if (map->inode && !bin_file_check_inode (bin_file, map->inode))
|
||||
return g_strdup_printf ("%s: inode mismatch", map->filename);
|
||||
|
||||
bin_sym = bin_file_lookup_symbol (bin_file, address);
|
||||
bin_sym_name = bin_symbol_get_name (bin_file, bin_sym);
|
||||
|
||||
if (map->filename)
|
||||
*tag = guess_tag (self, map);
|
||||
|
||||
return elf_demangle (bin_sym_name);
|
||||
}
|
||||
|
||||
static void
|
||||
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
|
||||
{
|
||||
iface->load = sp_elf_symbol_resolver_load;
|
||||
iface->resolve_with_context = sp_elf_symbol_resolver_resolve_with_context;
|
||||
}
|
||||
|
||||
SpSymbolResolver *
|
||||
sp_elf_symbol_resolver_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_ELF_SYMBOL_RESOLVER, NULL);
|
||||
}
|
||||
34
src/libsysprof/sp-elf-symbol-resolver.h
Normal file
34
src/libsysprof/sp-elf-symbol-resolver.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-elf-symbol-resolver.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_ELF_SYMBOL_RESOLVER_H
|
||||
#define SP_ELF_SYMBOL_RESOLVER_H
|
||||
|
||||
#include "sp-symbol-resolver.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_ELF_SYMBOL_RESOLVER (sp_elf_symbol_resolver_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpElfSymbolResolver, sp_elf_symbol_resolver, SP, ELF_SYMBOL_RESOLVER, GObject)
|
||||
|
||||
SpSymbolResolver *sp_elf_symbol_resolver_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_ELF_SYMBOL_RESOLVER_H */
|
||||
245
src/libsysprof/sp-gjs-source.c
Normal file
245
src/libsysprof/sp-gjs-source.c
Normal file
@ -0,0 +1,245 @@
|
||||
/* sp-gjs-source.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-capture-reader.h"
|
||||
#include "sp-gjs-source.h"
|
||||
|
||||
struct _SpGjsSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
SpCaptureWriter *writer;
|
||||
GArray *pids;
|
||||
GArray *enabled;
|
||||
};
|
||||
|
||||
#define ENABLE_PROFILER 0x1
|
||||
#define DISABLE_PROFILER 0x0
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpGjsSource, sp_gjs_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
static void
|
||||
sp_gjs_source_finalize (GObject *object)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)object;
|
||||
|
||||
g_clear_pointer (&self->pids, g_array_unref);
|
||||
g_clear_pointer (&self->enabled, g_array_unref);
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_gjs_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_class_init (SpGjsSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_gjs_source_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_init (SpGjsSource *self)
|
||||
{
|
||||
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
self->enabled = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_process_capture (SpGjsSource *self,
|
||||
GPid pid,
|
||||
const gchar *path)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(SpCaptureReader) reader = NULL;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
g_assert (path != NULL);
|
||||
|
||||
if (!(reader = sp_capture_reader_new (path, &error)))
|
||||
{
|
||||
g_warning ("Failed to load capture: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sp_capture_reader_splice (reader, self->writer, &error))
|
||||
{
|
||||
g_warning ("Failed to load capture: %s", error->message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_process_captures (SpGjsSource *self)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
for (i = 0; i < self->enabled->len; i++)
|
||||
{
|
||||
g_autofree gchar *filename = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
GPid pid = g_array_index (self->enabled, GPid, i);
|
||||
|
||||
filename = g_strdup_printf ("gjs-profile-%u", (guint)pid);
|
||||
path = g_build_filename (g_get_tmp_dir (), filename, NULL);
|
||||
|
||||
sp_gjs_source_process_capture (self, pid, path);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pid_is_profileable (GPid pid)
|
||||
{
|
||||
g_autofree gchar *path = NULL;
|
||||
g_autofree gchar *contents = NULL;
|
||||
const gchar *libgjs;
|
||||
gsize len = 0;
|
||||
|
||||
g_assert (pid != -1);
|
||||
|
||||
/*
|
||||
* Make sure this process has linked in libgjs. No sense in sending it a
|
||||
* signal unless we know it can handle it.
|
||||
*/
|
||||
|
||||
path = g_strdup_printf ("/proc/%d/maps", pid);
|
||||
if (!g_file_get_contents (path, &contents, &len, NULL))
|
||||
return FALSE;
|
||||
|
||||
if (NULL == (libgjs = strstr (contents, "libgjs."G_MODULE_SUFFIX)))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_enable_pid (SpGjsSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
union sigval si;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid != -1);
|
||||
|
||||
si.sival_int = ENABLE_PROFILER;
|
||||
|
||||
if (0 != sigqueue (pid, SIGUSR2, si))
|
||||
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
|
||||
else
|
||||
g_array_append_val (self->enabled, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_disable_pid (SpGjsSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
union sigval si;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid != -1);
|
||||
|
||||
si.sival_int = DISABLE_PROFILER;
|
||||
|
||||
if (0 != sigqueue (pid, SIGUSR2, si))
|
||||
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_start (SpSource *source)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid pid = g_array_index (self->pids, GPid, i);
|
||||
|
||||
if (pid_is_profileable (pid))
|
||||
sp_gjs_source_enable_pid (self, pid);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_stop (SpSource *source)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid pid = g_array_index (self->pids, GPid, i);
|
||||
|
||||
if (pid_is_profileable (pid))
|
||||
sp_gjs_source_disable_pid (self, pid);
|
||||
}
|
||||
|
||||
sp_gjs_source_process_captures (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_gjs_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpGjsSource *self = (SpGjsSource *)source;
|
||||
|
||||
g_assert (SP_IS_GJS_SOURCE (self));
|
||||
g_assert (pid > -1);
|
||||
|
||||
g_array_append_val (self->pids, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_gjs_source_set_writer;
|
||||
iface->start = sp_gjs_source_start;
|
||||
iface->stop = sp_gjs_source_stop;
|
||||
iface->add_pid = sp_gjs_source_add_pid;
|
||||
}
|
||||
|
||||
SpSource *
|
||||
sp_gjs_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_GJS_SOURCE, NULL);
|
||||
}
|
||||
34
src/libsysprof/sp-gjs-source.h
Normal file
34
src/libsysprof/sp-gjs-source.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-gjs-source.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_GJS_SOURCE_H
|
||||
#define SP_GJS_SOURCE_H
|
||||
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_GJS_SOURCE (sp_gjs_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpGjsSource, sp_gjs_source, SP, GJS_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_gjs_source_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_GJS_SOURCE_H */
|
||||
384
src/libsysprof/sp-hostinfo-source.c
Normal file
384
src/libsysprof/sp-hostinfo-source.c
Normal file
@ -0,0 +1,384 @@
|
||||
/* sp-hostinfo-source.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-hostinfo-source.h"
|
||||
|
||||
#define PROC_STAT_BUF_SIZE 4096
|
||||
|
||||
struct _SpHostinfoSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
guint handler;
|
||||
gint n_cpu;
|
||||
gint stat_fd;
|
||||
|
||||
SpCaptureWriter *writer;
|
||||
GArray *cpu_info;
|
||||
gchar *stat_buf;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gint counter_base;
|
||||
gdouble total;
|
||||
gdouble freq;
|
||||
glong last_user;
|
||||
glong last_idle;
|
||||
glong last_system;
|
||||
glong last_nice;
|
||||
glong last_iowait;
|
||||
glong last_irq;
|
||||
glong last_softirq;
|
||||
glong last_steal;
|
||||
glong last_guest;
|
||||
glong last_guest_nice;
|
||||
} CpuInfo;
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpHostinfoSource, sp_hostinfo_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
SpSource *
|
||||
sp_hostinfo_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_HOSTINFO_SOURCE, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
read_stat (SpHostinfoSource *self)
|
||||
{
|
||||
gssize len;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->stat_fd != -1);
|
||||
g_assert (self->stat_buf != NULL);
|
||||
|
||||
if (lseek (self->stat_fd, 0, SEEK_SET) != 0)
|
||||
return FALSE;
|
||||
|
||||
len = read (self->stat_fd, self->stat_buf, PROC_STAT_BUF_SIZE);
|
||||
if (len <= 0)
|
||||
return FALSE;
|
||||
|
||||
if (len < PROC_STAT_BUF_SIZE)
|
||||
self->stat_buf[len] = 0;
|
||||
else
|
||||
self->stat_buf[PROC_STAT_BUF_SIZE-1] = 0;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
poll_cpu (SpHostinfoSource *self)
|
||||
{
|
||||
gchar cpu[64] = { 0 };
|
||||
glong user;
|
||||
glong sys;
|
||||
glong nice;
|
||||
glong idle;
|
||||
glong iowait;
|
||||
glong irq;
|
||||
glong softirq;
|
||||
glong steal;
|
||||
glong guest;
|
||||
glong guest_nice;
|
||||
glong user_calc;
|
||||
glong system_calc;
|
||||
glong nice_calc;
|
||||
glong idle_calc;
|
||||
glong iowait_calc;
|
||||
glong irq_calc;
|
||||
glong softirq_calc;
|
||||
glong steal_calc;
|
||||
glong guest_calc;
|
||||
glong guest_nice_calc;
|
||||
glong total;
|
||||
gchar *line;
|
||||
gint ret;
|
||||
gint id;
|
||||
|
||||
if (read_stat (self))
|
||||
{
|
||||
line = self->stat_buf;
|
||||
|
||||
for (gsize i = 0; self->stat_buf[i]; i++)
|
||||
{
|
||||
if (self->stat_buf[i] == '\n')
|
||||
{
|
||||
self->stat_buf[i] = '\0';
|
||||
|
||||
if (strncmp (line, "cpu", 3) == 0)
|
||||
{
|
||||
if (isdigit (line[3]))
|
||||
{
|
||||
CpuInfo *cpu_info;
|
||||
|
||||
user = nice = sys = idle = id = 0;
|
||||
ret = sscanf (line, "%s %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
|
||||
cpu, &user, &nice, &sys, &idle,
|
||||
&iowait, &irq, &softirq, &steal, &guest, &guest_nice);
|
||||
if (ret != 11)
|
||||
goto next;
|
||||
|
||||
ret = sscanf(cpu, "cpu%d", &id);
|
||||
|
||||
if (ret != 1 || id < 0 || id >= self->n_cpu)
|
||||
goto next;
|
||||
|
||||
cpu_info = &g_array_index (self->cpu_info, CpuInfo, id);
|
||||
|
||||
user_calc = user - cpu_info->last_user;
|
||||
nice_calc = nice - cpu_info->last_nice;
|
||||
system_calc = sys - cpu_info->last_system;
|
||||
idle_calc = idle - cpu_info->last_idle;
|
||||
iowait_calc = iowait - cpu_info->last_iowait;
|
||||
irq_calc = irq - cpu_info->last_irq;
|
||||
softirq_calc = softirq - cpu_info->last_softirq;
|
||||
steal_calc = steal - cpu_info->last_steal;
|
||||
guest_calc = guest - cpu_info->last_guest;
|
||||
guest_nice_calc = guest_nice - cpu_info->last_guest_nice;
|
||||
|
||||
total = user_calc + nice_calc + system_calc + idle_calc + iowait_calc + irq_calc + softirq_calc + steal_calc + guest_calc + guest_nice_calc;
|
||||
cpu_info->total = ((total - idle_calc) / (gdouble)total) * 100.0;
|
||||
|
||||
cpu_info->last_user = user;
|
||||
cpu_info->last_nice = nice;
|
||||
cpu_info->last_idle = idle;
|
||||
cpu_info->last_system = sys;
|
||||
cpu_info->last_iowait = iowait;
|
||||
cpu_info->last_irq = irq;
|
||||
cpu_info->last_softirq = softirq;
|
||||
cpu_info->last_steal = steal;
|
||||
cpu_info->last_guest = guest;
|
||||
cpu_info->last_guest_nice = guest_nice;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* CPU info comes first. Skip further lines. */
|
||||
break;
|
||||
}
|
||||
|
||||
next:
|
||||
line = &self->stat_buf[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
publish_cpu (SpHostinfoSource *self)
|
||||
{
|
||||
SpCaptureCounterValue *counter_values;
|
||||
guint *counter_ids;
|
||||
|
||||
counter_ids = alloca (sizeof *counter_ids * self->n_cpu * 2);
|
||||
counter_values = alloca (sizeof *counter_values * self->n_cpu * 2);
|
||||
|
||||
for (guint i = 0; i < self->n_cpu; i++)
|
||||
{
|
||||
CpuInfo *info = &g_array_index (self->cpu_info, CpuInfo, i);
|
||||
SpCaptureCounterValue *value = &counter_values[i*2];
|
||||
guint *id = &counter_ids[i*2];
|
||||
|
||||
*id = info->counter_base;
|
||||
value->vdbl = info->total;
|
||||
|
||||
id++;
|
||||
value++;
|
||||
|
||||
*id = info->counter_base + 1;
|
||||
value->vdbl = info->freq;
|
||||
}
|
||||
|
||||
sp_capture_writer_set_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
getpid (),
|
||||
counter_ids,
|
||||
counter_values,
|
||||
self->n_cpu * 2);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
collect_hostinfo_cb (gpointer data)
|
||||
{
|
||||
SpHostinfoSource *self = data;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
poll_cpu (self);
|
||||
publish_cpu (self);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_finalize (GObject *object)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)object;
|
||||
|
||||
if (self->handler)
|
||||
{
|
||||
g_source_remove (self->handler);
|
||||
self->handler = 0;
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->cpu_info, g_array_unref);
|
||||
g_clear_pointer (&self->stat_buf, g_free);
|
||||
|
||||
G_OBJECT_CLASS (sp_hostinfo_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_class_init (SpHostinfoSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_hostinfo_source_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_init (SpHostinfoSource *self)
|
||||
{
|
||||
self->stat_fd = -1;
|
||||
self->cpu_info = g_array_new (FALSE, TRUE, sizeof (CpuInfo));
|
||||
self->stat_buf = g_malloc (PROC_STAT_BUF_SIZE);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_start (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
/* 20 samples per second */
|
||||
self->handler = g_timeout_add (1000/20, collect_hostinfo_cb, self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_stop (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
g_source_remove (self->handler);
|
||||
self->handler = 0;
|
||||
|
||||
if (self->stat_fd != -1)
|
||||
{
|
||||
close (self->stat_fd);
|
||||
self->stat_fd = -1;
|
||||
}
|
||||
|
||||
sp_source_emit_finished (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_hostinfo_source_prepare (SpSource *source)
|
||||
{
|
||||
SpHostinfoSource *self = (SpHostinfoSource *)source;
|
||||
SpCaptureCounter *counters;
|
||||
|
||||
g_assert (SP_IS_HOSTINFO_SOURCE (self));
|
||||
|
||||
self->stat_fd = open ("/proc/stat", O_RDONLY);
|
||||
self->n_cpu = g_get_num_processors ();
|
||||
|
||||
g_array_set_size (self->cpu_info, 0);
|
||||
|
||||
counters = alloca (sizeof *counters * self->n_cpu * 2);
|
||||
|
||||
for (guint i = 0; i < self->n_cpu; i++)
|
||||
{
|
||||
SpCaptureCounter *ctr = &counters[i*2];
|
||||
CpuInfo info = { 0 };
|
||||
|
||||
/*
|
||||
* Request 2 counter values.
|
||||
* One for CPU and one for Frequency.
|
||||
*/
|
||||
info.counter_base = sp_capture_writer_request_counter (self->writer, 2);
|
||||
|
||||
/*
|
||||
* Define counters for capture file.
|
||||
*/
|
||||
ctr->id = info.counter_base;
|
||||
ctr->type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
ctr->value.vdbl = 0;
|
||||
g_strlcpy (ctr->category, "CPU Percent", sizeof ctr->category);
|
||||
g_snprintf (ctr->name, sizeof ctr->name, "Total CPU %d", i);
|
||||
g_snprintf (ctr->description, sizeof ctr->description,
|
||||
"Total CPU usage %d", i);
|
||||
|
||||
ctr++;
|
||||
|
||||
ctr->id = info.counter_base + 1;
|
||||
ctr->type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
ctr->value.vdbl = 0;
|
||||
g_strlcpy (ctr->category, "CPU Frequency", sizeof ctr->category);
|
||||
g_snprintf (ctr->name, sizeof ctr->name, "CPU %d", i);
|
||||
g_snprintf (ctr->description, sizeof ctr->description,
|
||||
"Frequency of CPU %d", i);
|
||||
|
||||
g_array_append_val (self->cpu_info, info);
|
||||
}
|
||||
|
||||
sp_capture_writer_define_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
getpid (),
|
||||
counters,
|
||||
self->n_cpu * 2);
|
||||
|
||||
sp_source_emit_ready (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_hostinfo_source_set_writer;
|
||||
iface->prepare = sp_hostinfo_source_prepare;
|
||||
iface->start = sp_hostinfo_source_start;
|
||||
iface->stop = sp_hostinfo_source_stop;
|
||||
}
|
||||
35
src/libsysprof/sp-hostinfo-source.h
Normal file
35
src/libsysprof/sp-hostinfo-source.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* sp-hostinfo-source.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <christian@hergert.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_HOSTINFO_SOURCE_H
|
||||
#define SP_HOSTINFO_SOURCE_H
|
||||
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_HOSTINFO_SOURCE (sp_hostinfo_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpHostinfoSource, sp_hostinfo_source, SP, HOSTINFO_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_hostinfo_source_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_HOSTINFO_SOURCE_H */
|
||||
|
||||
121
src/libsysprof/sp-jitmap-symbol-resolver.c
Normal file
121
src/libsysprof/sp-jitmap-symbol-resolver.c
Normal file
@ -0,0 +1,121 @@
|
||||
/* sp-jitmap-symbol-resolver.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "sp-kernel-symbol.h"
|
||||
#include "sp-jitmap-symbol-resolver.h"
|
||||
|
||||
struct _SpJitmapSymbolResolver
|
||||
{
|
||||
GObject parent_instance;
|
||||
GHashTable *jitmap;
|
||||
};
|
||||
|
||||
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpJitmapSymbolResolver,
|
||||
sp_jitmap_symbol_resolver,
|
||||
G_TYPE_OBJECT,
|
||||
0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
|
||||
symbol_resolver_iface_init))
|
||||
|
||||
static void
|
||||
sp_jitmap_symbol_resolver_finalize (GObject *object)
|
||||
{
|
||||
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)object;
|
||||
|
||||
g_clear_pointer (&self->jitmap, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_jitmap_symbol_resolver_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_jitmap_symbol_resolver_class_init (SpJitmapSymbolResolverClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_jitmap_symbol_resolver_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_jitmap_symbol_resolver_init (SpJitmapSymbolResolver *self)
|
||||
{
|
||||
self->jitmap = g_hash_table_new_full (NULL, NULL, NULL, g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_jitmap_symbol_resolver_load (SpSymbolResolver *resolver,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)resolver;
|
||||
SpCaptureFrameType type;
|
||||
|
||||
g_assert (SP_IS_JITMAP_SYMBOL_RESOLVER (self));
|
||||
g_assert (reader != NULL);
|
||||
|
||||
while (sp_capture_reader_peek_type (reader, &type))
|
||||
{
|
||||
g_autoptr(GHashTable) jitmap = NULL;
|
||||
GHashTableIter iter;
|
||||
SpCaptureAddress addr;
|
||||
const gchar *str;
|
||||
|
||||
if (type != SP_CAPTURE_FRAME_JITMAP)
|
||||
{
|
||||
if (!sp_capture_reader_skip (reader))
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(jitmap = sp_capture_reader_read_jitmap (reader)))
|
||||
return;
|
||||
|
||||
g_hash_table_iter_init (&iter, jitmap);
|
||||
while (g_hash_table_iter_next (&iter, (gpointer *)&addr, (gpointer *)&str))
|
||||
g_hash_table_insert (self->jitmap, GSIZE_TO_POINTER (addr), g_strdup (str));
|
||||
}
|
||||
}
|
||||
|
||||
static gchar *
|
||||
sp_jitmap_symbol_resolver_resolve (SpSymbolResolver *resolver,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)resolver;
|
||||
|
||||
g_assert (SP_IS_JITMAP_SYMBOL_RESOLVER (self));
|
||||
|
||||
*tag = 0;
|
||||
|
||||
return g_strdup (g_hash_table_lookup (self->jitmap, GSIZE_TO_POINTER (address)));
|
||||
}
|
||||
|
||||
static void
|
||||
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
|
||||
{
|
||||
iface->load = sp_jitmap_symbol_resolver_load;
|
||||
iface->resolve = sp_jitmap_symbol_resolver_resolve;
|
||||
}
|
||||
|
||||
SpSymbolResolver *
|
||||
sp_jitmap_symbol_resolver_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_JITMAP_SYMBOL_RESOLVER, NULL);
|
||||
}
|
||||
34
src/libsysprof/sp-jitmap-symbol-resolver.h
Normal file
34
src/libsysprof/sp-jitmap-symbol-resolver.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-jitmap-symbol-resolver.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_JITMAP_SYMBOL_RESOLVER_H
|
||||
#define SP_JITMAP_SYMBOL_RESOLVER_H
|
||||
|
||||
#include "sp-symbol-resolver.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_JITMAP_SYMBOL_RESOLVER (sp_jitmap_symbol_resolver_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpJitmapSymbolResolver, sp_jitmap_symbol_resolver, SP, JITMAP_SYMBOL_RESOLVER, GObject)
|
||||
|
||||
SpSymbolResolver *sp_jitmap_symbol_resolver_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_JITMAP_SYMBOL_RESOLVER_H */
|
||||
82
src/libsysprof/sp-kernel-symbol-resolver.c
Normal file
82
src/libsysprof/sp-kernel-symbol-resolver.c
Normal file
@ -0,0 +1,82 @@
|
||||
/* sp-kernel-symbol-resolver.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "sp-kernel-symbol.h"
|
||||
#include "sp-kernel-symbol-resolver.h"
|
||||
|
||||
struct _SpKernelSymbolResolver
|
||||
{
|
||||
GObject parent_instance;
|
||||
};
|
||||
|
||||
static GQuark linux_quark;
|
||||
|
||||
static gchar *
|
||||
sp_kernel_symbol_resolver_resolve_with_context (SpSymbolResolver *resolver,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpAddressContext context,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
const SpKernelSymbol *sym;
|
||||
|
||||
g_assert (SP_IS_SYMBOL_RESOLVER (resolver));
|
||||
|
||||
if (context != SP_ADDRESS_CONTEXT_KERNEL)
|
||||
return NULL;
|
||||
|
||||
sym = sp_kernel_symbol_from_address (address);
|
||||
|
||||
if (sym != NULL)
|
||||
{
|
||||
*tag = linux_quark;
|
||||
return g_strdup (sym->name);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
|
||||
{
|
||||
iface->resolve_with_context = sp_kernel_symbol_resolver_resolve_with_context;
|
||||
}
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (SpKernelSymbolResolver,
|
||||
sp_kernel_symbol_resolver,
|
||||
G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
|
||||
symbol_resolver_iface_init))
|
||||
|
||||
static void
|
||||
sp_kernel_symbol_resolver_class_init (SpKernelSymbolResolverClass *klass)
|
||||
{
|
||||
linux_quark = g_quark_from_static_string ("Kernel");
|
||||
}
|
||||
|
||||
static void
|
||||
sp_kernel_symbol_resolver_init (SpKernelSymbolResolver *skernel)
|
||||
{
|
||||
}
|
||||
|
||||
SpSymbolResolver *
|
||||
sp_kernel_symbol_resolver_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_KERNEL_SYMBOL_RESOLVER, NULL);
|
||||
}
|
||||
34
src/libsysprof/sp-kernel-symbol-resolver.h
Normal file
34
src/libsysprof/sp-kernel-symbol-resolver.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-kernel-symbol-resolver.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_KERNEL_SYMBOL_RESOLVER_H
|
||||
#define SP_KERNEL_SYMBOL_RESOLVER_H
|
||||
|
||||
#include "sp-symbol-resolver.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_KERNEL_SYMBOL_RESOLVER (sp_kernel_symbol_resolver_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpKernelSymbolResolver, sp_kernel_symbol_resolver, SP, KERNEL_SYMBOL_RESOLVER, GObject)
|
||||
|
||||
SpSymbolResolver *sp_kernel_symbol_resolver_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_KERNEL_SYMBOL_RESOLVER_H */
|
||||
328
src/libsysprof/sp-kernel-symbol.c
Normal file
328
src/libsysprof/sp-kernel-symbol.c
Normal file
@ -0,0 +1,328 @@
|
||||
/* sp-kernel-symbol.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "sp-kernel-symbol"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#ifdef ENABLE_POLKIT
|
||||
# include <polkit/polkit.h>
|
||||
#endif
|
||||
#include <sysprof-capture.h>
|
||||
|
||||
#include "sp-kallsyms.h"
|
||||
#include "sp-line-reader.h"
|
||||
#include "sp-kernel-symbol.h"
|
||||
|
||||
static GArray *kernel_symbols;
|
||||
static GStringChunk *kernel_symbol_strs;
|
||||
static GHashTable *kernel_symbols_skip_hash;
|
||||
static const gchar *kernel_symbols_skip[] = {
|
||||
/* IRQ stack */
|
||||
"common_interrupt",
|
||||
"apic_timer_interrupt",
|
||||
"smp_apic_timer_interrupt",
|
||||
"hrtimer_interrupt",
|
||||
"__run_hrtimer",
|
||||
"perf_swevent_hrtimer",
|
||||
"perf_event_overflow",
|
||||
"__perf_event_overflow",
|
||||
"perf_prepare_sample",
|
||||
"perf_callchain",
|
||||
"perf_swcounter_hrtimer",
|
||||
"perf_counter_overflow",
|
||||
"__perf_counter_overflow",
|
||||
"perf_counter_output",
|
||||
|
||||
/* NMI stack */
|
||||
"nmi_stack_correct",
|
||||
"do_nmi",
|
||||
"notify_die",
|
||||
"atomic_notifier_call_chain",
|
||||
"notifier_call_chain",
|
||||
"perf_event_nmi_handler",
|
||||
"perf_counter_nmi_handler",
|
||||
"intel_pmu_handle_irq",
|
||||
"perf_event_overflow",
|
||||
"perf_counter_overflow",
|
||||
"__perf_event_overflow",
|
||||
"perf_prepare_sample",
|
||||
"perf_callchain",
|
||||
};
|
||||
|
||||
static gint
|
||||
sp_kernel_symbol_compare (gconstpointer a,
|
||||
gconstpointer b)
|
||||
{
|
||||
const SpKernelSymbol *syma = a;
|
||||
const SpKernelSymbol *symb = b;
|
||||
|
||||
if (syma->address > symb->address)
|
||||
return 1;
|
||||
else if (syma->address == symb->address)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
type_is_ignored (guint8 type)
|
||||
{
|
||||
/* Only allow symbols in the text (code) section */
|
||||
return (type != 't' && type != 'T');
|
||||
}
|
||||
|
||||
static gboolean
|
||||
authorize_proxy (GDBusConnection *conn)
|
||||
{
|
||||
#ifdef ENABLE_POLKIT
|
||||
PolkitSubject *subject = NULL;
|
||||
GPermission *permission = NULL;
|
||||
const gchar *name;
|
||||
|
||||
g_assert (G_IS_DBUS_CONNECTION (conn));
|
||||
|
||||
name = g_dbus_connection_get_unique_name (conn);
|
||||
if (name == NULL)
|
||||
goto failure;
|
||||
|
||||
subject = polkit_system_bus_name_new (name);
|
||||
if (subject == NULL)
|
||||
goto failure;
|
||||
|
||||
permission = polkit_permission_new_sync ("org.gnome.sysprof2.get-kernel-symbols", subject, NULL, NULL);
|
||||
if (permission == NULL)
|
||||
goto failure;
|
||||
|
||||
if (!g_permission_acquire (permission, NULL, NULL))
|
||||
goto failure;
|
||||
|
||||
return TRUE;
|
||||
|
||||
failure:
|
||||
g_clear_object (&subject);
|
||||
g_clear_object (&permission);
|
||||
#endif
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_kernel_symbol_load_from_sysprofd (void)
|
||||
{
|
||||
g_autoptr(GDBusConnection) conn = NULL;
|
||||
g_autoptr(GVariant) ret = NULL;
|
||||
g_autoptr(GVariant) results = NULL;
|
||||
g_autoptr(GArray) ar = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
GVariantIter iter;
|
||||
const gchar *name;
|
||||
guint64 addr;
|
||||
guint8 type;
|
||||
|
||||
if (!(conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL)))
|
||||
return FALSE;
|
||||
|
||||
if (!authorize_proxy (conn))
|
||||
{
|
||||
g_warning ("Failed to acquire sufficient credentials to read kernel symbols");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ret = g_dbus_connection_call_sync (conn,
|
||||
"org.gnome.Sysprof2",
|
||||
"/org/gnome/Sysprof2",
|
||||
"org.gnome.Sysprof2",
|
||||
"GetKernelSymbols",
|
||||
NULL,
|
||||
G_VARIANT_TYPE ("(a(tys))"),
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
&error);
|
||||
|
||||
if (error != NULL)
|
||||
{
|
||||
g_warning ("Failed to load symbols from sysprofd: %s", error->message);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ar = g_array_new (FALSE, TRUE, sizeof (SpKernelSymbol));
|
||||
|
||||
results = g_variant_get_child_value (ret, 0);
|
||||
g_variant_iter_init (&iter, results);
|
||||
while (g_variant_iter_loop (&iter, "(ty&s)", &addr, &type, &name))
|
||||
{
|
||||
SpKernelSymbol sym;
|
||||
|
||||
if (type_is_ignored (type))
|
||||
continue;
|
||||
|
||||
sym.address = addr;
|
||||
sym.name = g_string_chunk_insert_const (kernel_symbol_strs, name);
|
||||
|
||||
g_array_append_val (ar, sym);
|
||||
}
|
||||
|
||||
g_array_sort (ar, sp_kernel_symbol_compare);
|
||||
|
||||
#if 0
|
||||
g_print ("First: 0x%lx Last: 0x%lx\n",
|
||||
g_array_index (ar, SpKernelSymbol, 0).address,
|
||||
g_array_index (ar, SpKernelSymbol, ar->len - 1).address);
|
||||
#endif
|
||||
|
||||
kernel_symbols = g_steal_pointer (&ar);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_kernel_symbol_load (void)
|
||||
{
|
||||
g_autoptr(GHashTable) skip = NULL;
|
||||
g_autoptr(SpKallsyms) kallsyms = NULL;
|
||||
g_autoptr(GArray) ar = NULL;
|
||||
const gchar *name;
|
||||
guint64 addr;
|
||||
guint8 type;
|
||||
|
||||
skip = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
for (guint i = 0; i < G_N_ELEMENTS (kernel_symbols_skip); i++)
|
||||
g_hash_table_insert (skip, (gchar *)kernel_symbols_skip[i], NULL);
|
||||
kernel_symbols_skip_hash = g_steal_pointer (&skip);
|
||||
|
||||
kernel_symbol_strs = g_string_chunk_new (4096);
|
||||
ar = g_array_new (FALSE, TRUE, sizeof (SpKernelSymbol));
|
||||
|
||||
if (!(kallsyms = sp_kallsyms_new (NULL)))
|
||||
goto query_daemon;
|
||||
|
||||
while (sp_kallsyms_next (kallsyms, &name, &addr, &type))
|
||||
{
|
||||
SpKernelSymbol sym;
|
||||
|
||||
if (type_is_ignored (type))
|
||||
continue;
|
||||
|
||||
sym.address = addr;
|
||||
sym.name = g_string_chunk_insert_const (kernel_symbol_strs, name);
|
||||
|
||||
g_array_append_val (ar, sym);
|
||||
}
|
||||
|
||||
if (ar->len == 0)
|
||||
goto query_daemon;
|
||||
|
||||
g_array_sort (ar, sp_kernel_symbol_compare);
|
||||
kernel_symbols = g_steal_pointer (&ar);
|
||||
|
||||
return TRUE;
|
||||
|
||||
query_daemon:
|
||||
if (sp_kernel_symbol_load_from_sysprofd ())
|
||||
return TRUE;
|
||||
|
||||
g_warning ("Kernel symbols will not be available.");
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static const SpKernelSymbol *
|
||||
sp_kernel_symbol_lookup (SpKernelSymbol *symbols,
|
||||
SpCaptureAddress address,
|
||||
guint first,
|
||||
guint last)
|
||||
{
|
||||
if (address >= symbols [last].address)
|
||||
{
|
||||
return &symbols [last];
|
||||
}
|
||||
else if (last - first < 3)
|
||||
{
|
||||
while (last >= first)
|
||||
{
|
||||
if (address >= symbols[last].address)
|
||||
return &symbols [last];
|
||||
|
||||
last--;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
int mid = (first + last) / 2;
|
||||
|
||||
if (symbols [mid].address > address)
|
||||
return sp_kernel_symbol_lookup (symbols, address, first, mid);
|
||||
else
|
||||
return sp_kernel_symbol_lookup (symbols, address, mid, last);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_kernel_symbol_from_address:
|
||||
* @address: the address of the instruction pointer
|
||||
*
|
||||
* Locates the kernel symbol that contains @address.
|
||||
*
|
||||
* Returns: (transfer none): An #SpKernelSymbol or %NULL.
|
||||
*/
|
||||
const SpKernelSymbol *
|
||||
sp_kernel_symbol_from_address (SpCaptureAddress address)
|
||||
{
|
||||
const SpKernelSymbol *first;
|
||||
const SpKernelSymbol *ret;
|
||||
|
||||
if G_UNLIKELY (kernel_symbols == NULL)
|
||||
{
|
||||
static gboolean failed;
|
||||
|
||||
if (failed)
|
||||
return NULL;
|
||||
|
||||
if (!sp_kernel_symbol_load ())
|
||||
{
|
||||
failed = TRUE;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
g_assert (kernel_symbols != NULL);
|
||||
g_assert (kernel_symbols->len > 0);
|
||||
|
||||
/* Short circuit if this is out of range */
|
||||
first = &g_array_index (kernel_symbols, SpKernelSymbol, 0);
|
||||
if (address < first->address)
|
||||
return NULL;
|
||||
|
||||
ret = sp_kernel_symbol_lookup ((SpKernelSymbol *)(gpointer)kernel_symbols->data,
|
||||
address,
|
||||
0,
|
||||
kernel_symbols->len - 1);
|
||||
|
||||
/* We resolve all symbols, including ignored symbols so that we
|
||||
* don't give back the wrong function juxtapose an ignored func.
|
||||
*/
|
||||
if (ret != NULL && g_hash_table_contains (kernel_symbols_skip_hash, ret->name))
|
||||
return NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
36
src/libsysprof/sp-kernel-symbol.h
Normal file
36
src/libsysprof/sp-kernel-symbol.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-kernel-symbol.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_KERNEL_SYMBOL_H
|
||||
#define SP_KERNEL_SYMBOL_H
|
||||
|
||||
#include "sp-capture-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureAddress address;
|
||||
const gchar *name;
|
||||
} SpKernelSymbol;
|
||||
|
||||
const SpKernelSymbol *sp_kernel_symbol_from_address (SpCaptureAddress address);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_KERNEL_SYMBOL_H */
|
||||
824
src/libsysprof/sp-local-profiler.c
Normal file
824
src/libsysprof/sp-local-profiler.c
Normal file
@ -0,0 +1,824 @@
|
||||
/* sp-local-profiler.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-local-profiler.h"
|
||||
#include "sp-platform.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureWriter *writer;
|
||||
|
||||
/* All sources added */
|
||||
GPtrArray *sources;
|
||||
|
||||
/* Array of GError failures */
|
||||
GPtrArray *failures;
|
||||
|
||||
/* Sources currently starting */
|
||||
GPtrArray *starting;
|
||||
|
||||
/* Sources currently stopping */
|
||||
GPtrArray *stopping;
|
||||
|
||||
/* Sources that have failed or finished */
|
||||
GPtrArray *finished_or_failed;
|
||||
|
||||
/* Pids to notify children about before prepare */
|
||||
GArray *pids;
|
||||
|
||||
/* Timer for simple time tracking */
|
||||
GTimer *timer;
|
||||
guint timer_notify_source;
|
||||
|
||||
/* Arguments and environment variables for spawning */
|
||||
gchar **spawn_argv;
|
||||
gchar **spawn_env;
|
||||
|
||||
/* State flags */
|
||||
guint is_running : 1;
|
||||
guint is_stopping : 1;
|
||||
guint is_starting : 1;
|
||||
|
||||
/*
|
||||
* If we should spawn argv when starting up. This allows UI to set
|
||||
* spawn argv/env but enable disable with a toggle.
|
||||
*/
|
||||
guint spawn : 1;
|
||||
|
||||
/* If we should inherit the environment when spawning */
|
||||
guint spawn_inherit_environ : 1;
|
||||
|
||||
/*
|
||||
* If we should profile the entire system. Setting this results in pids
|
||||
* being ignored. This is primarily useful for UI to toggle on/off the
|
||||
* feature of per-process vs whole-system.
|
||||
*/
|
||||
guint whole_system : 1;
|
||||
|
||||
/*
|
||||
* If we got a stop request after calling start() but before we have had
|
||||
* a chance to settle, then we need to stop immediately after starting.
|
||||
* We do this to avoid a more complex state machine (for now).
|
||||
*/
|
||||
guint stop_after_starting : 1;
|
||||
} SpLocalProfilerPrivate;
|
||||
|
||||
static void profiler_iface_init (SpProfilerInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpLocalProfiler, sp_local_profiler, G_TYPE_OBJECT, 0,
|
||||
G_ADD_PRIVATE (SpLocalProfiler)
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_PROFILER, profiler_iface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
N_PROPS,
|
||||
|
||||
PROP_ELAPSED,
|
||||
PROP_IS_MUTABLE,
|
||||
PROP_IS_RUNNING,
|
||||
PROP_SPAWN,
|
||||
PROP_SPAWN_ARGV,
|
||||
PROP_SPAWN_ENV,
|
||||
PROP_SPAWN_INHERIT_ENVIRON,
|
||||
PROP_WHOLE_SYSTEM,
|
||||
};
|
||||
|
||||
static inline gint
|
||||
_g_ptr_array_find (GPtrArray *ar,
|
||||
gpointer item)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < ar->len; i++)
|
||||
{
|
||||
if (item == g_ptr_array_index (ar, i))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
_g_ptr_array_contains (GPtrArray *ar,
|
||||
gpointer item)
|
||||
{
|
||||
return (-1 != _g_ptr_array_find (ar, item));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_clear_timer (SpLocalProfiler *self)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
|
||||
g_clear_pointer (&priv->timer, g_timer_destroy);
|
||||
|
||||
if (priv->timer_notify_source != 0)
|
||||
{
|
||||
g_source_remove (priv->timer_notify_source);
|
||||
priv->timer_notify_source = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_real_stopped (SpProfiler *profiler)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
|
||||
sp_local_profiler_clear_timer (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_local_profiler_notify_elapsed_cb (gpointer data)
|
||||
{
|
||||
SpLocalProfiler *self = data;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
|
||||
g_object_notify (G_OBJECT (self), "elapsed");
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_finish_stopping (SpLocalProfiler *self)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (priv->is_starting == FALSE);
|
||||
g_assert (priv->is_stopping == TRUE);
|
||||
g_assert (priv->stopping->len == 0);
|
||||
|
||||
if (priv->failures->len > 0)
|
||||
{
|
||||
const GError *error = g_ptr_array_index (priv->failures, 0);
|
||||
|
||||
sp_profiler_emit_failed (SP_PROFILER (self), error);
|
||||
}
|
||||
|
||||
priv->is_running = FALSE;
|
||||
priv->is_stopping = FALSE;
|
||||
|
||||
sp_profiler_emit_stopped (SP_PROFILER (self));
|
||||
|
||||
g_object_notify (G_OBJECT (self), "is-mutable");
|
||||
g_object_notify (G_OBJECT (self), "is-running");
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_stop (SpProfiler *profiler)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
|
||||
if (priv->is_starting)
|
||||
{
|
||||
priv->stop_after_starting = TRUE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (priv->is_stopping || !priv->is_running)
|
||||
return;
|
||||
|
||||
priv->is_stopping = TRUE;
|
||||
|
||||
/*
|
||||
* First we add everything to the stopping list, so that we can
|
||||
* be notified of when they have completed. If everything stopped
|
||||
* synchronously, the stopping list will be empty after calling
|
||||
* sp_source_stop() for every source. Otherwise, we need to delay
|
||||
* stopping for a little bit.
|
||||
*/
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
|
||||
if (!_g_ptr_array_contains (priv->finished_or_failed, source))
|
||||
g_ptr_array_add (priv->stopping, g_object_ref (source));
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
|
||||
sp_source_stop (source);
|
||||
}
|
||||
|
||||
if (priv->is_stopping && priv->stopping->len == 0)
|
||||
sp_local_profiler_finish_stopping (self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
sp_local_profiler_dispose (GObject *object)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)object;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
if (priv->is_running || priv->is_starting)
|
||||
{
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
return;
|
||||
}
|
||||
|
||||
sp_local_profiler_clear_timer (self);
|
||||
|
||||
G_OBJECT_CLASS (sp_local_profiler_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_finalize (GObject *object)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)object;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_clear_pointer (&priv->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&priv->sources, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->starting, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->stopping, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->failures, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->finished_or_failed, g_ptr_array_unref);
|
||||
g_clear_pointer (&priv->pids, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_local_profiler_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpLocalProfiler *self = SP_LOCAL_PROFILER (object);
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_ELAPSED:
|
||||
g_value_set_double (value, priv->timer ? g_timer_elapsed (priv->timer, NULL) : 0.0);
|
||||
break;
|
||||
|
||||
case PROP_IS_MUTABLE:
|
||||
g_value_set_boolean (value, !(priv->is_starting || priv->is_starting || priv->is_running));
|
||||
break;
|
||||
|
||||
case PROP_IS_RUNNING:
|
||||
g_value_set_boolean (value, priv->is_running);
|
||||
break;
|
||||
|
||||
case PROP_WHOLE_SYSTEM:
|
||||
g_value_set_boolean (value, priv->whole_system);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN:
|
||||
g_value_set_boolean (value, priv->spawn);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_INHERIT_ENVIRON:
|
||||
g_value_set_boolean (value, priv->spawn_inherit_environ);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_ARGV:
|
||||
g_value_set_boxed (value, priv->spawn_argv);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_ENV:
|
||||
g_value_set_boxed (value, priv->spawn_env);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpLocalProfiler *self = SP_LOCAL_PROFILER (object);
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_WHOLE_SYSTEM:
|
||||
priv->whole_system = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN:
|
||||
priv->spawn = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_INHERIT_ENVIRON:
|
||||
priv->spawn_inherit_environ = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_ARGV:
|
||||
g_strfreev (priv->spawn_argv);
|
||||
priv->spawn_argv = g_value_dup_boxed (value);
|
||||
break;
|
||||
|
||||
case PROP_SPAWN_ENV:
|
||||
g_strfreev (priv->spawn_env);
|
||||
priv->spawn_env = g_value_dup_boxed (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_class_init (SpLocalProfilerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = sp_local_profiler_dispose;
|
||||
object_class->finalize = sp_local_profiler_finalize;
|
||||
object_class->get_property = sp_local_profiler_get_property;
|
||||
object_class->set_property = sp_local_profiler_set_property;
|
||||
|
||||
g_object_class_override_property (object_class, PROP_ELAPSED, "elapsed");
|
||||
g_object_class_override_property (object_class, PROP_IS_MUTABLE, "is-mutable");
|
||||
g_object_class_override_property (object_class, PROP_IS_RUNNING, "is-running");
|
||||
g_object_class_override_property (object_class, PROP_SPAWN, "spawn");
|
||||
g_object_class_override_property (object_class, PROP_SPAWN_ARGV, "spawn-argv");
|
||||
g_object_class_override_property (object_class, PROP_SPAWN_ENV, "spawn-env");
|
||||
g_object_class_override_property (object_class, PROP_SPAWN_INHERIT_ENVIRON, "spawn-inherit-environ");
|
||||
g_object_class_override_property (object_class, PROP_WHOLE_SYSTEM, "whole-system");
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_init (SpLocalProfiler *self)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
priv->whole_system = TRUE;
|
||||
|
||||
priv->failures = g_ptr_array_new_with_free_func ((GDestroyNotify)g_error_free);
|
||||
priv->sources = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
priv->starting = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
priv->stopping = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
priv->finished_or_failed = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
priv->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
}
|
||||
|
||||
SpProfiler *
|
||||
sp_local_profiler_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_LOCAL_PROFILER, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_finish_startup (SpLocalProfiler *self)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (priv->is_starting == TRUE);
|
||||
g_assert (priv->starting->len == 0);
|
||||
|
||||
sp_local_profiler_clear_timer (self);
|
||||
|
||||
priv->timer = g_timer_new ();
|
||||
|
||||
/*
|
||||
* Add a source to update our watchers of elapsed time.
|
||||
* We use 1000 instead of add_seconds(1) so that we are
|
||||
* not subject to as much drift.
|
||||
*/
|
||||
priv->timer_notify_source =
|
||||
g_timeout_add (1000,
|
||||
sp_local_profiler_notify_elapsed_cb,
|
||||
self);
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
|
||||
sp_source_start (source);
|
||||
}
|
||||
|
||||
priv->is_starting = FALSE;
|
||||
|
||||
/*
|
||||
* If any of the sources failed during startup, we will have a non-empty
|
||||
* failures list.
|
||||
*/
|
||||
if (priv->failures->len > 0)
|
||||
{
|
||||
const GError *error = g_ptr_array_index (priv->failures, 0);
|
||||
|
||||
g_object_ref (self);
|
||||
sp_profiler_emit_failed (SP_PROFILER (self), error);
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
g_object_unref (self);
|
||||
return;
|
||||
}
|
||||
|
||||
priv->is_running = TRUE;
|
||||
|
||||
g_object_notify (G_OBJECT (self), "is-mutable");
|
||||
g_object_notify (G_OBJECT (self), "is-running");
|
||||
|
||||
/*
|
||||
* If all the sources are transient (in that they just generate information
|
||||
* and then exit), we could be finished as soon as we complete startup.
|
||||
*
|
||||
* If we detect this, we stop immediately.
|
||||
*/
|
||||
if (priv->finished_or_failed->len == priv->sources->len || priv->stop_after_starting)
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_start (SpProfiler *profiler)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
g_return_if_fail (priv->is_starting == FALSE);
|
||||
|
||||
g_clear_pointer (&priv->timer, g_timer_destroy);
|
||||
g_object_notify (G_OBJECT (self), "elapsed");
|
||||
|
||||
if (priv->writer == NULL)
|
||||
{
|
||||
SpCaptureWriter *writer;
|
||||
int fd;
|
||||
|
||||
if ((-1 == (fd = sp_memfd_create ("[sysprof]"))) ||
|
||||
(NULL == (writer = sp_capture_writer_new_from_fd (fd, 0))))
|
||||
{
|
||||
const GError error = {
|
||||
G_FILE_ERROR,
|
||||
g_file_error_from_errno (errno),
|
||||
(gchar *)g_strerror (errno)
|
||||
};
|
||||
|
||||
if (fd != -1)
|
||||
close (fd);
|
||||
|
||||
sp_profiler_emit_failed (SP_PROFILER (self), &error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sp_profiler_set_writer (SP_PROFILER (self), writer);
|
||||
g_clear_pointer (&writer, sp_capture_writer_unref);
|
||||
}
|
||||
|
||||
priv->is_running = TRUE;
|
||||
priv->is_starting = TRUE;
|
||||
|
||||
if (priv->failures->len > 0)
|
||||
g_ptr_array_remove_range (priv->failures, 0, priv->failures->len);
|
||||
|
||||
if (priv->spawn && priv->spawn_argv && priv->spawn_argv[0])
|
||||
{
|
||||
g_autoptr(GPtrArray) ar = g_ptr_array_new_with_free_func (g_free);
|
||||
GPid pid;
|
||||
GError *error = NULL;
|
||||
|
||||
if (priv->spawn_inherit_environ)
|
||||
{
|
||||
gchar **environ = g_get_environ ();
|
||||
|
||||
for (i = 0; environ[i]; i++)
|
||||
g_ptr_array_add (ar, environ[i]);
|
||||
g_free (environ);
|
||||
}
|
||||
|
||||
if (priv->spawn_env)
|
||||
{
|
||||
for (i = 0; priv->spawn_env[i]; i++)
|
||||
g_ptr_array_add (ar, g_strdup (priv->spawn_env[i]));
|
||||
}
|
||||
|
||||
g_ptr_array_add (ar, NULL);
|
||||
|
||||
if (!g_spawn_async (g_get_home_dir (),
|
||||
priv->spawn_argv,
|
||||
(gchar **)ar->pdata,
|
||||
(G_SPAWN_SEARCH_PATH |
|
||||
G_SPAWN_STDOUT_TO_DEV_NULL |
|
||||
G_SPAWN_STDOUT_TO_DEV_NULL),
|
||||
NULL,
|
||||
NULL,
|
||||
&pid,
|
||||
&error))
|
||||
g_ptr_array_add (priv->failures, error);
|
||||
else
|
||||
g_array_append_val (priv->pids, pid);
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
guint j;
|
||||
|
||||
if (priv->whole_system == FALSE)
|
||||
{
|
||||
for (j = 0; j < priv->pids->len; j++)
|
||||
{
|
||||
GPid pid = g_array_index (priv->pids, GPid, j);
|
||||
|
||||
sp_source_add_pid (source, pid);
|
||||
}
|
||||
}
|
||||
|
||||
sp_source_set_writer (source, priv->writer);
|
||||
sp_source_prepare (source);
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->sources->len; i++)
|
||||
{
|
||||
SpSource *source = g_ptr_array_index (priv->sources, i);
|
||||
|
||||
if (!sp_source_get_is_ready (source))
|
||||
g_ptr_array_add (priv->starting, g_object_ref (source));
|
||||
}
|
||||
|
||||
if (priv->starting->len == 0)
|
||||
sp_local_profiler_finish_startup (self);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_set_writer (SpProfiler *profiler,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
g_return_if_fail (writer != NULL);
|
||||
|
||||
if (priv->writer != writer)
|
||||
{
|
||||
g_clear_pointer (&priv->writer, sp_capture_writer_unref);
|
||||
|
||||
if (writer != NULL)
|
||||
priv->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_track_completed (SpLocalProfiler *self,
|
||||
SpSource *source)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
gint i;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (SP_IS_SOURCE (source));
|
||||
|
||||
if (!_g_ptr_array_contains (priv->finished_or_failed, source))
|
||||
g_ptr_array_add (priv->finished_or_failed, g_object_ref (source));
|
||||
|
||||
if (priv->is_starting)
|
||||
{
|
||||
i = _g_ptr_array_find (priv->starting, source);
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
g_ptr_array_remove_index (priv->starting, i);
|
||||
if (priv->starting->len == 0)
|
||||
sp_local_profiler_finish_startup (self);
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->is_stopping)
|
||||
{
|
||||
i = _g_ptr_array_find (priv->stopping, source);
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
g_ptr_array_remove_index_fast (priv->stopping, i);
|
||||
|
||||
if ((priv->is_stopping == TRUE) && (priv->stopping->len == 0))
|
||||
sp_local_profiler_finish_stopping (self);
|
||||
}
|
||||
}
|
||||
|
||||
if (!priv->is_starting)
|
||||
{
|
||||
if (priv->finished_or_failed->len == priv->sources->len)
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_source_finished (SpLocalProfiler *self,
|
||||
SpSource *source)
|
||||
{
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (SP_IS_SOURCE (source));
|
||||
|
||||
sp_local_profiler_track_completed (self, source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_source_ready (SpLocalProfiler *self,
|
||||
SpSource *source)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (SP_IS_SOURCE (source));
|
||||
|
||||
for (i = 0; i < priv->starting->len; i++)
|
||||
{
|
||||
SpSource *ele = g_ptr_array_index (priv->starting, i);
|
||||
|
||||
if (ele == source)
|
||||
{
|
||||
g_ptr_array_remove_index_fast (priv->starting, i);
|
||||
|
||||
if ((priv->is_starting == TRUE) && (priv->starting->len == 0))
|
||||
sp_local_profiler_finish_startup (self);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_source_failed (SpLocalProfiler *self,
|
||||
const GError *reason,
|
||||
SpSource *source)
|
||||
{
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_assert (SP_IS_LOCAL_PROFILER (self));
|
||||
g_assert (reason != NULL);
|
||||
g_assert (SP_IS_SOURCE (source));
|
||||
|
||||
sp_local_profiler_track_completed (self, source);
|
||||
|
||||
/* Failure emitted out of band */
|
||||
if (!priv->is_starting && !priv->is_stopping && !priv->is_running)
|
||||
return;
|
||||
|
||||
g_ptr_array_add (priv->failures, g_error_copy (reason));
|
||||
|
||||
/* Ignore during start/stop, we handle this in other places */
|
||||
if (priv->is_starting || priv->is_stopping)
|
||||
return;
|
||||
|
||||
if (priv->is_running)
|
||||
sp_local_profiler_stop (SP_PROFILER (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_add_source (SpProfiler *profiler,
|
||||
SpSource *source)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (SP_IS_SOURCE (source));
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
g_return_if_fail (priv->is_starting == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
|
||||
g_signal_connect_object (source,
|
||||
"failed",
|
||||
G_CALLBACK (sp_local_profiler_source_failed),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (source,
|
||||
"finished",
|
||||
G_CALLBACK (sp_local_profiler_source_finished),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
g_signal_connect_object (source,
|
||||
"ready",
|
||||
G_CALLBACK (sp_local_profiler_source_ready),
|
||||
self,
|
||||
G_CONNECT_SWAPPED);
|
||||
|
||||
|
||||
g_ptr_array_add (priv->sources, g_object_ref (source));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_add_pid (SpProfiler *profiler,
|
||||
GPid pid)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (pid > -1);
|
||||
g_return_if_fail (priv->is_starting == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
|
||||
g_array_append_val (priv->pids, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_local_profiler_remove_pid (SpProfiler *profiler,
|
||||
GPid pid)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
|
||||
g_return_if_fail (pid > -1);
|
||||
g_return_if_fail (priv->is_starting == FALSE);
|
||||
g_return_if_fail (priv->is_stopping == FALSE);
|
||||
g_return_if_fail (priv->is_running == FALSE);
|
||||
|
||||
for (i = 0; i < priv->pids->len; i++)
|
||||
{
|
||||
GPid ele = g_array_index (priv->pids, GPid, i);
|
||||
|
||||
if (ele == pid)
|
||||
{
|
||||
g_array_remove_index_fast (priv->pids, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const GPid *
|
||||
sp_local_profiler_get_pids (SpProfiler *profiler,
|
||||
guint *n_pids)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_LOCAL_PROFILER (self), NULL);
|
||||
g_return_val_if_fail (n_pids != NULL, NULL);
|
||||
|
||||
*n_pids = priv->pids->len;
|
||||
|
||||
return (GPid *)(gpointer)priv->pids->data;
|
||||
}
|
||||
|
||||
static SpCaptureWriter *
|
||||
sp_local_profiler_get_writer (SpProfiler *profiler)
|
||||
{
|
||||
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
|
||||
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
|
||||
|
||||
g_return_val_if_fail (SP_IS_LOCAL_PROFILER (self), NULL);
|
||||
|
||||
return priv->writer;
|
||||
}
|
||||
|
||||
static void
|
||||
profiler_iface_init (SpProfilerInterface *iface)
|
||||
{
|
||||
iface->add_pid = sp_local_profiler_add_pid;
|
||||
iface->add_source = sp_local_profiler_add_source;
|
||||
iface->get_pids = sp_local_profiler_get_pids;
|
||||
iface->get_writer = sp_local_profiler_get_writer;
|
||||
iface->remove_pid = sp_local_profiler_remove_pid;
|
||||
iface->set_writer = sp_local_profiler_set_writer;
|
||||
iface->start = sp_local_profiler_start;
|
||||
iface->stop = sp_local_profiler_stop;
|
||||
iface->stopped = sp_local_profiler_real_stopped;
|
||||
}
|
||||
40
src/libsysprof/sp-local-profiler.h
Normal file
40
src/libsysprof/sp-local-profiler.h
Normal file
@ -0,0 +1,40 @@
|
||||
/* sp-local-profiler.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_LOCAL_PROFILER_H
|
||||
#define SP_LOCAL_PROFILER_H
|
||||
|
||||
#include "sp-profiler.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_LOCAL_PROFILER (sp_local_profiler_get_type())
|
||||
|
||||
G_DECLARE_DERIVABLE_TYPE (SpLocalProfiler, sp_local_profiler, SP, LOCAL_PROFILER, GObject)
|
||||
|
||||
struct _SpLocalProfilerClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
gpointer padding[8];
|
||||
};
|
||||
|
||||
SpProfiler *sp_local_profiler_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_LOCAL_PROFILER_H */
|
||||
119
src/libsysprof/sp-map-lookaside.c
Normal file
119
src/libsysprof/sp-map-lookaside.c
Normal file
@ -0,0 +1,119 @@
|
||||
/* sp-map-lookaside.c
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "sp-map-lookaside.h"
|
||||
|
||||
struct _SpMapLookaside
|
||||
{
|
||||
GSequence *seq;
|
||||
GStringChunk *chunk;
|
||||
};
|
||||
|
||||
static gint
|
||||
sp_map_compare (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
const SpMap *map_a = a;
|
||||
const SpMap *map_b = b;
|
||||
|
||||
return sp_capture_address_compare (map_a->start, map_b->start);
|
||||
}
|
||||
|
||||
static gint
|
||||
sp_map_compare_in_range (gconstpointer a,
|
||||
gconstpointer b,
|
||||
gpointer user_data)
|
||||
{
|
||||
const SpMap *map_a = a;
|
||||
const SpMap *map_b = b;
|
||||
|
||||
/*
|
||||
* map_b is the needle for the search.
|
||||
* Only map_b->start is set.
|
||||
*/
|
||||
|
||||
if ((map_b->start >= map_a->start) && (map_b->start < map_a->end))
|
||||
return 0;
|
||||
|
||||
return sp_capture_address_compare (map_a->start, map_b->start);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_map_free (gpointer data)
|
||||
{
|
||||
SpMap *map = data;
|
||||
|
||||
g_slice_free (SpMap, map);
|
||||
}
|
||||
|
||||
SpMapLookaside *
|
||||
sp_map_lookaside_new (void)
|
||||
{
|
||||
SpMapLookaside *ret;
|
||||
|
||||
ret = g_slice_new (SpMapLookaside);
|
||||
ret->seq = g_sequence_new (sp_map_free);
|
||||
ret->chunk = g_string_chunk_new (4096);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
sp_map_lookaside_free (SpMapLookaside *self)
|
||||
{
|
||||
g_sequence_free (self->seq);
|
||||
g_string_chunk_free (self->chunk);
|
||||
g_slice_free (SpMapLookaside, self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_map_lookaside_insert (SpMapLookaside *self,
|
||||
const SpMap *map)
|
||||
{
|
||||
SpMap *copy;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (map != NULL);
|
||||
|
||||
copy = g_slice_new (SpMap);
|
||||
copy->start = map->start;
|
||||
copy->end = map->end;
|
||||
copy->offset = map->offset;
|
||||
copy->inode = map->inode;
|
||||
copy->filename = g_string_chunk_insert_const (self->chunk, map->filename);
|
||||
|
||||
g_sequence_insert_sorted (self->seq, copy, sp_map_compare, NULL);
|
||||
}
|
||||
|
||||
const SpMap *
|
||||
sp_map_lookaside_lookup (SpMapLookaside *self,
|
||||
SpCaptureAddress address)
|
||||
{
|
||||
SpMap map = { address };
|
||||
GSequenceIter *iter;
|
||||
|
||||
g_assert (self != NULL);
|
||||
|
||||
iter = g_sequence_lookup (self->seq, &map, sp_map_compare_in_range, NULL);
|
||||
|
||||
if (iter != NULL)
|
||||
return g_sequence_get (iter);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
50
src/libsysprof/sp-map-lookaside.h
Normal file
50
src/libsysprof/sp-map-lookaside.h
Normal file
@ -0,0 +1,50 @@
|
||||
/* sp-map-lookaside.h
|
||||
*
|
||||
* Copyright 2016 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SP_MAP_LOOKASIDE_H
|
||||
#define SP_MAP_LOOKASIDE_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "sp-capture-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _SpMapLookaside SpMapLookaside;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SpCaptureAddress start;
|
||||
SpCaptureAddress end;
|
||||
off_t offset;
|
||||
ino_t inode;
|
||||
const gchar *filename;
|
||||
} SpMap;
|
||||
|
||||
SpMapLookaside *sp_map_lookaside_new (void);
|
||||
void sp_map_lookaside_insert (SpMapLookaside *self,
|
||||
const SpMap *map);
|
||||
const SpMap *sp_map_lookaside_lookup (SpMapLookaside *self,
|
||||
SpCaptureAddress address);
|
||||
void sp_map_lookaside_free (SpMapLookaside *self);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpMapLookaside, sp_map_lookaside_free)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_MAP_LOOKASIDE_H */
|
||||
467
src/libsysprof/sp-memory-source.c
Normal file
467
src/libsysprof/sp-memory-source.c
Normal file
@ -0,0 +1,467 @@
|
||||
/* sp-memory-source.c
|
||||
*
|
||||
* Copyright 2018 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#define G_LOG_DOMAIN "sp-memory-source"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sysprof-capture.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-memory-source.h"
|
||||
|
||||
#define BUF_SIZE 4096
|
||||
|
||||
struct _SpMemorySource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
/* Capture writer to deliver samples */
|
||||
SpCaptureWriter *writer;
|
||||
|
||||
/* 4k stat buffer for reading proc */
|
||||
gchar *stat_buf;
|
||||
|
||||
/* Array of MemStat */
|
||||
GArray *mem_stats;
|
||||
|
||||
/* Timeout to perform polling */
|
||||
guint timer_source;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GPid pid;
|
||||
int stat_fd;
|
||||
|
||||
union {
|
||||
struct {
|
||||
SpCaptureCounterValue used;
|
||||
gint64 total;
|
||||
gint64 avail;
|
||||
gint64 free;
|
||||
} sys;
|
||||
struct {
|
||||
SpCaptureCounterValue used;
|
||||
gint64 size;
|
||||
gint64 resident;
|
||||
gint64 shared;
|
||||
gint64 text;
|
||||
gint64 data;
|
||||
} proc;
|
||||
};
|
||||
|
||||
guint counter_ids[1];
|
||||
} MemStat;
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (SpMemorySource, sp_memory_source, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
static GHashTable *keys;
|
||||
|
||||
static void
|
||||
mem_stat_open (MemStat *st)
|
||||
{
|
||||
g_assert (st != NULL);
|
||||
g_assert (st->stat_fd == -1);
|
||||
|
||||
if (st->pid != -1)
|
||||
{
|
||||
g_autofree gchar *path = g_strdup_printf ("/proc/%d/statm", st->pid);
|
||||
|
||||
if (-1 == (st->stat_fd = open (path, O_RDONLY)))
|
||||
g_warning ("Failed to access statm for pid %d", st->pid);
|
||||
}
|
||||
else
|
||||
{
|
||||
st->stat_fd = open ("/proc/meminfo", O_RDONLY);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_close (MemStat *st)
|
||||
{
|
||||
g_assert (st != NULL);
|
||||
|
||||
if (st->stat_fd != -1)
|
||||
{
|
||||
close (st->stat_fd);
|
||||
st->stat_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_parse_statm (MemStat *st,
|
||||
gchar *buf)
|
||||
{
|
||||
g_assert (st != NULL);
|
||||
g_assert (buf != NULL);
|
||||
|
||||
sscanf (buf,
|
||||
"%"G_GINT64_FORMAT" "
|
||||
"%"G_GINT64_FORMAT" "
|
||||
"%"G_GINT64_FORMAT" "
|
||||
"%"G_GINT64_FORMAT" "
|
||||
"%*1c "
|
||||
"%"G_GINT64_FORMAT,
|
||||
&st->proc.size,
|
||||
&st->proc.resident,
|
||||
&st->proc.shared,
|
||||
&st->proc.text,
|
||||
&st->proc.data);
|
||||
|
||||
st->proc.used.vdbl = st->proc.size - st->proc.shared - st->proc.text - st->proc.data;
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_parse_meminfo (MemStat *st,
|
||||
gchar *buf)
|
||||
{
|
||||
gchar *bufptr = buf;
|
||||
gchar *save = NULL;
|
||||
|
||||
g_assert (st != NULL);
|
||||
g_assert (buf != NULL);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
goffset off;
|
||||
gchar *key;
|
||||
gchar *value;
|
||||
gchar *unit;
|
||||
gint64 v64;
|
||||
gint64 *v64ptr;
|
||||
|
||||
/* Get the data key name */
|
||||
if (!(key = strtok_r (bufptr, " \n\t:", &save)))
|
||||
break;
|
||||
|
||||
bufptr = NULL;
|
||||
|
||||
/* Offset from self to save value. Stop after getting to
|
||||
* last value we care about.
|
||||
*/
|
||||
if (!(off = GPOINTER_TO_UINT (g_hash_table_lookup (keys, key))))
|
||||
break;
|
||||
|
||||
/* Get the data value */
|
||||
if (!(value = strtok_r (bufptr, " \n\t:", &save)))
|
||||
break;
|
||||
|
||||
/* Parse the numeric value of this column */
|
||||
v64 = g_ascii_strtoll (value, NULL, 10);
|
||||
if ((v64 == G_MININT64 || v64 == G_MAXINT64) && errno == ERANGE)
|
||||
break;
|
||||
|
||||
/* Get the data unit */
|
||||
unit = strtok_r (bufptr, " \n\t:", &save);
|
||||
|
||||
if (g_strcmp0 (unit, "kB") == 0)
|
||||
v64 *= 1024;
|
||||
else if (g_strcmp0 (unit, "mB") == 0)
|
||||
v64 *= 1024 * 1024;
|
||||
|
||||
v64ptr = (gint64 *)(gpointer)(((gchar *)st) + off);
|
||||
|
||||
*v64ptr = v64;
|
||||
}
|
||||
|
||||
/* Create pre-compiled value for used to simplify display */
|
||||
st->sys.used.vdbl = (gdouble)st->sys.total - (gdouble)st->sys.avail;
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_poll (MemStat *st,
|
||||
gchar *stat_buf)
|
||||
{
|
||||
gssize r;
|
||||
|
||||
g_assert (st != NULL);
|
||||
g_assert (st->stat_fd != -1);
|
||||
|
||||
/* Seek to begin of FD to reset the proc read */
|
||||
if ((r = lseek (st->stat_fd, 0, SEEK_SET)) < 0)
|
||||
return;
|
||||
|
||||
/* Now read the updated proc buffer */
|
||||
if ((r = read (st->stat_fd, stat_buf, BUF_SIZE)) < 0)
|
||||
return;
|
||||
|
||||
if (r < BUF_SIZE)
|
||||
stat_buf[r] = '\0';
|
||||
stat_buf[BUF_SIZE-1] = '\0';
|
||||
|
||||
if (st->pid == -1)
|
||||
mem_stat_parse_meminfo (st, stat_buf);
|
||||
else
|
||||
mem_stat_parse_statm (st, stat_buf);
|
||||
}
|
||||
|
||||
static void
|
||||
mem_stat_publish (MemStat *st,
|
||||
SpCaptureWriter *writer,
|
||||
gint64 current_time)
|
||||
{
|
||||
g_assert (st != NULL);
|
||||
g_assert (writer != NULL);
|
||||
|
||||
sp_capture_writer_set_counters (writer,
|
||||
current_time,
|
||||
-1,
|
||||
st->pid,
|
||||
st->counter_ids,
|
||||
st->pid == -1 ? &st->sys.used : &st->proc.used,
|
||||
1);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_memory_source_new:
|
||||
*
|
||||
* Create a new #SpMemorySource.
|
||||
*
|
||||
* Use this source to record basic memory statistics about the system
|
||||
* such as Available, Free, and Total Memory.
|
||||
*
|
||||
* Returns: (transfer full): a newly created #SpMemorySource
|
||||
|
||||
* Since: 3.32
|
||||
*/
|
||||
SpSource *
|
||||
sp_memory_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_MEMORY_SOURCE, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_finalize (GObject *object)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)object;
|
||||
|
||||
if (self->timer_source != 0)
|
||||
{
|
||||
g_source_remove (self->timer_source);
|
||||
self->timer_source = 0;
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->stat_buf, g_free);
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->mem_stats, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_memory_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_class_init (SpMemorySourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_memory_source_finalize;
|
||||
|
||||
keys = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
|
||||
#define ADD_OFFSET(n,o) \
|
||||
g_hash_table_insert (keys, (gchar *)n, GUINT_TO_POINTER (o))
|
||||
ADD_OFFSET ("MemTotal", G_STRUCT_OFFSET (MemStat, sys.total));
|
||||
ADD_OFFSET ("MemFree", G_STRUCT_OFFSET (MemStat, sys.free));
|
||||
ADD_OFFSET ("MemAvailable", G_STRUCT_OFFSET (MemStat, sys.avail));
|
||||
#undef ADD_OFFSET
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_init (SpMemorySource *self)
|
||||
{
|
||||
self->stat_buf = g_malloc (BUF_SIZE);
|
||||
self->mem_stats = g_array_new (FALSE, FALSE, sizeof (MemStat));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
|
||||
g_assert (SP_IS_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
g_assert (self->writer == NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_prepare (SpSource *source)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
/* If no pids are registered, setup a system memory stat */
|
||||
if (self->mem_stats->len == 0)
|
||||
{
|
||||
MemStat st = {0};
|
||||
|
||||
st.pid = -1;
|
||||
st.stat_fd = -1;
|
||||
|
||||
g_array_append_val (self->mem_stats, st);
|
||||
}
|
||||
|
||||
/* Open FDs to all the necessary files in proc. We will re-use
|
||||
* them to avoid re-opening files later.
|
||||
*/
|
||||
for (guint i = 0; i < self->mem_stats->len; i++)
|
||||
{
|
||||
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
|
||||
SpCaptureCounter counters[5];
|
||||
guint base;
|
||||
|
||||
mem_stat_open (st);
|
||||
|
||||
if (st->pid == -1)
|
||||
{
|
||||
base = sp_capture_writer_request_counter (self->writer, 1);
|
||||
|
||||
g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category);
|
||||
g_strlcpy (counters[0].name, "Used", sizeof counters[0].name);
|
||||
g_strlcpy (counters[0].description, "Memory used by system", sizeof counters[0].description);
|
||||
|
||||
counters[0].id = st->counter_ids[0] = base;
|
||||
counters[0].type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
counters[0].value.vdbl = 0;
|
||||
|
||||
sp_capture_writer_define_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
-1,
|
||||
counters,
|
||||
1);
|
||||
}
|
||||
else
|
||||
{
|
||||
base = sp_capture_writer_request_counter (self->writer, 1);
|
||||
|
||||
g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category);
|
||||
g_strlcpy (counters[0].name, "Used", sizeof counters[0].name);
|
||||
g_strlcpy (counters[0].description, "Memory used by process", sizeof counters[0].description);
|
||||
|
||||
counters[0].id = st->counter_ids[0] = base;
|
||||
counters[0].type = SP_CAPTURE_COUNTER_DOUBLE;
|
||||
counters[0].value.vdbl = 0;
|
||||
|
||||
sp_capture_writer_define_counters (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
st->pid,
|
||||
counters,
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
sp_source_emit_ready (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_memory_source_timer_cb (SpMemorySource *self)
|
||||
{
|
||||
gint64 current_time;
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
current_time = sp_clock_get_current_time ();
|
||||
|
||||
for (guint i = 0; i < self->mem_stats->len; i++)
|
||||
{
|
||||
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
|
||||
|
||||
mem_stat_poll (st, self->stat_buf);
|
||||
mem_stat_publish (st, self->writer, current_time);
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_start (SpSource *source)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
|
||||
/* Poll 4x/sec for memory stats */
|
||||
self->timer_source = g_timeout_add_full (G_PRIORITY_HIGH,
|
||||
1000 / 4,
|
||||
(GSourceFunc)sp_memory_source_timer_cb,
|
||||
self,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_stop (SpSource *source)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
|
||||
if (self->timer_source != 0)
|
||||
{
|
||||
g_source_remove (self->timer_source);
|
||||
self->timer_source = 0;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < self->mem_stats->len; i++)
|
||||
{
|
||||
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
|
||||
|
||||
mem_stat_close (st);
|
||||
}
|
||||
|
||||
sp_source_emit_finished (source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_memory_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpMemorySource *self = (SpMemorySource *)source;
|
||||
MemStat st = {0};
|
||||
|
||||
g_assert (SP_IS_MEMORY_SOURCE (self));
|
||||
|
||||
st.pid = pid;
|
||||
st.stat_fd = -1;
|
||||
|
||||
g_array_append_val (self->mem_stats, st);
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_memory_source_set_writer;
|
||||
iface->prepare = sp_memory_source_prepare;
|
||||
iface->start = sp_memory_source_start;
|
||||
iface->stop = sp_memory_source_stop;
|
||||
iface->add_pid = sp_memory_source_add_pid;
|
||||
}
|
||||
33
src/libsysprof/sp-memory-source.h
Normal file
33
src/libsysprof/sp-memory-source.h
Normal file
@ -0,0 +1,33 @@
|
||||
/* sp-memory-source.h
|
||||
*
|
||||
* Copyright 2018 Christian Hergert <chergert@redhat.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_MEMORY_SOURCE (sp_memory_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpMemorySource, sp_memory_source, SP, MEMORY_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_memory_source_new (void);
|
||||
|
||||
G_END_DECLS
|
||||
845
src/libsysprof/sp-perf-counter.c
Normal file
845
src/libsysprof/sp-perf-counter.c
Normal file
@ -0,0 +1,845 @@
|
||||
/* sp-perf-counter.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/>.
|
||||
*/
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <gio/gio.h>
|
||||
#include <gio/gunixfdlist.h>
|
||||
#ifdef ENABLE_POLKIT
|
||||
# include <polkit/polkit.h>
|
||||
#endif
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-perf-counter.h"
|
||||
|
||||
/*
|
||||
* Number of pages to map for the ring buffer. We map one additional buffer
|
||||
* at the beginning for header information to communicate with perf.
|
||||
*/
|
||||
#define N_PAGES 32
|
||||
|
||||
/*
|
||||
* This represents a stream coming to us from perf. All SpPerfCounterInfo
|
||||
* share a single GSource used for watching incoming G_IO_IN requests.
|
||||
* The map is the mmap() zone we are using as a ring buffer for communicating
|
||||
* with perf. The rest is for managing the ring buffer.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
gint fd;
|
||||
gpointer fdtag;
|
||||
struct perf_event_mmap_page *map;
|
||||
guint8 *data;
|
||||
guint64 tail;
|
||||
gint cpu;
|
||||
guint in_callback : 1;
|
||||
} SpPerfCounterInfo;
|
||||
|
||||
struct _SpPerfCounter
|
||||
{
|
||||
volatile gint ref_count;
|
||||
|
||||
/*
|
||||
* If we are should currently be enabled. We allow calling
|
||||
* multiple times and disabling when we reach zero.
|
||||
*/
|
||||
guint enabled;
|
||||
|
||||
/*
|
||||
* Our main context and source for delivering callbacks.
|
||||
*/
|
||||
GMainContext *context;
|
||||
GSource *source;
|
||||
|
||||
/*
|
||||
* An array of SpPerfCounterInfo, indicating all of our open
|
||||
* perf stream.s
|
||||
*/
|
||||
GPtrArray *info;
|
||||
|
||||
/*
|
||||
* The callback to execute for every discovered perf event.
|
||||
*/
|
||||
SpPerfCounterCallback callback;
|
||||
gpointer callback_data;
|
||||
GDestroyNotify callback_data_destroy;
|
||||
|
||||
/*
|
||||
* The number of samples we've recorded.
|
||||
*/
|
||||
guint64 n_samples;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GSource source;
|
||||
SpPerfCounter *counter;
|
||||
} PerfGSource;
|
||||
|
||||
G_DEFINE_BOXED_TYPE (SpPerfCounter,
|
||||
sp_perf_counter,
|
||||
(GBoxedCopyFunc)sp_perf_counter_ref,
|
||||
(GBoxedFreeFunc)sp_perf_counter_unref)
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
static GDBusConnection *shared_conn;
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
perf_gsource_dispatch (GSource *source,
|
||||
GSourceFunc callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
return callback ? callback (user_data) : G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static GSourceFuncs source_funcs = {
|
||||
NULL, NULL, perf_gsource_dispatch, NULL
|
||||
};
|
||||
|
||||
static void
|
||||
sp_perf_counter_info_free (SpPerfCounterInfo *info)
|
||||
{
|
||||
if (info->map)
|
||||
{
|
||||
gsize map_size;
|
||||
|
||||
map_size = N_PAGES * getpagesize () + getpagesize ();
|
||||
munmap (info->map, map_size);
|
||||
|
||||
info->map = NULL;
|
||||
info->data = NULL;
|
||||
}
|
||||
|
||||
if (info->fd != -1)
|
||||
{
|
||||
close (info->fd);
|
||||
info->fd = 0;
|
||||
}
|
||||
|
||||
g_slice_free (SpPerfCounterInfo, info);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_finalize (SpPerfCounter *self)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->ref_count == 0);
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
if (info->fdtag)
|
||||
g_source_remove_unix_fd (self->source, info->fdtag);
|
||||
|
||||
sp_perf_counter_info_free (info);
|
||||
}
|
||||
|
||||
if (self->callback_data_destroy)
|
||||
self->callback_data_destroy (self->callback_data);
|
||||
|
||||
g_clear_pointer (&self->source, g_source_destroy);
|
||||
g_clear_pointer (&self->info, g_ptr_array_unref);
|
||||
g_clear_pointer (&self->context, g_main_context_unref);
|
||||
g_slice_free (SpPerfCounter, self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_unref (SpPerfCounter *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_perf_counter_finalize (self);
|
||||
}
|
||||
|
||||
SpPerfCounter *
|
||||
sp_perf_counter_ref (SpPerfCounter *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;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_flush (SpPerfCounter *self,
|
||||
SpPerfCounterInfo *info)
|
||||
{
|
||||
guint64 head;
|
||||
guint64 tail;
|
||||
guint64 n_bytes = N_PAGES * getpagesize ();
|
||||
guint64 mask = n_bytes - 1;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (info != NULL);
|
||||
|
||||
tail = info->tail;
|
||||
head = info->map->data_head;
|
||||
|
||||
atomic_thread_fence (memory_order_acquire);
|
||||
|
||||
if (head < tail)
|
||||
tail = head;
|
||||
|
||||
while ((head - tail) >= sizeof (struct perf_event_header))
|
||||
{
|
||||
g_autofree guint8 *free_me = NULL;
|
||||
struct perf_event_header *header;
|
||||
guint8 buffer[4096];
|
||||
|
||||
/* Note that:
|
||||
*
|
||||
* - perf events are a multiple of 64 bits
|
||||
* - the perf event header is 64 bits
|
||||
* - the data area is a multiple of 64 bits
|
||||
*
|
||||
* which means there will always be space for one header, which means we
|
||||
* can safely dereference the size field.
|
||||
*/
|
||||
header = (struct perf_event_header *)(gpointer)(info->data + (tail & mask));
|
||||
|
||||
if (header->size > head - tail)
|
||||
{
|
||||
/* The kernel did not generate a complete event.
|
||||
* I don't think that can happen, but we may as well
|
||||
* be paranoid.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
if (info->data + (tail & mask) + header->size > info->data + n_bytes)
|
||||
{
|
||||
gint n_before;
|
||||
gint n_after;
|
||||
guint8 *b;
|
||||
|
||||
if (header->size > sizeof buffer)
|
||||
free_me = b = g_malloc (header->size);
|
||||
else
|
||||
b = buffer;
|
||||
|
||||
n_after = (tail & mask) + header->size - n_bytes;
|
||||
n_before = header->size - n_after;
|
||||
|
||||
memcpy (b, info->data + (tail & mask), n_before);
|
||||
memcpy (b + n_before, info->data, n_after);
|
||||
|
||||
header = (struct perf_event_header *)(gpointer)b;
|
||||
}
|
||||
|
||||
if (header->type == PERF_RECORD_SAMPLE)
|
||||
self->n_samples++;
|
||||
|
||||
if (self->callback != NULL)
|
||||
{
|
||||
info->in_callback = TRUE;
|
||||
self->callback ((SpPerfCounterEvent *)header, info->cpu, self->callback_data);
|
||||
info->in_callback = FALSE;
|
||||
}
|
||||
|
||||
tail += header->size;
|
||||
}
|
||||
|
||||
info->tail = tail;
|
||||
|
||||
atomic_thread_fence (memory_order_seq_cst);
|
||||
info->map->data_tail = tail;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_counter_dispatch (gpointer user_data)
|
||||
{
|
||||
SpPerfCounter *self = user_data;
|
||||
guint i;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (self->info != NULL);
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
sp_perf_counter_flush (self, info);
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_enable_info (SpPerfCounter *self,
|
||||
SpPerfCounterInfo *info)
|
||||
{
|
||||
g_assert (self != NULL);
|
||||
g_assert (info != NULL);
|
||||
|
||||
if (0 != ioctl (info->fd, PERF_EVENT_IOC_ENABLE))
|
||||
g_warning ("Failed to enable counters");
|
||||
|
||||
g_source_modify_unix_fd (self->source, info->fdtag, G_IO_IN);
|
||||
}
|
||||
|
||||
SpPerfCounter *
|
||||
sp_perf_counter_new (GMainContext *context)
|
||||
{
|
||||
SpPerfCounter *ret;
|
||||
|
||||
if (context == NULL)
|
||||
context = g_main_context_default ();
|
||||
|
||||
ret = g_slice_new0 (SpPerfCounter);
|
||||
ret->ref_count = 1;
|
||||
ret->info = g_ptr_array_new ();
|
||||
ret->context = g_main_context_ref (context);
|
||||
ret->source = g_source_new (&source_funcs, sizeof (PerfGSource));
|
||||
|
||||
((PerfGSource *)ret->source)->counter = ret;
|
||||
g_source_set_callback (ret->source, sp_perf_counter_dispatch, ret, NULL);
|
||||
g_source_set_name (ret->source, "[perf]");
|
||||
g_source_attach (ret->source, context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_close (SpPerfCounter *self,
|
||||
gint fd)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (fd != -1);
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
if (info->fd == fd)
|
||||
{
|
||||
g_ptr_array_remove_index (self->info, i);
|
||||
if (self->source)
|
||||
g_source_remove_unix_fd (self->source, info->fdtag);
|
||||
sp_perf_counter_info_free (info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_add_info (SpPerfCounter *self,
|
||||
int fd,
|
||||
int cpu)
|
||||
{
|
||||
SpPerfCounterInfo *info;
|
||||
guint8 *map;
|
||||
gsize map_size;
|
||||
|
||||
g_assert (self != NULL);
|
||||
g_assert (fd != -1);
|
||||
|
||||
map_size = N_PAGES * getpagesize () + getpagesize ();
|
||||
map = mmap (NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
|
||||
if (map == MAP_FAILED)
|
||||
{
|
||||
close (fd);
|
||||
return;
|
||||
}
|
||||
|
||||
info = g_slice_new0 (SpPerfCounterInfo);
|
||||
info->fd = fd;
|
||||
info->map = (gpointer)map;
|
||||
info->data = map + getpagesize ();
|
||||
info->tail = 0;
|
||||
info->cpu = cpu;
|
||||
|
||||
g_ptr_array_add (self->info, info);
|
||||
|
||||
info->fdtag = g_source_add_unix_fd (self->source, info->fd, G_IO_ERR);
|
||||
|
||||
if (self->enabled)
|
||||
sp_perf_counter_enable_info (self, info);
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_take_fd (SpPerfCounter *self,
|
||||
int fd)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (fd > -1);
|
||||
|
||||
sp_perf_counter_add_info (self, fd, -1);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
static GDBusProxy *
|
||||
get_proxy (void)
|
||||
{
|
||||
static GDBusProxy *proxy;
|
||||
|
||||
if (proxy != NULL)
|
||||
return g_object_ref (proxy);
|
||||
|
||||
if (shared_conn == NULL)
|
||||
shared_conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL);
|
||||
|
||||
if (shared_conn == NULL)
|
||||
return NULL;
|
||||
|
||||
proxy = g_dbus_proxy_new_sync (shared_conn,
|
||||
(G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION),
|
||||
NULL,
|
||||
"org.gnome.Sysprof2",
|
||||
"/org/gnome/Sysprof2",
|
||||
"org.gnome.Sysprof2",
|
||||
NULL, NULL);
|
||||
|
||||
if (proxy != NULL)
|
||||
{
|
||||
g_object_add_weak_pointer (G_OBJECT (proxy), (gpointer *)&proxy);
|
||||
return g_object_ref (proxy);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
authorize_proxy (GDBusProxy *proxy)
|
||||
{
|
||||
PolkitSubject *subject = NULL;
|
||||
GPermission *permission = NULL;
|
||||
GDBusConnection *conn;
|
||||
const gchar *name;
|
||||
|
||||
g_assert (G_IS_DBUS_PROXY (proxy));
|
||||
|
||||
conn = g_dbus_proxy_get_connection (proxy);
|
||||
if (conn == NULL)
|
||||
goto failure;
|
||||
|
||||
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.perf-event-open", 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);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static GDBusProxy *
|
||||
get_authorized_proxy (void)
|
||||
{
|
||||
g_autoptr(GDBusProxy) proxy = NULL;
|
||||
|
||||
proxy = get_proxy ();
|
||||
if (proxy != NULL && authorize_proxy (proxy))
|
||||
return g_steal_pointer (&proxy);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_ping_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GDBusProxy *proxy = (GDBusProxy *)object;
|
||||
g_autoptr(GTask) task = user_data;
|
||||
g_autoptr(GVariant) ret = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_DBUS_PROXY (proxy));
|
||||
g_assert (G_IS_TASK (task));
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
|
||||
ret = g_dbus_proxy_call_finish (proxy, result, &error);
|
||||
|
||||
if (error != NULL)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
g_task_return_boolean (task, TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_acquire_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = user_data;
|
||||
GPermission *permission = (GPermission *)object;
|
||||
g_autoptr(GDBusProxy) proxy = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_PERMISSION (permission));
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
g_assert (G_IS_TASK (task));
|
||||
|
||||
if (!g_permission_acquire_finish (permission, result, &error))
|
||||
{
|
||||
g_task_return_error (task, error);
|
||||
return;
|
||||
}
|
||||
|
||||
proxy = get_proxy ();
|
||||
|
||||
if (proxy == NULL)
|
||||
{
|
||||
/* We don't connect at startup, shouldn't happen */
|
||||
g_task_return_new_error (task,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
"Failed to create proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
g_dbus_proxy_call (proxy,
|
||||
"org.freedesktop.DBus.Peer.Ping",
|
||||
NULL,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
5000,
|
||||
g_task_get_cancellable (task),
|
||||
sp_perf_counter_ping_cb,
|
||||
g_object_ref (task));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_permission_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = user_data;
|
||||
GPermission *permission;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
g_assert (G_IS_TASK (task));
|
||||
|
||||
if (NULL == (permission = polkit_permission_new_finish (result, &error)))
|
||||
{
|
||||
g_task_return_error (task, error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_permission_acquire_async (permission,
|
||||
g_task_get_cancellable (task),
|
||||
sp_perf_counter_acquire_cb,
|
||||
g_object_ref (task));
|
||||
|
||||
g_object_unref (permission);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_counter_get_bus_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GDBusConnection) bus = NULL;
|
||||
g_autoptr(GTask) task = user_data;
|
||||
PolkitSubject *subject = NULL;
|
||||
const gchar *name;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
g_assert (G_IS_TASK (task));
|
||||
|
||||
if (NULL == (bus = g_bus_get_finish (result, &error)))
|
||||
{
|
||||
g_task_return_error (task, error);
|
||||
return;
|
||||
}
|
||||
|
||||
shared_conn = g_object_ref (bus);
|
||||
|
||||
name = g_dbus_connection_get_unique_name (bus);
|
||||
subject = polkit_system_bus_name_new (name);
|
||||
|
||||
polkit_permission_new ("org.gnome.sysprof2.perf-event-open",
|
||||
subject,
|
||||
g_task_get_cancellable (task),
|
||||
sp_perf_counter_permission_cb,
|
||||
g_object_ref (task));
|
||||
|
||||
g_object_unref (subject);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
sp_perf_counter_authorize_async (GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
task = g_task_new (NULL, cancellable, callback, user_data);
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
g_bus_get (G_BUS_TYPE_SYSTEM,
|
||||
cancellable,
|
||||
sp_perf_counter_get_bus_cb,
|
||||
g_object_ref (task));
|
||||
#else
|
||||
g_task_return_new_error (task,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_NOT_SUPPORTED,
|
||||
"Sysprofd is not supported in current configuration");
|
||||
#endif
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_perf_counter_authorize_finish (GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_assert (G_IS_TASK (result));
|
||||
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
}
|
||||
|
||||
gint
|
||||
sp_perf_counter_open (SpPerfCounter *self,
|
||||
struct perf_event_attr *attr,
|
||||
GPid pid,
|
||||
gint cpu,
|
||||
gint group_fd,
|
||||
gulong flags)
|
||||
{
|
||||
#ifdef ENABLE_POLKIT
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autoptr(GDBusProxy) proxy = NULL;
|
||||
g_autoptr(GUnixFDList) fdlist = NULL;
|
||||
g_autoptr(GVariant) res = NULL;
|
||||
g_autoptr(GVariant) params = NULL;
|
||||
gint handle = -1;
|
||||
#endif
|
||||
gint ret = -1;
|
||||
|
||||
g_return_val_if_fail (self != NULL, -1);
|
||||
g_return_val_if_fail (attr != NULL, -1);
|
||||
|
||||
/*
|
||||
* First, we try to run the syscall locally, since we should avoid the
|
||||
* polkit request unless we have to use it for elevated privileges.
|
||||
*/
|
||||
if (-1 != (ret = syscall (__NR_perf_event_open, attr, pid, cpu, group_fd, flags)))
|
||||
{
|
||||
sp_perf_counter_take_fd (self, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_POLKIT
|
||||
params = g_variant_new_parsed (
|
||||
"("
|
||||
"["
|
||||
"{'comm', <%b>},"
|
||||
#ifdef HAVE_PERF_CLOCKID
|
||||
"{'clockid', <%i>},"
|
||||
"{'use_clockid', <%b>},"
|
||||
#endif
|
||||
"{'config', <%t>},"
|
||||
"{'disabled', <%b>},"
|
||||
"{'exclude_idle', <%b>},"
|
||||
"{'mmap', <%b>},"
|
||||
"{'wakeup_events', <%u>},"
|
||||
"{'sample_id_all', <%b>},"
|
||||
"{'sample_period', <%t>},"
|
||||
"{'sample_type', <%t>},"
|
||||
"{'task', <%b>},"
|
||||
"{'type', <%u>}"
|
||||
"],"
|
||||
"%i,"
|
||||
"%i,"
|
||||
"%t"
|
||||
")",
|
||||
(gboolean)!!attr->comm,
|
||||
#ifdef HAVE_PERF_CLOCKID
|
||||
(gint32)attr->clockid,
|
||||
(gboolean)!!attr->use_clockid,
|
||||
#endif
|
||||
(guint64)attr->config,
|
||||
(gboolean)!!attr->disabled,
|
||||
(gboolean)!!attr->exclude_idle,
|
||||
(gboolean)!!attr->mmap,
|
||||
(guint32)attr->wakeup_events,
|
||||
(gboolean)!!attr->sample_id_all,
|
||||
(guint64)attr->sample_period,
|
||||
(guint64)attr->sample_type,
|
||||
(gboolean)!!attr->task,
|
||||
(guint32)attr->type,
|
||||
(gint32)pid,
|
||||
(gint32)cpu,
|
||||
(guint64)flags);
|
||||
|
||||
params = g_variant_ref_sink (params);
|
||||
|
||||
if (NULL == (proxy = get_authorized_proxy ()))
|
||||
{
|
||||
errno = EPERM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = g_dbus_proxy_call_with_unix_fd_list_sync (proxy,
|
||||
"PerfEventOpen",
|
||||
params,
|
||||
G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION,
|
||||
60000,
|
||||
NULL,
|
||||
&fdlist,
|
||||
NULL,
|
||||
&error);
|
||||
|
||||
if (res == NULL)
|
||||
{
|
||||
g_autofree gchar *str = g_variant_print (params, TRUE);
|
||||
|
||||
g_warning ("PerfEventOpen: %s: %s", error->message, str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_variant_is_of_type (res, (const GVariantType *)"(h)"))
|
||||
{
|
||||
g_warning ("Received something other than a handle");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fdlist == NULL)
|
||||
{
|
||||
g_warning ("Failed to receive fdlist");
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_variant_get (res, "(h)", &handle);
|
||||
|
||||
if (-1 == (ret = g_unix_fd_list_get (fdlist, handle, &error)))
|
||||
{
|
||||
g_warning ("%s", error->message);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sp_perf_counter_take_fd (self, ret);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_set_callback (SpPerfCounter *self,
|
||||
SpPerfCounterCallback callback,
|
||||
gpointer callback_data,
|
||||
GDestroyNotify callback_data_destroy)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (self->callback_data_destroy)
|
||||
self->callback_data_destroy (self->callback_data);
|
||||
|
||||
self->callback = callback;
|
||||
self->callback_data = callback_data;
|
||||
self->callback_data_destroy = callback_data_destroy;
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_enable (SpPerfCounter *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (g_atomic_int_add (&self->enabled, 1) == 0)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
sp_perf_counter_enable_info (self, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_counter_disable (SpPerfCounter *self)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (g_atomic_int_dec_and_test (&self->enabled))
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < self->info->len; i++)
|
||||
{
|
||||
SpPerfCounterInfo *info = g_ptr_array_index (self->info, i);
|
||||
|
||||
if (0 != ioctl (info->fd, PERF_EVENT_IOC_DISABLE))
|
||||
g_warning ("Failed to disable counters");
|
||||
|
||||
if (!info->in_callback)
|
||||
sp_perf_counter_flush (self, info);
|
||||
|
||||
g_source_modify_unix_fd (self->source, info->fdtag, G_IO_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
154
src/libsysprof/sp-perf-counter.h
Normal file
154
src/libsysprof/sp-perf-counter.h
Normal file
@ -0,0 +1,154 @@
|
||||
/* sp-perf-counter.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_PERF_COUNTER_H
|
||||
#define SP_PERF_COUNTER_H
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <linux/perf_event.h>
|
||||
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* Structs representing the layouts of perf records returned by the
|
||||
* kernel.
|
||||
*
|
||||
* perf returns variable-layout structs based on the
|
||||
* perf_event_sample_format selectors in perf_event_attr.sample_type.
|
||||
* These structs are the particular layouts that sysprof requests.
|
||||
*/
|
||||
|
||||
#define SP_TYPE_PERF_COUNTER (sp_perf_counter_get_type())
|
||||
|
||||
typedef struct _SpPerfCounter SpPerfCounter;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint32 pid;
|
||||
guint32 ppid;
|
||||
guint32 tid;
|
||||
guint32 ptid;
|
||||
guint64 time;
|
||||
} SpPerfCounterEventFork;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint32 pid;
|
||||
guint32 tid;
|
||||
gchar comm[0];
|
||||
} SpPerfCounterEventComm;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint32 pid;
|
||||
guint32 ppid;
|
||||
guint32 tid;
|
||||
guint32 ptid;
|
||||
guint64 time;
|
||||
} SpPerfCounterEventExit;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint32 pid;
|
||||
guint32 tid;
|
||||
guint64 addr;
|
||||
guint64 len;
|
||||
guint64 pgoff;
|
||||
char filename[0];
|
||||
} SpPerfCounterEventMmap;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint64 identifier;
|
||||
guint64 ip;
|
||||
guint32 pid;
|
||||
guint32 tid;
|
||||
guint64 time;
|
||||
guint64 n_ips;
|
||||
guint64 ips[0];
|
||||
} SpPerfCounterEventCallchain;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint64 identifier;
|
||||
guint64 ip;
|
||||
guint32 pid;
|
||||
guint32 tid;
|
||||
guint64 time;
|
||||
guint32 raw_size;
|
||||
guchar raw[];
|
||||
} SpPerfCounterEventTracepoint;
|
||||
|
||||
typedef union
|
||||
{
|
||||
struct perf_event_header header;
|
||||
guint8 raw[0];
|
||||
SpPerfCounterEventFork fork;
|
||||
SpPerfCounterEventComm comm;
|
||||
SpPerfCounterEventExit exit;
|
||||
SpPerfCounterEventMmap mmap;
|
||||
SpPerfCounterEventCallchain callchain;
|
||||
SpPerfCounterEventTracepoint tracepoint;
|
||||
} SpPerfCounterEvent;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
typedef void (*SpPerfCounterCallback) (SpPerfCounterEvent *event,
|
||||
guint cpu,
|
||||
gpointer user_data);
|
||||
|
||||
void sp_perf_counter_authorize_async (GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
gboolean sp_perf_counter_authorize_finish (GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
GType sp_perf_counter_get_type (void);
|
||||
SpPerfCounter *sp_perf_counter_new (GMainContext *context);
|
||||
void sp_perf_counter_set_callback (SpPerfCounter *self,
|
||||
SpPerfCounterCallback callback,
|
||||
gpointer callback_data,
|
||||
GDestroyNotify callback_data_destroy);
|
||||
void sp_perf_counter_add_pid (SpPerfCounter *self,
|
||||
GPid pid);
|
||||
gint sp_perf_counter_open (SpPerfCounter *self,
|
||||
struct perf_event_attr *attr,
|
||||
GPid pid,
|
||||
gint cpu,
|
||||
gint group_fd,
|
||||
gulong flags);
|
||||
void sp_perf_counter_take_fd (SpPerfCounter *self,
|
||||
int fd);
|
||||
void sp_perf_counter_enable (SpPerfCounter *self);
|
||||
void sp_perf_counter_disable (SpPerfCounter *self);
|
||||
void sp_perf_counter_close (SpPerfCounter *self,
|
||||
gint fd);
|
||||
SpPerfCounter *sp_perf_counter_ref (SpPerfCounter *self);
|
||||
void sp_perf_counter_unref (SpPerfCounter *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PERF_COUNTER_H */
|
||||
935
src/libsysprof/sp-perf-source.c
Normal file
935
src/libsysprof/sp-perf-source.c
Normal file
@ -0,0 +1,935 @@
|
||||
/* sp-perf-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/>.
|
||||
*/
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib/gi18n.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sp-clock.h"
|
||||
#include "sp-line-reader.h"
|
||||
#include "sp-perf-counter.h"
|
||||
#include "sp-perf-source.h"
|
||||
|
||||
#define N_WAKEUP_EVENTS 149
|
||||
|
||||
/* Identifiers for the various tracepoints we might watch for */
|
||||
enum SpTracepoint {
|
||||
DRM_VBLANK,
|
||||
DRM_I915_BEGIN,
|
||||
DRM_I915_END,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
enum SpTracepoint tp;
|
||||
const char *path;
|
||||
const char **fields;
|
||||
} SpOptionalTracepoint;
|
||||
|
||||
/* Global list of the optional tracepoints we might want to watch. */
|
||||
static const SpOptionalTracepoint optional_tracepoints[] = {
|
||||
/* This event fires just after the vblank IRQ handler starts.
|
||||
*
|
||||
* Note that on many platforms when nothing is waiting for vblank
|
||||
* (no pageflips have happened recently, no rendering is
|
||||
* synchronizing to vblank), the vblank IRQ will get masked off and
|
||||
* the event won't show up in the timeline.
|
||||
*
|
||||
* Also note that when we're in watch-a-single-process mode, we
|
||||
* won't get the event since it comes in on an IRQ handler, not for
|
||||
* our pid.
|
||||
*/
|
||||
{ DRM_VBLANK, "drm/drm_vblank_event",
|
||||
(const char *[]){ "crtc", "seq", NULL } },
|
||||
|
||||
/* I915 GPU execution.
|
||||
*
|
||||
* These are the wrong events to be watching. We need to use the
|
||||
* ones under CONFIG_DRM_I915_LOW_LEVEL_TRACEPOINTS instead.
|
||||
*/
|
||||
#if 0
|
||||
{ DRM_I915_BEGIN, "i915/i915_gem_request_add",
|
||||
(const char *[]){ "ctx", "ring", "seqno", NULL } },
|
||||
{ DRM_I915_END, "i915/i915_gem_request_retire",
|
||||
(const char *[]){ "ctx", "ring", "seqno", NULL } },
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Struct describing tracepoint events.
|
||||
*
|
||||
* This should be extended with some sort of union for the describing
|
||||
* the locations of the relevant fields within the _RAW section of the
|
||||
* struct perf_event, so we can pick out things like the vblank CRTC
|
||||
* number and MSC.
|
||||
*/
|
||||
typedef struct {
|
||||
enum SpTracepoint tp;
|
||||
gsize field_offsets[0];
|
||||
} SpTracepointDesc;
|
||||
|
||||
struct _SpPerfSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
SpCaptureWriter *writer;
|
||||
SpPerfCounter *counter;
|
||||
GHashTable *pids;
|
||||
|
||||
/* Mapping from perf sample identifiers to SpTracepointDesc. */
|
||||
GHashTable *tracepoint_event_ids;
|
||||
|
||||
guint running : 1;
|
||||
guint is_ready : 1;
|
||||
};
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpPerfSource, sp_perf_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
enum {
|
||||
TARGET_EXITED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint signals [N_SIGNALS];
|
||||
|
||||
static void
|
||||
sp_perf_source_real_target_exited (SpPerfSource *self)
|
||||
{
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
sp_source_emit_finished (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_finalize (GObject *object)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)object;
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->counter, sp_perf_counter_unref);
|
||||
g_clear_pointer (&self->pids, g_hash_table_unref);
|
||||
g_clear_pointer (&self->tracepoint_event_ids, g_hash_table_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_perf_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_class_init (SpPerfSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_perf_source_finalize;
|
||||
|
||||
signals [TARGET_EXITED] =
|
||||
g_signal_new_class_handler ("target-exited",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_CALLBACK (sp_perf_source_real_target_exited),
|
||||
NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_init (SpPerfSource *self)
|
||||
{
|
||||
self->pids = g_hash_table_new (NULL, NULL);
|
||||
self->tracepoint_event_ids = g_hash_table_new (NULL, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_emit_exited (gpointer data)
|
||||
{
|
||||
g_autoptr(SpPerfSource) self = data;
|
||||
|
||||
g_signal_emit (self, signals [TARGET_EXITED], 0);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_handle_tracepoint (SpPerfSource *self,
|
||||
gint cpu,
|
||||
const SpPerfCounterEventTracepoint *sample,
|
||||
SpTracepointDesc *tp_desc)
|
||||
{
|
||||
gchar *message = NULL;
|
||||
|
||||
/* Note that field_offsets[] correspond to the
|
||||
* SpOptionalTracepoint->fields[] strings. Yes, this is gross.
|
||||
*/
|
||||
switch (tp_desc->tp)
|
||||
{
|
||||
case DRM_VBLANK:
|
||||
message = g_strdup_printf ("crtc=%d, seq=%u",
|
||||
*(gint *)(sample->raw +
|
||||
tp_desc->field_offsets[0]),
|
||||
*(guint *)(sample->raw +
|
||||
tp_desc->field_offsets[1]));
|
||||
|
||||
sp_capture_writer_add_mark (self->writer,
|
||||
sample->time,
|
||||
cpu,
|
||||
sample->pid,
|
||||
0,
|
||||
"drm",
|
||||
"vblank",
|
||||
message);
|
||||
break;
|
||||
|
||||
case DRM_I915_BEGIN:
|
||||
case DRM_I915_END:
|
||||
message = g_strdup_printf ("ctx=%u, ring=%u, seqno=%u",
|
||||
*(guint *)(sample->raw +
|
||||
tp_desc->field_offsets[0]),
|
||||
*(guint *)(sample->raw +
|
||||
tp_desc->field_offsets[1]),
|
||||
*(guint *)(sample->raw +
|
||||
tp_desc->field_offsets[2]));
|
||||
|
||||
sp_capture_writer_add_mark (self->writer,
|
||||
sample->time,
|
||||
cpu,
|
||||
sample->pid,
|
||||
0,
|
||||
"drm",
|
||||
(tp_desc->tp == DRM_I915_BEGIN ?
|
||||
"i915 gpu begin" : "i915 gpu end"),
|
||||
message);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
g_free (message);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_handle_callchain (SpPerfSource *self,
|
||||
gint cpu,
|
||||
const SpPerfCounterEventCallchain *sample)
|
||||
{
|
||||
const guint64 *ips;
|
||||
gint n_ips;
|
||||
guint64 trace[3];
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
g_assert (sample != NULL);
|
||||
|
||||
ips = sample->ips;
|
||||
n_ips = sample->n_ips;
|
||||
|
||||
if (n_ips == 0)
|
||||
{
|
||||
if (sample->header.misc & PERF_RECORD_MISC_KERNEL)
|
||||
{
|
||||
trace[0] = PERF_CONTEXT_KERNEL;
|
||||
trace[1] = sample->ip;
|
||||
trace[2] = PERF_CONTEXT_USER;
|
||||
|
||||
ips = trace;
|
||||
n_ips = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
trace[0] = PERF_CONTEXT_USER;
|
||||
trace[1] = sample->ip;
|
||||
|
||||
ips = trace;
|
||||
n_ips = 2;
|
||||
}
|
||||
}
|
||||
|
||||
sp_capture_writer_add_sample (self->writer,
|
||||
sample->time,
|
||||
cpu,
|
||||
sample->pid,
|
||||
ips,
|
||||
n_ips);
|
||||
}
|
||||
|
||||
static inline void
|
||||
realign (gsize *pos,
|
||||
gsize align)
|
||||
{
|
||||
*pos = (*pos + align - 1) & ~(align - 1);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_handle_event (SpPerfCounterEvent *event,
|
||||
guint cpu,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpPerfSource *self = user_data;
|
||||
SpTracepointDesc *tp_desc;
|
||||
gsize offset;
|
||||
gint64 time;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
g_assert (event != NULL);
|
||||
|
||||
switch (event->header.type)
|
||||
{
|
||||
case PERF_RECORD_COMM:
|
||||
offset = strlen (event->comm.comm) + 1;
|
||||
realign (&offset, sizeof (guint64));
|
||||
offset += sizeof (GPid) + sizeof (GPid);
|
||||
memcpy (&time, event->comm.comm + offset, sizeof time);
|
||||
|
||||
sp_capture_writer_add_process (self->writer,
|
||||
time,
|
||||
cpu,
|
||||
event->comm.pid,
|
||||
event->comm.comm);
|
||||
|
||||
break;
|
||||
|
||||
case PERF_RECORD_EXIT:
|
||||
sp_capture_writer_add_exit (self->writer,
|
||||
event->exit.time,
|
||||
cpu,
|
||||
event->exit.pid);
|
||||
|
||||
if (g_hash_table_contains (self->pids, GINT_TO_POINTER (event->exit.pid)))
|
||||
{
|
||||
g_hash_table_remove (self->pids, GINT_TO_POINTER (event->exit.pid));
|
||||
|
||||
if (self->running && (g_hash_table_size (self->pids) == 0))
|
||||
{
|
||||
self->running = FALSE;
|
||||
sp_perf_counter_disable (self->counter);
|
||||
g_timeout_add (0, do_emit_exited, g_object_ref (self));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PERF_RECORD_FORK:
|
||||
sp_capture_writer_add_fork (self->writer,
|
||||
event->fork.time,
|
||||
cpu,
|
||||
event->fork.ppid,
|
||||
event->fork.pid);
|
||||
|
||||
/*
|
||||
* TODO: We should add support for "follow fork" of the GPid if we are
|
||||
* targetting it.
|
||||
*/
|
||||
|
||||
break;
|
||||
|
||||
case PERF_RECORD_LOST:
|
||||
break;
|
||||
|
||||
case PERF_RECORD_MMAP:
|
||||
offset = strlen (event->mmap.filename) + 1;
|
||||
realign (&offset, sizeof (guint64));
|
||||
offset += sizeof (GPid) + sizeof (GPid);
|
||||
memcpy (&time, event->mmap.filename + offset, sizeof time);
|
||||
|
||||
sp_capture_writer_add_map (self->writer,
|
||||
time,
|
||||
cpu,
|
||||
event->mmap.pid,
|
||||
event->mmap.addr,
|
||||
event->mmap.addr + event->mmap.len,
|
||||
event->mmap.pgoff,
|
||||
0,
|
||||
event->mmap.filename);
|
||||
|
||||
break;
|
||||
|
||||
case PERF_RECORD_READ:
|
||||
break;
|
||||
|
||||
case PERF_RECORD_SAMPLE:
|
||||
/* We don't capture IPs with tracepoints, and get _RAW data
|
||||
* instead. Handle them separately.
|
||||
*/
|
||||
g_assert (&event->callchain.identifier == &event->tracepoint.identifier);
|
||||
tp_desc = g_hash_table_lookup (self->tracepoint_event_ids,
|
||||
GINT_TO_POINTER (event->callchain.identifier));
|
||||
if (tp_desc)
|
||||
{
|
||||
sp_perf_source_handle_tracepoint (self, cpu, &event->tracepoint,
|
||||
tp_desc);
|
||||
}
|
||||
else
|
||||
{
|
||||
sp_perf_source_handle_callchain (self, cpu, &event->callchain);
|
||||
}
|
||||
break;
|
||||
|
||||
case PERF_RECORD_THROTTLE:
|
||||
case PERF_RECORD_UNTHROTTLE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_get_tracepoint_config (const char *path, gint64 *config)
|
||||
{
|
||||
gchar *filename = NULL;
|
||||
gchar *contents;
|
||||
size_t len;
|
||||
|
||||
filename = g_strdup_printf ("/sys/kernel/debug/tracing/events/%s/id", path);
|
||||
if (!filename)
|
||||
return FALSE;
|
||||
|
||||
if (!g_file_get_contents (filename, &contents, &len, NULL))
|
||||
{
|
||||
g_free (filename);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_free(filename);
|
||||
|
||||
*config = strtoull(contents, NULL, 0);
|
||||
|
||||
g_free (contents);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_get_tracepoint_fields (SpTracepointDesc *tp_desc,
|
||||
const SpOptionalTracepoint *optional_tp,
|
||||
GError **error)
|
||||
{
|
||||
gchar *filename = NULL;
|
||||
gchar *contents;
|
||||
size_t len;
|
||||
gint i;
|
||||
filename = g_strdup_printf ("/sys/kernel/debug/tracing/events/%s/format",
|
||||
optional_tp->path);
|
||||
if (!filename)
|
||||
return FALSE;
|
||||
|
||||
if (!g_file_get_contents (filename, &contents, &len, NULL))
|
||||
{
|
||||
g_free (filename);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_free (filename);
|
||||
|
||||
/* Look up our fields. Some example strings:
|
||||
*
|
||||
* field:unsigned short common_type; offset:0; size:2; signed:0;
|
||||
* field:int crtc; offset:8; size:4; signed:1;
|
||||
* field:unsigned int seq; offset:12; size:4; signed:0;
|
||||
*/
|
||||
for (i = 0; optional_tp->fields[i] != NULL; i++)
|
||||
{
|
||||
gchar *pattern = g_strdup_printf ("%s;\toffset:", optional_tp->fields[i]);
|
||||
gchar *match;
|
||||
gint64 offset;
|
||||
|
||||
match = strstr (contents, pattern);
|
||||
if (!match)
|
||||
{
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
_("Sysprof failed to find field “%s”."),
|
||||
optional_tp->fields[i]);
|
||||
g_free (contents);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
offset = g_ascii_strtoll (match + strlen (pattern),
|
||||
NULL, 0);
|
||||
if (offset == G_MININT64 && errno != 0)
|
||||
{
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
_("Sysprof failed to parse offset for “%s”."),
|
||||
optional_tp->fields[i]);
|
||||
g_free (contents);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
tp_desc->field_offsets[i] = offset;
|
||||
g_free (pattern);
|
||||
}
|
||||
|
||||
g_free (contents);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Adds a perf tracepoint event, if it's available.
|
||||
*
|
||||
* These are kernel tracepoints that we want to include in our capture
|
||||
* when present, but may be kernel version or driver-specific.
|
||||
*/
|
||||
static void
|
||||
sp_perf_source_add_optional_tracepoint (SpPerfSource *self,
|
||||
GPid pid,
|
||||
gint cpu,
|
||||
const SpOptionalTracepoint *optional_tracepoint,
|
||||
GError **error)
|
||||
{
|
||||
struct perf_event_attr attr = { 0 };
|
||||
SpTracepointDesc *tp_desc;
|
||||
gulong flags = 0;
|
||||
gint fd;
|
||||
gint64 config;
|
||||
gint64 id;
|
||||
int ret;
|
||||
gint num_fields;
|
||||
|
||||
if (!sp_perf_get_tracepoint_config(optional_tracepoint->path, &config))
|
||||
return;
|
||||
|
||||
attr.type = PERF_TYPE_TRACEPOINT;
|
||||
attr.sample_type = PERF_SAMPLE_RAW
|
||||
| PERF_SAMPLE_IP
|
||||
| PERF_SAMPLE_TID
|
||||
| PERF_SAMPLE_IDENTIFIER
|
||||
| PERF_SAMPLE_RAW
|
||||
| PERF_SAMPLE_TIME;
|
||||
attr.config = config;
|
||||
attr.sample_period = 1;
|
||||
|
||||
#ifdef HAVE_PERF_CLOCKID
|
||||
attr.clockid = sp_clock;
|
||||
attr.use_clockid = 1;
|
||||
#endif
|
||||
|
||||
attr.size = sizeof attr;
|
||||
|
||||
fd = sp_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags);
|
||||
|
||||
ret = ioctl (fd, PERF_EVENT_IOC_ID, &id);
|
||||
if (ret != 0)
|
||||
{
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
_("Sysprof failed to get perf_event ID."));
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
/* The fields list is NULL-terminated, count how many are there. */
|
||||
for (num_fields = 0; optional_tracepoint->fields[num_fields]; num_fields++)
|
||||
;
|
||||
|
||||
tp_desc = g_malloc (sizeof (*tp_desc) +
|
||||
sizeof(*tp_desc->field_offsets) * num_fields);
|
||||
if (!tp_desc)
|
||||
{
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
tp_desc->tp = optional_tracepoint->tp;
|
||||
|
||||
if (!sp_perf_get_tracepoint_fields (tp_desc, optional_tracepoint, error))
|
||||
{
|
||||
free(tp_desc);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Here's where we should inspect the /format file to determine how
|
||||
* to pick fields out of the _RAW data.
|
||||
*/
|
||||
|
||||
/* We're truncating the event ID from 64b to 32 to fit in the hash.
|
||||
* The event IDs start from 0 at boot, so meh.
|
||||
*/
|
||||
g_assert (id <= 0xffffffff);
|
||||
g_hash_table_insert (self->tracepoint_event_ids,
|
||||
GINT_TO_POINTER (id), tp_desc);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_source_start_pid (SpPerfSource *self,
|
||||
GPid pid,
|
||||
GError **error)
|
||||
{
|
||||
struct perf_event_attr attr = { 0 };
|
||||
gulong flags = 0;
|
||||
gint ncpu = g_get_num_processors ();
|
||||
gint cpu = 0;
|
||||
gint fd;
|
||||
gint i;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
attr.sample_type = PERF_SAMPLE_IP
|
||||
| PERF_SAMPLE_TID
|
||||
| PERF_SAMPLE_IDENTIFIER
|
||||
| PERF_SAMPLE_CALLCHAIN
|
||||
| PERF_SAMPLE_TIME;
|
||||
attr.wakeup_events = N_WAKEUP_EVENTS;
|
||||
attr.disabled = TRUE;
|
||||
attr.mmap = 1;
|
||||
attr.comm = 1;
|
||||
attr.task = 1;
|
||||
attr.exclude_idle = 1;
|
||||
attr.sample_id_all = 1;
|
||||
|
||||
#ifdef HAVE_PERF_CLOCKID
|
||||
attr.clockid = sp_clock;
|
||||
attr.use_clockid = 1;
|
||||
#endif
|
||||
|
||||
attr.size = sizeof attr;
|
||||
|
||||
if (pid != -1)
|
||||
{
|
||||
ncpu = 0;
|
||||
cpu = -1;
|
||||
}
|
||||
|
||||
/* Perf won't let us capture on all CPUs on all pids, so we have to
|
||||
* loop over CPUs if we're not just watching a single pid.
|
||||
*/
|
||||
for (; cpu < ncpu; cpu++)
|
||||
{
|
||||
attr.type = PERF_TYPE_HARDWARE;
|
||||
attr.config = PERF_COUNT_HW_CPU_CYCLES;
|
||||
attr.sample_period = 1200000;
|
||||
|
||||
fd = sp_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags);
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
/*
|
||||
* We might just not have access to hardware counters, so try to
|
||||
* gracefully fallback to software counters.
|
||||
*/
|
||||
attr.type = PERF_TYPE_SOFTWARE;
|
||||
attr.config = PERF_COUNT_SW_CPU_CLOCK;
|
||||
attr.sample_period = 1000000;
|
||||
|
||||
errno = 0;
|
||||
|
||||
fd = sp_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags);
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
if (errno == EPERM || errno == EACCES)
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_PERMISSION_DENIED,
|
||||
_("Sysprof requires authorization to access your computers performance counters."));
|
||||
else
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_FAILED,
|
||||
_("An error occurred while attempting to access performance counters: %s"),
|
||||
g_strerror (errno));
|
||||
|
||||
sp_source_stop (SP_SOURCE (self));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS(optional_tracepoints); i++)
|
||||
{
|
||||
sp_perf_source_add_optional_tracepoint (self, pid, cpu,
|
||||
&optional_tracepoints[i],
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_start (SpSource *source)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
self->counter = sp_perf_counter_new (NULL);
|
||||
|
||||
sp_perf_counter_set_callback (self->counter,
|
||||
sp_perf_source_handle_event,
|
||||
self, NULL);
|
||||
|
||||
if (g_hash_table_size (self->pids) > 0)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
|
||||
g_hash_table_iter_init (&iter, self->pids);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, &key, NULL))
|
||||
{
|
||||
GPid pid = GPOINTER_TO_INT (key);
|
||||
|
||||
if (!sp_perf_source_start_pid (self, pid, &error))
|
||||
{
|
||||
sp_source_emit_failed (source, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!sp_perf_source_start_pid (self, -1, &error))
|
||||
{
|
||||
sp_source_emit_failed (source, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self->running = TRUE;
|
||||
|
||||
sp_perf_counter_enable (self->counter);
|
||||
|
||||
sp_source_emit_ready (source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_stop (SpSource *source)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
if (self->running)
|
||||
{
|
||||
self->running = FALSE;
|
||||
sp_perf_counter_disable (self->counter);
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->counter, sp_perf_counter_unref);
|
||||
|
||||
sp_source_emit_finished (source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_return_if_fail (SP_IS_PERF_SOURCE (self));
|
||||
g_return_if_fail (pid >= -1);
|
||||
g_return_if_fail (self->writer == NULL);
|
||||
|
||||
g_hash_table_add (self->pids, GINT_TO_POINTER (pid));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_emit_ready (SpPerfSource *self)
|
||||
{
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
self->is_ready = TRUE;
|
||||
sp_source_emit_ready (SP_SOURCE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_authorize_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(SpPerfSource) self = user_data;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
g_assert (G_IS_ASYNC_RESULT (result));
|
||||
|
||||
if (!sp_perf_counter_authorize_finish (result, &error))
|
||||
{
|
||||
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
|
||||
{
|
||||
sp_source_emit_failed (SP_SOURCE (self), error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sp_perf_source_emit_ready (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
user_owns_pid (uid_t uid,
|
||||
GPid pid)
|
||||
{
|
||||
g_autofree gchar *contents = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
g_autoptr(SpLineReader) reader = NULL;
|
||||
gchar *line;
|
||||
gsize len;
|
||||
gsize line_len;
|
||||
|
||||
path = g_strdup_printf ("/proc/%u/status", (guint)pid);
|
||||
|
||||
if (!g_file_get_contents (path, &contents, &len, NULL))
|
||||
return FALSE;
|
||||
|
||||
reader = sp_line_reader_new (contents, len);
|
||||
|
||||
while (NULL != (line = (gchar *)sp_line_reader_next (reader, &line_len)))
|
||||
{
|
||||
if (g_str_has_prefix (line, "Uid:"))
|
||||
{
|
||||
g_auto(GStrv) parts = NULL;
|
||||
guint i;
|
||||
|
||||
line[line_len] = '\0';
|
||||
parts = g_strsplit (line, "\t", 0);
|
||||
|
||||
for (i = 1; parts[i]; i++)
|
||||
{
|
||||
gint64 v64;
|
||||
|
||||
v64 = g_ascii_strtoll (parts[i], NULL, 10);
|
||||
|
||||
if (v64 > 0 && v64 <= G_MAXUINT)
|
||||
{
|
||||
if ((uid_t)v64 == uid)
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_source_needs_auth (SpPerfSource *self)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
uid_t uid;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
if (g_hash_table_size (self->pids) == 0)
|
||||
return TRUE;
|
||||
|
||||
uid = getuid ();
|
||||
|
||||
g_hash_table_iter_init (&iter, self->pids);
|
||||
|
||||
while (g_hash_table_iter_next (&iter, &key, NULL))
|
||||
{
|
||||
GPid pid = GPOINTER_TO_INT (key);
|
||||
|
||||
if (!user_owns_pid (uid, pid))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_perf_source_prepare (SpSource *source)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
if (sp_perf_source_needs_auth (self))
|
||||
sp_perf_counter_authorize_async (NULL,
|
||||
sp_perf_source_authorize_cb,
|
||||
g_object_ref (self));
|
||||
else
|
||||
sp_perf_source_emit_ready (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_perf_source_get_is_ready (SpSource *source)
|
||||
{
|
||||
SpPerfSource *self = (SpPerfSource *)source;
|
||||
|
||||
g_assert (SP_IS_PERF_SOURCE (self));
|
||||
|
||||
return self->is_ready;
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->start = sp_perf_source_start;
|
||||
iface->stop = sp_perf_source_stop;
|
||||
iface->set_writer = sp_perf_source_set_writer;
|
||||
iface->add_pid = sp_perf_source_add_pid;
|
||||
iface->prepare = sp_perf_source_prepare;
|
||||
iface->get_is_ready = sp_perf_source_get_is_ready;
|
||||
}
|
||||
|
||||
SpSource *
|
||||
sp_perf_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_PERF_SOURCE, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sp_perf_source_set_target_pid (SpPerfSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PERF_SOURCE (self));
|
||||
g_return_if_fail (pid >= -1);
|
||||
|
||||
if (pid == -1)
|
||||
g_hash_table_remove_all (self->pids);
|
||||
else
|
||||
sp_perf_source_add_pid (SP_SOURCE (self), pid);
|
||||
}
|
||||
36
src/libsysprof/sp-perf-source.h
Normal file
36
src/libsysprof/sp-perf-source.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-perf-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_PERF_SOURCE_H
|
||||
#define SP_PERF_SOURCE_H
|
||||
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PERF_SOURCE (sp_perf_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpPerfSource, sp_perf_source, SP, PERF_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_perf_source_new (void);
|
||||
void sp_perf_source_set_target_pid (SpPerfSource *self,
|
||||
GPid pid);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PERF_SOURCE_H */
|
||||
517
src/libsysprof/sp-proc-source.c
Normal file
517
src/libsysprof/sp-proc-source.c
Normal file
@ -0,0 +1,517 @@
|
||||
/* sp-proc-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/>.
|
||||
*/
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-proc-source.h"
|
||||
|
||||
struct _SpProcSource
|
||||
{
|
||||
GObject parent_instance;
|
||||
SpCaptureWriter *writer;
|
||||
GArray *pids;
|
||||
};
|
||||
|
||||
static void source_iface_init (SpSourceInterface *iface);
|
||||
static gchar **proc_readlines (const gchar *format,
|
||||
...)
|
||||
G_GNUC_PRINTF (1, 2);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpProcSource, sp_proc_source, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
|
||||
|
||||
static gchar **
|
||||
proc_readlines (const gchar *format,
|
||||
...)
|
||||
{
|
||||
gchar **ret = NULL;
|
||||
gchar *filename = NULL;
|
||||
gchar *contents = NULL;
|
||||
va_list args;
|
||||
gsize len;
|
||||
|
||||
g_assert (format != NULL);
|
||||
|
||||
va_start (args, format);
|
||||
filename = g_strdup_vprintf (format, args);
|
||||
va_end (args);
|
||||
|
||||
if (g_file_get_contents (filename, &contents, &len, NULL))
|
||||
ret = g_strsplit (contents, "\n", 0);
|
||||
|
||||
g_free (contents);
|
||||
g_free (filename);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
gchar *
|
||||
sp_proc_source_get_command_line (GPid pid,
|
||||
gboolean *is_kernel)
|
||||
{
|
||||
gchar *ret;
|
||||
gchar **lines;
|
||||
|
||||
|
||||
if (is_kernel)
|
||||
*is_kernel = FALSE;
|
||||
|
||||
/*
|
||||
* Get the full command line from /proc/pid/cmdline.
|
||||
*/
|
||||
if (NULL != (lines = proc_readlines ("/proc/%d/cmdline", pid)))
|
||||
{
|
||||
if (lines [0] && lines [0][0])
|
||||
{
|
||||
ret = g_strdup (lines [0]);
|
||||
g_strfreev (lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
g_strfreev (lines);
|
||||
}
|
||||
|
||||
/*
|
||||
* We are guessing this is a kernel process based on cmdline being null.
|
||||
*/
|
||||
if (is_kernel)
|
||||
*is_kernel = TRUE;
|
||||
|
||||
/*
|
||||
* Check the first line of /proc/pid/status for Name: foo
|
||||
*/
|
||||
if (NULL != (lines = proc_readlines ("/proc/%d/status", pid)))
|
||||
{
|
||||
if (lines [0] && g_str_has_prefix (lines [0], "Name:"))
|
||||
{
|
||||
ret = g_strstrip (g_strdup (lines [0] + 5));
|
||||
g_strfreev (lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
g_strfreev (lines);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_populate_process (SpProcSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
gchar *cmdline;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (pid > 0);
|
||||
|
||||
if (NULL != (cmdline = sp_proc_source_get_command_line (pid, NULL)))
|
||||
{
|
||||
sp_capture_writer_add_process (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
pid,
|
||||
cmdline);
|
||||
g_free (cmdline);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
strv_at_least_len (GStrv strv,
|
||||
guint len)
|
||||
{
|
||||
for (guint i = 0; i < len; i++)
|
||||
{
|
||||
if (strv[i] == NULL)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
find_mount (GStrv mounts,
|
||||
const gchar *mount)
|
||||
{
|
||||
gsize len = strlen (mount);
|
||||
|
||||
for (guint i = 0; mounts[i]; i++)
|
||||
{
|
||||
const gchar *endptr;
|
||||
const gchar *begin;
|
||||
|
||||
if (!g_str_has_prefix (mounts[i], mount))
|
||||
continue;
|
||||
|
||||
if (mounts[i][len] != ' ')
|
||||
continue;
|
||||
|
||||
begin = &mounts[i][len + 1];
|
||||
endptr = strchr (begin, ' ');
|
||||
if (endptr == NULL)
|
||||
continue;
|
||||
|
||||
return g_strndup (begin, endptr - begin);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_proc_source_translate_path:
|
||||
* @file: the path to the file inside target mount namespace
|
||||
* @mountinfo: mount info to locate translated path
|
||||
* @out_file: (out): location for the translated path
|
||||
* @out_file_len: length of @out_file
|
||||
*
|
||||
* This function will use @mountinfo to locate the longest common prefix
|
||||
* to determine which mount contains @file. That will be used to translate
|
||||
* the path pointed to by @file into the host namespace.
|
||||
*
|
||||
* The result is stored in @out_file and will always be NULL terminated.
|
||||
*/
|
||||
static void
|
||||
sp_proc_source_translate_path (const gchar *file,
|
||||
GStrv mountinfo,
|
||||
GStrv mounts,
|
||||
gchar *out_file,
|
||||
gsize out_file_len)
|
||||
{
|
||||
g_autofree gchar *closest_host = NULL;
|
||||
g_autofree gchar *closest_guest = NULL;
|
||||
g_autofree gchar *closest_mount = NULL;
|
||||
gsize closest_len = 0;
|
||||
|
||||
g_assert (file != NULL);
|
||||
g_assert (g_str_has_prefix (file, "/newroot/"));
|
||||
g_assert (mountinfo != NULL);
|
||||
g_assert (out_file != NULL);
|
||||
|
||||
if (!g_str_has_prefix (file, "/newroot/"))
|
||||
goto failure;
|
||||
|
||||
file += strlen ("/newroot");
|
||||
|
||||
for (guint i = 0; mountinfo[i] != NULL; i++)
|
||||
{
|
||||
g_auto(GStrv) parts = g_strsplit (mountinfo[i], " ", 11);
|
||||
const gchar *host;
|
||||
const gchar *guest;
|
||||
const gchar *mount;
|
||||
|
||||
/*
|
||||
* Not ideal to do the string split here, but it is much easier
|
||||
* to just do that until we get this right, and then improve
|
||||
* things later when a strok()/etc parser.
|
||||
*/
|
||||
|
||||
if (!strv_at_least_len (parts, 10))
|
||||
continue;
|
||||
|
||||
host = parts[3];
|
||||
guest = parts[4];
|
||||
mount = parts[9];
|
||||
|
||||
if (g_str_has_prefix (file, guest))
|
||||
{
|
||||
gsize len = strlen (guest);
|
||||
|
||||
if (len > closest_len && (file[len] == '\0' || file[len] == '/'))
|
||||
{
|
||||
g_free (closest_host);
|
||||
g_free (closest_guest);
|
||||
g_free (closest_mount);
|
||||
|
||||
closest_guest = g_strdup (guest);
|
||||
closest_host = g_strdup (host);
|
||||
closest_mount = g_strdup (mount);
|
||||
|
||||
closest_len = len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closest_len > 0)
|
||||
{
|
||||
/*
|
||||
* The translated path is relative to the mount. So we need to add that
|
||||
* prefix to this as well, based on matching it from the closest_mount.
|
||||
*/
|
||||
g_autofree gchar *mount = NULL;
|
||||
|
||||
mount = find_mount (mounts, closest_mount);
|
||||
|
||||
if (mount != NULL)
|
||||
{
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
path = g_build_filename (mount, closest_host, file + strlen (closest_guest), NULL);
|
||||
g_strlcpy (out_file, path, out_file_len);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
failure:
|
||||
/* Fallback to just copying the source */
|
||||
g_strlcpy (out_file, file, out_file_len);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_populate_maps (SpProcSource *self,
|
||||
GPid pid,
|
||||
GStrv mounts)
|
||||
{
|
||||
g_auto(GStrv) lines = NULL;
|
||||
g_auto(GStrv) mountinfo = NULL;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (pid > 0);
|
||||
|
||||
if (NULL == (lines = proc_readlines ("/proc/%d/maps", pid)))
|
||||
return;
|
||||
|
||||
if (NULL == (mountinfo = proc_readlines ("/proc/%d/mountinfo", pid)))
|
||||
return;
|
||||
|
||||
for (i = 0; lines [i] != NULL; i++)
|
||||
{
|
||||
gchar file[256];
|
||||
gchar translated[256];
|
||||
const gchar *fileptr = file;
|
||||
gulong start;
|
||||
gulong end;
|
||||
gulong offset;
|
||||
gulong inode;
|
||||
gint r;
|
||||
|
||||
r = sscanf (lines [i],
|
||||
"%lx-%lx %*15s %lx %*x:%*x %lu %255s",
|
||||
&start, &end, &offset, &inode, file);
|
||||
|
||||
file [sizeof file - 1] = '\0';
|
||||
|
||||
if (r != 5)
|
||||
continue;
|
||||
|
||||
if (strcmp ("[vdso]", file) == 0)
|
||||
{
|
||||
/*
|
||||
* Søren Sandmann Pedersen says:
|
||||
*
|
||||
* For the vdso, the kernel reports 'offset' as the
|
||||
* the same as the mapping addres. This doesn't make
|
||||
* any sense to me, so we just zero it here. There
|
||||
* is code in binfile.c (read_inode) that returns 0
|
||||
* for [vdso].
|
||||
*/
|
||||
offset = 0;
|
||||
inode = 0;
|
||||
}
|
||||
|
||||
if (g_str_has_prefix (file, "/newroot/"))
|
||||
{
|
||||
/*
|
||||
* If this file starts with /newroot/, then it is in a different
|
||||
* mount-namespace from our profiler process. This means that we need
|
||||
* to translate the filename to the real path on disk inside our
|
||||
* (hopefully the default) mount-namespace. To do this, we have to
|
||||
* look at /proc/$pid/mountinfo to locate the longest-common-prefix
|
||||
* for the path.
|
||||
*/
|
||||
sp_proc_source_translate_path (file,
|
||||
mountinfo,
|
||||
mounts,
|
||||
translated,
|
||||
sizeof translated);
|
||||
fileptr = translated;
|
||||
}
|
||||
|
||||
sp_capture_writer_add_map (self->writer,
|
||||
SP_CAPTURE_CURRENT_TIME,
|
||||
-1,
|
||||
pid,
|
||||
start,
|
||||
end,
|
||||
offset,
|
||||
inode,
|
||||
fileptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_populate (SpProcSource *self)
|
||||
{
|
||||
g_auto(GStrv) mounts = NULL;
|
||||
const gchar *name;
|
||||
GDir *dir;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
|
||||
if (NULL == (mounts = proc_readlines ("/proc/mounts")))
|
||||
return;
|
||||
|
||||
if (self->pids->len > 0)
|
||||
{
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid pid = g_array_index (self->pids, GPid, i);
|
||||
|
||||
sp_proc_source_populate_process (self, pid);
|
||||
sp_proc_source_populate_maps (self, pid, mounts);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (NULL == (dir = g_dir_open ("/proc", 0, NULL)))
|
||||
return;
|
||||
|
||||
while (NULL != (name = g_dir_read_name (dir)))
|
||||
{
|
||||
GPid pid;
|
||||
char *end;
|
||||
|
||||
pid = strtol (name, &end, 10);
|
||||
if (pid <= 0 || *end != '\0')
|
||||
continue;
|
||||
|
||||
sp_proc_source_populate_process (self, pid);
|
||||
sp_proc_source_populate_maps (self, pid, mounts);
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_start (SpSource *source)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)source;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (self->writer != NULL);
|
||||
|
||||
sp_proc_source_populate (self);
|
||||
sp_source_emit_finished (source);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_stop (SpSource *source)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)source;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_set_writer (SpSource *source,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)source;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (writer != NULL);
|
||||
|
||||
self->writer = sp_capture_writer_ref (writer);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_add_pid (SpSource *source,
|
||||
GPid pid)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)source;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_PROC_SOURCE (self));
|
||||
g_assert (pid > -1);
|
||||
|
||||
for (i = 0; i < self->pids->len; i++)
|
||||
{
|
||||
GPid ele = g_array_index (self->pids, GPid, i);
|
||||
|
||||
if (ele == pid)
|
||||
return;
|
||||
}
|
||||
|
||||
g_array_append_val (self->pids, pid);
|
||||
}
|
||||
|
||||
static void
|
||||
source_iface_init (SpSourceInterface *iface)
|
||||
{
|
||||
iface->set_writer = sp_proc_source_set_writer;
|
||||
iface->start = sp_proc_source_start;
|
||||
iface->stop = sp_proc_source_stop;
|
||||
iface->add_pid = sp_proc_source_add_pid;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_finalize (GObject *object)
|
||||
{
|
||||
SpProcSource *self = (SpProcSource *)object;
|
||||
|
||||
g_clear_pointer (&self->writer, sp_capture_writer_unref);
|
||||
g_clear_pointer (&self->pids, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_proc_source_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_class_init (SpProcSourceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_proc_source_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_proc_source_init (SpProcSource *self)
|
||||
{
|
||||
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
|
||||
}
|
||||
|
||||
SpSource *
|
||||
sp_proc_source_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_PROC_SOURCE, NULL);
|
||||
}
|
||||
36
src/libsysprof/sp-proc-source.h
Normal file
36
src/libsysprof/sp-proc-source.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-proc-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_PROC_SOURCE_H
|
||||
#define SP_PROC_SOURCE_H
|
||||
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PROC_SOURCE (sp_proc_source_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpProcSource, sp_proc_source, SP, PROC_SOURCE, GObject)
|
||||
|
||||
SpSource *sp_proc_source_new (void);
|
||||
gchar *sp_proc_source_get_command_line (GPid pid,
|
||||
gboolean *is_kernel);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PROC_SOURCE_H */
|
||||
234
src/libsysprof/sp-process-model-item.c
Normal file
234
src/libsysprof/sp-process-model-item.c
Normal file
@ -0,0 +1,234 @@
|
||||
/* sp-process-model-item.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 <string.h>
|
||||
|
||||
#include "sp-process-model-item.h"
|
||||
#include "sp-proc-source.h"
|
||||
|
||||
struct _SpProcessModelItem
|
||||
{
|
||||
GObject parent_instance;
|
||||
GPid pid;
|
||||
gchar *command_line; /* Short version (first field) */
|
||||
gchar **argv; /* Long version (argv as a strv) */
|
||||
guint is_kernel : 1;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (SpProcessModelItem, sp_process_model_item, G_TYPE_OBJECT)
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_COMMAND_LINE,
|
||||
PROP_PID,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
|
||||
static void
|
||||
sp_process_model_item_finalize (GObject *object)
|
||||
{
|
||||
SpProcessModelItem *self = (SpProcessModelItem *)object;
|
||||
|
||||
g_clear_pointer (&self->command_line, g_free);
|
||||
g_clear_pointer (&self->argv, g_strfreev);
|
||||
|
||||
G_OBJECT_CLASS (sp_process_model_item_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_item_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpProcessModelItem *self = SP_PROCESS_MODEL_ITEM (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_COMMAND_LINE:
|
||||
g_value_set_string (value, self->command_line);
|
||||
break;
|
||||
|
||||
case PROP_PID:
|
||||
g_value_set_int (value, self->pid);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_item_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpProcessModelItem *self = SP_PROCESS_MODEL_ITEM (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_COMMAND_LINE:
|
||||
self->command_line = g_value_dup_string (value);
|
||||
break;
|
||||
|
||||
case PROP_PID:
|
||||
self->pid = g_value_get_int (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_item_class_init (SpProcessModelItemClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_process_model_item_finalize;
|
||||
object_class->get_property = sp_process_model_item_get_property;
|
||||
object_class->set_property = sp_process_model_item_set_property;
|
||||
|
||||
properties [PROP_COMMAND_LINE] =
|
||||
g_param_spec_string ("command-line",
|
||||
"Command Line",
|
||||
"Command Line",
|
||||
NULL,
|
||||
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
properties [PROP_PID] =
|
||||
g_param_spec_int ("pid",
|
||||
"Pid",
|
||||
"Pid",
|
||||
-1,
|
||||
G_MAXINT,
|
||||
-1,
|
||||
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_item_init (SpProcessModelItem *self)
|
||||
{
|
||||
}
|
||||
|
||||
SpProcessModelItem *
|
||||
sp_process_model_item_new (GPid pid)
|
||||
{
|
||||
SpProcessModelItem *ret;
|
||||
gchar *cmdline;
|
||||
gboolean is_kernel;
|
||||
|
||||
cmdline = sp_proc_source_get_command_line (pid, &is_kernel);
|
||||
|
||||
ret = g_object_new (SP_TYPE_PROCESS_MODEL_ITEM,
|
||||
"command-line", cmdline,
|
||||
"pid", (int)pid,
|
||||
NULL);
|
||||
ret->is_kernel = is_kernel;
|
||||
|
||||
g_free (cmdline);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
guint
|
||||
sp_process_model_item_hash (SpProcessModelItem *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ITEM (self), 0);
|
||||
|
||||
return self->pid;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_process_model_item_equal (SpProcessModelItem *self,
|
||||
SpProcessModelItem *other)
|
||||
{
|
||||
g_assert (SP_IS_PROCESS_MODEL_ITEM (self));
|
||||
g_assert (SP_IS_PROCESS_MODEL_ITEM (other));
|
||||
|
||||
return ((self->pid == other->pid) &&
|
||||
(g_strcmp0 (self->command_line, other->command_line) == 0));
|
||||
}
|
||||
|
||||
GPid
|
||||
sp_process_model_item_get_pid (SpProcessModelItem *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ITEM (self), 0);
|
||||
|
||||
return self->pid;
|
||||
}
|
||||
|
||||
const gchar *
|
||||
sp_process_model_item_get_command_line (SpProcessModelItem *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ITEM (self), NULL);
|
||||
|
||||
return self->command_line;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_process_model_item_is_kernel (SpProcessModelItem *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ITEM (self), FALSE);
|
||||
|
||||
return self->is_kernel;
|
||||
}
|
||||
|
||||
const gchar * const *
|
||||
sp_process_model_item_get_argv (SpProcessModelItem *self)
|
||||
{
|
||||
g_autofree gchar *contents = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
const gchar *pos;
|
||||
const gchar *endptr;
|
||||
GPtrArray *ar;
|
||||
gsize size = 0;
|
||||
GPid pid;
|
||||
|
||||
g_return_val_if_fail (SP_IS_PROCESS_MODEL_ITEM (self), NULL);
|
||||
|
||||
if (self->argv)
|
||||
return (const gchar * const *)self->argv;
|
||||
|
||||
if ((pid = sp_process_model_item_get_pid (self)) < 0)
|
||||
return NULL;
|
||||
|
||||
path = g_strdup_printf ("/proc/%u/cmdline", (guint)pid);
|
||||
if (!g_file_get_contents (path, &contents, &size, NULL))
|
||||
return NULL;
|
||||
|
||||
ar = g_ptr_array_new ();
|
||||
|
||||
/* Each parameter is followed by \0 */
|
||||
for (pos = contents, endptr = contents + size;
|
||||
pos < endptr;
|
||||
pos += strlen (pos) + 1)
|
||||
g_ptr_array_add (ar, g_strdup (pos));
|
||||
g_ptr_array_add (ar, NULL);
|
||||
|
||||
g_clear_pointer (&self->argv, g_strfreev);
|
||||
self->argv = (gchar **)g_ptr_array_free (ar, FALSE);
|
||||
|
||||
return (const gchar * const *)self->argv;
|
||||
}
|
||||
|
||||
41
src/libsysprof/sp-process-model-item.h
Normal file
41
src/libsysprof/sp-process-model-item.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* sp-process-model-item.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_PROCESS_MODEL_ITEM_H
|
||||
#define SP_PROCESS_MODEL_ITEM_H
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PROCESS_MODEL_ITEM (sp_process_model_item_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpProcessModelItem, sp_process_model_item, SP, PROCESS_MODEL_ITEM, GObject)
|
||||
|
||||
SpProcessModelItem *sp_process_model_item_new (GPid pid);
|
||||
guint sp_process_model_item_hash (SpProcessModelItem *self);
|
||||
gboolean sp_process_model_item_equal (SpProcessModelItem *self,
|
||||
SpProcessModelItem *other);
|
||||
GPid sp_process_model_item_get_pid (SpProcessModelItem *self);
|
||||
const gchar *sp_process_model_item_get_command_line (SpProcessModelItem *self);
|
||||
gboolean sp_process_model_item_is_kernel (SpProcessModelItem *self);
|
||||
const gchar * const *sp_process_model_item_get_argv (SpProcessModelItem *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PROCESS_MODEL_ITEM_H */
|
||||
314
src/libsysprof/sp-process-model.c
Normal file
314
src/libsysprof/sp-process-model.c
Normal file
@ -0,0 +1,314 @@
|
||||
/* sp-process-model.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 <stdlib.h>
|
||||
|
||||
#include "sp-process-model.h"
|
||||
#include "sp-process-model-item.h"
|
||||
|
||||
#define QUEUE_RELOAD_TIMEOUT_MSEC 100
|
||||
|
||||
struct _SpProcessModel
|
||||
{
|
||||
GObject parent_instance;
|
||||
guint reload_source;
|
||||
GPtrArray *items;
|
||||
};
|
||||
|
||||
static void list_model_iface_init (GListModelInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_EXTENDED (SpProcessModel, sp_process_model, G_TYPE_OBJECT, 0,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
|
||||
|
||||
static void
|
||||
sp_process_model_finalize (GObject *object)
|
||||
{
|
||||
SpProcessModel *self = (SpProcessModel *)object;
|
||||
|
||||
if (self->reload_source)
|
||||
{
|
||||
g_source_remove (self->reload_source);
|
||||
self->reload_source = 0;
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->items, g_ptr_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_process_model_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_class_init (SpProcessModelClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_process_model_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_init (SpProcessModel *self)
|
||||
{
|
||||
self->items = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
|
||||
sp_process_model_queue_reload (self);
|
||||
}
|
||||
|
||||
static guint
|
||||
find_index (GPtrArray *ar,
|
||||
GPid pid)
|
||||
{
|
||||
guint i;
|
||||
|
||||
g_assert (ar != NULL);
|
||||
|
||||
for (i = 0; i < ar->len; i++)
|
||||
{
|
||||
SpProcessModelItem *item = g_ptr_array_index (ar, i);
|
||||
GPid item_pid = sp_process_model_item_get_pid (item);
|
||||
|
||||
g_assert (pid != item_pid);
|
||||
|
||||
if (item_pid > pid)
|
||||
return i;
|
||||
}
|
||||
|
||||
return ar->len;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_merge_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
SpProcessModel *self = (SpProcessModel *)object;
|
||||
g_autoptr(GPtrArray) ret = NULL;
|
||||
g_autoptr(GHashTable) old_hash = NULL;
|
||||
g_autoptr(GHashTable) new_hash = NULL;
|
||||
GError *error = NULL;
|
||||
guint i;
|
||||
|
||||
g_assert (SP_IS_PROCESS_MODEL (self));
|
||||
g_assert (G_IS_TASK (result));
|
||||
|
||||
ret = g_task_propagate_pointer (G_TASK (result), &error);
|
||||
|
||||
if (ret == NULL)
|
||||
{
|
||||
g_warning ("%s", error->message);
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: Clearly this could be optimized to walk both arrays at once
|
||||
* and do a proper 2-way merge.
|
||||
*/
|
||||
|
||||
old_hash = g_hash_table_new ((GHashFunc)sp_process_model_item_hash,
|
||||
(GEqualFunc)sp_process_model_item_equal);
|
||||
new_hash = g_hash_table_new ((GHashFunc)sp_process_model_item_hash,
|
||||
(GEqualFunc)sp_process_model_item_equal);
|
||||
|
||||
for (i = 0; i < self->items->len; i++)
|
||||
{
|
||||
SpProcessModelItem *item = g_ptr_array_index (self->items, i);
|
||||
|
||||
g_hash_table_insert (old_hash, item, NULL);
|
||||
}
|
||||
|
||||
for (i = 0; i < ret->len; i++)
|
||||
{
|
||||
SpProcessModelItem *item = g_ptr_array_index (ret, i);
|
||||
|
||||
g_hash_table_insert (new_hash, item, NULL);
|
||||
}
|
||||
|
||||
for (i = self->items->len; i > 0; i--)
|
||||
{
|
||||
guint index = i - 1;
|
||||
SpProcessModelItem *item = g_ptr_array_index (self->items, index);
|
||||
|
||||
if (!g_hash_table_contains (new_hash, item))
|
||||
{
|
||||
g_ptr_array_remove_index (self->items, index);
|
||||
g_list_model_items_changed (G_LIST_MODEL (self), index, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ret->len; i++)
|
||||
{
|
||||
SpProcessModelItem *item = g_ptr_array_index (ret, i);
|
||||
GPid pid;
|
||||
guint index;
|
||||
|
||||
if (g_hash_table_contains (old_hash, item))
|
||||
continue;
|
||||
|
||||
pid = sp_process_model_item_get_pid (item);
|
||||
index = find_index (self->items, pid);
|
||||
|
||||
g_ptr_array_insert (self->items, index, g_object_ref (item));
|
||||
g_list_model_items_changed (G_LIST_MODEL (self), index, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static gint
|
||||
compare_by_pid (gconstpointer a,
|
||||
gconstpointer b)
|
||||
{
|
||||
SpProcessModelItem **aitem = (SpProcessModelItem **)a;
|
||||
SpProcessModelItem **bitem = (SpProcessModelItem **)b;
|
||||
|
||||
return sp_process_model_item_get_pid (*aitem) - sp_process_model_item_get_pid (*bitem);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_process_model_reload_worker (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
g_autoptr(GPtrArray) ret = NULL;
|
||||
const gchar *name;
|
||||
GError *error = NULL;
|
||||
GDir *dir;
|
||||
|
||||
g_assert (SP_IS_PROCESS_MODEL (source_object));
|
||||
g_assert (G_IS_TASK (task));
|
||||
|
||||
dir = g_dir_open ("/proc", 0, &error);
|
||||
|
||||
if (dir == NULL)
|
||||
{
|
||||
g_task_return_error (task, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
|
||||
while ((name = g_dir_read_name (dir)))
|
||||
{
|
||||
SpProcessModelItem *item;
|
||||
GPid pid;
|
||||
gchar *end;
|
||||
|
||||
pid = strtol (name, &end, 10);
|
||||
if (pid <= 0 || *end != '\0')
|
||||
continue;
|
||||
|
||||
item = sp_process_model_item_new (pid);
|
||||
|
||||
if (sp_process_model_item_is_kernel (item))
|
||||
{
|
||||
g_object_unref (item);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_ptr_array_add (ret, item);
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
|
||||
g_ptr_array_sort (ret, compare_by_pid);
|
||||
g_task_return_pointer (task, g_ptr_array_ref (ret), (GDestroyNotify)g_ptr_array_unref);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sp_process_model_do_reload (gpointer user_data)
|
||||
{
|
||||
SpProcessModel *self = user_data;
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
self->reload_source = 0;
|
||||
|
||||
task = g_task_new (self, NULL, sp_process_model_merge_cb, NULL);
|
||||
g_task_set_priority (task, G_PRIORITY_LOW);
|
||||
g_task_run_in_thread (task, sp_process_model_reload_worker);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
SpProcessModel *
|
||||
sp_process_model_new (void)
|
||||
{
|
||||
return g_object_new (SP_TYPE_PROCESS_MODEL, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sp_process_model_reload (SpProcessModel *self)
|
||||
{
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
g_return_if_fail (SP_IS_PROCESS_MODEL (self));
|
||||
|
||||
if (self->reload_source != 0)
|
||||
{
|
||||
g_source_remove (self->reload_source);
|
||||
self->reload_source = 0;
|
||||
}
|
||||
|
||||
task = g_task_new (self, NULL, NULL, NULL);
|
||||
g_task_set_priority (task, G_PRIORITY_LOW);
|
||||
g_task_run_in_thread_sync (task, sp_process_model_reload_worker);
|
||||
|
||||
sp_process_model_merge_cb (G_OBJECT (self), G_ASYNC_RESULT (task), NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sp_process_model_queue_reload (SpProcessModel *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROCESS_MODEL (self));
|
||||
|
||||
if (self->reload_source == 0)
|
||||
self->reload_source = g_timeout_add (QUEUE_RELOAD_TIMEOUT_MSEC,
|
||||
sp_process_model_do_reload,
|
||||
self);
|
||||
}
|
||||
|
||||
static GType
|
||||
sp_process_model_get_item_type (GListModel *model)
|
||||
{
|
||||
return SP_TYPE_PROCESS_MODEL_ITEM;
|
||||
}
|
||||
|
||||
static guint
|
||||
sp_process_model_get_n_items (GListModel *model)
|
||||
{
|
||||
SpProcessModel *self = (SpProcessModel *)model;
|
||||
|
||||
return self->items->len;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
sp_process_model_get_item (GListModel *model,
|
||||
guint position)
|
||||
{
|
||||
SpProcessModel *self = (SpProcessModel *)model;
|
||||
|
||||
g_return_val_if_fail (SP_IS_PROCESS_MODEL (self), NULL);
|
||||
g_return_val_if_fail (position < self->items->len, NULL);
|
||||
|
||||
return g_object_ref (g_ptr_array_index (self->items, position));
|
||||
}
|
||||
|
||||
static void
|
||||
list_model_iface_init (GListModelInterface *iface)
|
||||
{
|
||||
iface->get_item_type = sp_process_model_get_item_type;
|
||||
iface->get_n_items = sp_process_model_get_n_items;
|
||||
iface->get_item = sp_process_model_get_item;
|
||||
}
|
||||
36
src/libsysprof/sp-process-model.h
Normal file
36
src/libsysprof/sp-process-model.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* sp-process-model.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_PROCESS_MODEL_H
|
||||
#define SP_PROCESS_MODEL_H
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PROCESS_MODEL (sp_process_model_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpProcessModel, sp_process_model, SP, PROCESS_MODEL, GObject)
|
||||
|
||||
SpProcessModel *sp_process_model_new (void);
|
||||
void sp_process_model_reload (SpProcessModel *self);
|
||||
void sp_process_model_queue_reload (SpProcessModel *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PROCESS_MODEL_H */
|
||||
81
src/libsysprof/sp-profile.c
Normal file
81
src/libsysprof/sp-profile.c
Normal file
@ -0,0 +1,81 @@
|
||||
/* sp-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/>.
|
||||
*/
|
||||
|
||||
#include "sp-profile.h"
|
||||
|
||||
G_DEFINE_INTERFACE (SpProfile, sp_profile, G_TYPE_OBJECT)
|
||||
|
||||
static void
|
||||
dummy_generate (SpProfile *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr(GTask) task = NULL;
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
g_task_return_boolean (task, TRUE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dummy_generate_finish (SpProfile *self,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_profile_default_init (SpProfileInterface *iface)
|
||||
{
|
||||
iface->generate = dummy_generate;
|
||||
iface->generate_finish = dummy_generate_finish;
|
||||
}
|
||||
|
||||
void
|
||||
sp_profile_generate (SpProfile *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILE (self));
|
||||
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
||||
|
||||
SP_PROFILE_GET_IFACE (self)->generate (self, cancellable, callback, user_data);
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_profile_generate_finish (SpProfile *self,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_PROFILE (self), FALSE);
|
||||
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
|
||||
|
||||
return SP_PROFILE_GET_IFACE (self)->generate_finish (self, result, error);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profile_set_reader (SpProfile *self,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILE (self));
|
||||
g_return_if_fail (reader != NULL);
|
||||
|
||||
SP_PROFILE_GET_IFACE (self)->set_reader (self, reader);
|
||||
}
|
||||
59
src/libsysprof/sp-profile.h
Normal file
59
src/libsysprof/sp-profile.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* sp-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_PROFILE_H
|
||||
#define SP_PROFILE_H
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include "sp-capture-reader.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PROFILE (sp_profile_get_type ())
|
||||
|
||||
G_DECLARE_INTERFACE (SpProfile, sp_profile, SP, PROFILE, GObject)
|
||||
|
||||
struct _SpProfileInterface
|
||||
{
|
||||
GTypeInterface parent;
|
||||
|
||||
void (*set_reader) (SpProfile *self,
|
||||
SpCaptureReader *reader);
|
||||
void (*generate) (SpProfile *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
gboolean (*generate_finish) (SpProfile *self,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
};
|
||||
|
||||
void sp_profile_set_reader (SpProfile *self,
|
||||
SpCaptureReader *reader);
|
||||
void sp_profile_generate (SpProfile *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
gboolean sp_profile_generate_finish (SpProfile *self,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PROFILE_H */
|
||||
291
src/libsysprof/sp-profiler.c
Normal file
291
src/libsysprof/sp-profiler.c
Normal file
@ -0,0 +1,291 @@
|
||||
/* sp-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 "sp-profiler.h"
|
||||
|
||||
G_DEFINE_INTERFACE (SpProfiler, sp_profiler, G_TYPE_OBJECT)
|
||||
|
||||
enum {
|
||||
FAILED,
|
||||
STOPPED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint signals [N_SIGNALS];
|
||||
|
||||
static void
|
||||
sp_profiler_default_init (SpProfilerInterface *iface)
|
||||
{
|
||||
signals [FAILED] = g_signal_new ("failed",
|
||||
G_TYPE_FROM_INTERFACE (iface),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (SpProfilerInterface, failed),
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, G_TYPE_ERROR);
|
||||
|
||||
signals [STOPPED] = g_signal_new ("stopped",
|
||||
G_TYPE_FROM_INTERFACE (iface),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
G_STRUCT_OFFSET (SpProfilerInterface, stopped),
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 0);
|
||||
|
||||
g_object_interface_install_property (iface,
|
||||
g_param_spec_double ("elapsed",
|
||||
"Elapsed",
|
||||
"The amount of elapsed time profiling",
|
||||
0,
|
||||
G_MAXDOUBLE,
|
||||
0,
|
||||
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_interface_install_property (iface,
|
||||
g_param_spec_boolean ("is-running",
|
||||
"Is Running",
|
||||
"If the profiler is currently running.",
|
||||
FALSE,
|
||||
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_interface_install_property (iface,
|
||||
g_param_spec_boolean ("is-mutable",
|
||||
"Is Mutable",
|
||||
"If the profiler can still be prepared.",
|
||||
TRUE,
|
||||
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_interface_install_property (iface,
|
||||
g_param_spec_boolean ("spawn-inherit-environ",
|
||||
"Spawn Inherit Environ",
|
||||
"If the spawned child should inherit the parents environment",
|
||||
TRUE,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_interface_install_property (iface,
|
||||
g_param_spec_boolean ("whole-system",
|
||||
"Whole System",
|
||||
"If the whole system should be profiled",
|
||||
TRUE,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_interface_install_property (iface,
|
||||
g_param_spec_boolean ("spawn",
|
||||
"Spawn",
|
||||
"If configured child should be spawned",
|
||||
TRUE,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_interface_install_property (iface,
|
||||
g_param_spec_boxed ("spawn-argv",
|
||||
"Spawn Argv",
|
||||
"The arguments for the spawn child",
|
||||
G_TYPE_STRV,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_interface_install_property (iface,
|
||||
g_param_spec_boxed ("spawn-env",
|
||||
"Spawn Environment",
|
||||
"The environment for the spawn child",
|
||||
G_TYPE_STRV,
|
||||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
}
|
||||
|
||||
gdouble
|
||||
sp_profiler_get_elapsed (SpProfiler *self)
|
||||
{
|
||||
gdouble value = 0.0;
|
||||
g_return_val_if_fail (SP_IS_PROFILER (self), 0.0);
|
||||
g_object_get (self, "elapsed", &value, NULL);
|
||||
return value;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_profiler_get_is_running (SpProfiler *self)
|
||||
{
|
||||
gboolean is_running = FALSE;
|
||||
g_return_val_if_fail (SP_IS_PROFILER (self), FALSE);
|
||||
g_object_get (self, "is-running", &is_running, NULL);
|
||||
return is_running;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_profiler_get_is_mutable (SpProfiler *self)
|
||||
{
|
||||
gboolean is_mutable = FALSE;
|
||||
g_return_val_if_fail (SP_IS_PROFILER (self), FALSE);
|
||||
g_object_get (self, "is-mutable", &is_mutable, NULL);
|
||||
return is_mutable;
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_profiler_get_spawn_inherit_environ (SpProfiler *self)
|
||||
{
|
||||
gboolean spawn_inherit_environ = FALSE;
|
||||
g_return_val_if_fail (SP_IS_PROFILER (self), FALSE);
|
||||
g_object_get (self, "spawn-inherit-environ", &spawn_inherit_environ, NULL);
|
||||
return spawn_inherit_environ;
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_set_spawn_inherit_environ (SpProfiler *self,
|
||||
gboolean spawn_inherit_environ)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_object_set (self, "spawn-inherit-environ", !!spawn_inherit_environ, NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_profiler_get_spawn (SpProfiler *self)
|
||||
{
|
||||
gboolean spawn = FALSE;
|
||||
g_return_val_if_fail (SP_IS_PROFILER (self), FALSE);
|
||||
g_object_get (self, "spawn", &spawn, NULL);
|
||||
return spawn;
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_set_spawn (SpProfiler *self,
|
||||
gboolean spawn)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_object_set (self, "spawn", !!spawn, NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_profiler_get_whole_system (SpProfiler *self)
|
||||
{
|
||||
gboolean whole_system = FALSE;
|
||||
g_return_val_if_fail (SP_IS_PROFILER (self), FALSE);
|
||||
g_object_get (self, "whole-system", &whole_system, NULL);
|
||||
return whole_system;
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_set_whole_system (SpProfiler *self,
|
||||
gboolean whole_system)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_object_set (self, "whole-system", !!whole_system, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_set_spawn_argv (SpProfiler *self,
|
||||
const gchar * const *spawn_argv)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_object_set (self, "spawn-argv", spawn_argv, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_set_spawn_env (SpProfiler *self,
|
||||
const gchar * const *spawn_env)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_object_set (self, "spawn-env", spawn_env, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_add_source (SpProfiler *self,
|
||||
SpSource *source)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_return_if_fail (SP_IS_SOURCE (source));
|
||||
|
||||
SP_PROFILER_GET_IFACE (self)->add_source (self, source);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_set_writer (SpProfiler *self,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_return_if_fail (writer != NULL);
|
||||
|
||||
SP_PROFILER_GET_IFACE (self)->set_writer (self, writer);
|
||||
}
|
||||
|
||||
SpCaptureWriter *
|
||||
sp_profiler_get_writer (SpProfiler *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_PROFILER (self), NULL);
|
||||
|
||||
return SP_PROFILER_GET_IFACE (self)->get_writer (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_start (SpProfiler *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
|
||||
SP_PROFILER_GET_IFACE (self)->start (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_stop (SpProfiler *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
|
||||
SP_PROFILER_GET_IFACE (self)->stop (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_add_pid (SpProfiler *self,
|
||||
GPid pid)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_return_if_fail (pid > -1);
|
||||
|
||||
SP_PROFILER_GET_IFACE (self)->add_pid (self, pid);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_remove_pid (SpProfiler *self,
|
||||
GPid pid)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_return_if_fail (pid > -1);
|
||||
|
||||
SP_PROFILER_GET_IFACE (self)->remove_pid (self, pid);
|
||||
}
|
||||
|
||||
const GPid *
|
||||
sp_profiler_get_pids (SpProfiler *self,
|
||||
guint *n_pids)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_PROFILER (self), NULL);
|
||||
g_return_val_if_fail (n_pids != NULL, NULL);
|
||||
|
||||
return SP_PROFILER_GET_IFACE (self)->get_pids (self, n_pids);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_emit_failed (SpProfiler *self,
|
||||
const GError *error)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
g_return_if_fail (error != NULL);
|
||||
|
||||
g_signal_emit (self, signals [FAILED], 0, error);
|
||||
}
|
||||
|
||||
void
|
||||
sp_profiler_emit_stopped (SpProfiler *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_PROFILER (self));
|
||||
|
||||
g_signal_emit (self, signals [STOPPED], 0);
|
||||
}
|
||||
161
src/libsysprof/sp-profiler.h
Normal file
161
src/libsysprof/sp-profiler.h
Normal file
@ -0,0 +1,161 @@
|
||||
/* sp-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_PROFILER_H
|
||||
#define SP_PROFILER_H
|
||||
|
||||
#include "sp-capture-writer.h"
|
||||
#include "sp-source.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_PROFILER (sp_profiler_get_type())
|
||||
|
||||
G_DECLARE_INTERFACE (SpProfiler, sp_profiler, SP, PROFILER, GObject)
|
||||
|
||||
struct _SpProfilerInterface
|
||||
{
|
||||
GTypeInterface parent_interface;
|
||||
|
||||
/**
|
||||
* SpProfiler::failed:
|
||||
* @self: A #SpProfiler
|
||||
* @reason: A #GError representing the reason for the failure
|
||||
*
|
||||
* This signal is emitted if the profiler failed. Note that
|
||||
* #SpProfiler::stopped will also be emitted, but does not allow for
|
||||
* receiving the error condition.
|
||||
*/
|
||||
void (*failed) (SpProfiler *self,
|
||||
const GError *error);
|
||||
|
||||
/**
|
||||
* SpProfiler::stopped:
|
||||
* @self: A #SpProfiler
|
||||
*
|
||||
* This signal is emitted when a profiler is stopped. It will always be
|
||||
* emitted after a sp_profiler_start() has been called, either after
|
||||
* completion of sp_profiler_stop() or after a failure or after asynchronous
|
||||
* completion of stopping.
|
||||
*/
|
||||
void (*stopped) (SpProfiler *self);
|
||||
|
||||
/**
|
||||
* SpProfiler::add_source:
|
||||
*
|
||||
* Adds a source to the profiler.
|
||||
*/
|
||||
void (*add_source) (SpProfiler *profiler,
|
||||
SpSource *source);
|
||||
|
||||
/**
|
||||
* SpProfiler::set_writer:
|
||||
*
|
||||
* Sets the writer to use for the profiler.
|
||||
*/
|
||||
void (*set_writer) (SpProfiler *self,
|
||||
SpCaptureWriter *writer);
|
||||
|
||||
/**
|
||||
* SpProfiler::get_writer:
|
||||
*
|
||||
* Gets the writer that is being used to capture.
|
||||
*
|
||||
* Returns: (nullable) (transfer none): A #SpCaptureWriter.
|
||||
*/
|
||||
SpCaptureWriter *(*get_writer) (SpProfiler *self);
|
||||
|
||||
/**
|
||||
* SpProfiler::start:
|
||||
*
|
||||
* Starts the profiler.
|
||||
*/
|
||||
void (*start) (SpProfiler *self);
|
||||
|
||||
/**
|
||||
* SpProfiler::stop:
|
||||
*
|
||||
* Stops the profiler.
|
||||
*/
|
||||
void (*stop) (SpProfiler *self);
|
||||
|
||||
/**
|
||||
* SpProfiler::add_pid:
|
||||
*
|
||||
* Add a pid to be profiled.
|
||||
*/
|
||||
void (*add_pid) (SpProfiler *self,
|
||||
GPid pid);
|
||||
|
||||
/**
|
||||
* SpProfiler::remove_pid:
|
||||
*
|
||||
* Remove a pid from the profiler. This will not be called after
|
||||
* SpProfiler::start has been called.
|
||||
*/
|
||||
void (*remove_pid) (SpProfiler *self,
|
||||
GPid pid);
|
||||
|
||||
/**
|
||||
* SpProfiler::get_pids:
|
||||
*
|
||||
* Gets the pids that are part of this profiling session. If no pids
|
||||
* have been specified, %NULL is returned.
|
||||
*
|
||||
* Returns: (nullable) (transfer none): An array of #GPid, or %NULL.
|
||||
*/
|
||||
const GPid *(*get_pids) (SpProfiler *self,
|
||||
guint *n_pids);
|
||||
};
|
||||
|
||||
void sp_profiler_emit_failed (SpProfiler *self,
|
||||
const GError *error);
|
||||
void sp_profiler_emit_stopped (SpProfiler *self);
|
||||
gdouble sp_profiler_get_elapsed (SpProfiler *self);
|
||||
gboolean sp_profiler_get_is_mutable (SpProfiler *self);
|
||||
gboolean sp_profiler_get_spawn_inherit_environ (SpProfiler *self);
|
||||
void sp_profiler_set_spawn_inherit_environ (SpProfiler *self,
|
||||
gboolean spawn_inherit_environ);
|
||||
gboolean sp_profiler_get_whole_system (SpProfiler *self);
|
||||
void sp_profiler_set_whole_system (SpProfiler *self,
|
||||
gboolean whole_system);
|
||||
gboolean sp_profiler_get_spawn (SpProfiler *self);
|
||||
void sp_profiler_set_spawn (SpProfiler *self,
|
||||
gboolean spawn);
|
||||
void sp_profiler_set_spawn_argv (SpProfiler *self,
|
||||
const gchar * const *spawn_argv);
|
||||
void sp_profiler_set_spawn_env (SpProfiler *self,
|
||||
const gchar * const *spawn_env);
|
||||
void sp_profiler_add_source (SpProfiler *self,
|
||||
SpSource *source);
|
||||
void sp_profiler_set_writer (SpProfiler *self,
|
||||
SpCaptureWriter *writer);
|
||||
SpCaptureWriter *sp_profiler_get_writer (SpProfiler *self);
|
||||
gboolean sp_profiler_get_is_running (SpProfiler *self);
|
||||
void sp_profiler_start (SpProfiler *self);
|
||||
void sp_profiler_stop (SpProfiler *self);
|
||||
void sp_profiler_add_pid (SpProfiler *self,
|
||||
GPid pid);
|
||||
void sp_profiler_remove_pid (SpProfiler *self,
|
||||
GPid pid);
|
||||
const GPid *sp_profiler_get_pids (SpProfiler *self,
|
||||
guint *n_pids);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_PROFILER_H */
|
||||
251
src/libsysprof/sp-selection.c
Normal file
251
src/libsysprof/sp-selection.c
Normal file
@ -0,0 +1,251 @@
|
||||
/* sp-selection.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-selection"
|
||||
|
||||
#include "sp-selection.h"
|
||||
|
||||
struct _SpSelection
|
||||
{
|
||||
GObject parent_instance;
|
||||
GArray *ranges;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gint64 begin;
|
||||
gint64 end;
|
||||
} Range;
|
||||
|
||||
G_DEFINE_TYPE (SpSelection, sp_selection, G_TYPE_OBJECT)
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_HAS_SELECTION,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
enum {
|
||||
CHANGED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static GParamSpec *properties [N_PROPS];
|
||||
static guint signals [N_SIGNALS];
|
||||
|
||||
static inline void
|
||||
int64_swap (gint64 *a,
|
||||
gint64 *b)
|
||||
{
|
||||
if (*a > *b)
|
||||
{
|
||||
gint64 tmp = *a;
|
||||
*a = *b;
|
||||
*b = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_selection_finalize (GObject *object)
|
||||
{
|
||||
SpSelection *self = (SpSelection *)object;
|
||||
|
||||
g_clear_pointer (&self->ranges, g_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (sp_selection_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_selection_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SpSelection *self = SP_SELECTION (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_HAS_SELECTION:
|
||||
g_value_set_boolean (value, sp_selection_get_has_selection (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sp_selection_class_init (SpSelectionClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = sp_selection_finalize;
|
||||
object_class->get_property = sp_selection_get_property;
|
||||
|
||||
properties [PROP_HAS_SELECTION] =
|
||||
g_param_spec_boolean ("has-selection",
|
||||
"Has Selection",
|
||||
"Has Selection",
|
||||
FALSE,
|
||||
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
|
||||
/**
|
||||
* SpSelection::changed:
|
||||
*
|
||||
* This signal is emitted when the selection has changed.
|
||||
*/
|
||||
signals [CHANGED] =
|
||||
g_signal_new ("changed",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
sp_selection_init (SpSelection *self)
|
||||
{
|
||||
self->ranges = g_array_new (FALSE, FALSE, sizeof (Range));
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_selection_get_has_selection (SpSelection *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_SELECTION (self), FALSE);
|
||||
|
||||
return self->ranges->len > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_selection_foreach:
|
||||
* @self: A #SpSelection
|
||||
* @foreach_func: (scope call): a callback for each range
|
||||
* @user_data: user data for @foreach_func
|
||||
*
|
||||
* Calls @foreach_func for every selected range.
|
||||
*/
|
||||
void
|
||||
sp_selection_foreach (SpSelection *self,
|
||||
SpSelectionForeachFunc foreach_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SELECTION (self));
|
||||
g_return_if_fail (foreach_func != NULL);
|
||||
|
||||
for (guint i = 0; i < self->ranges->len; i++)
|
||||
{
|
||||
const Range *range = &g_array_index (self->ranges, Range, i);
|
||||
foreach_func (self, range->begin, range->end, user_data);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_selection_select_range (SpSelection *self,
|
||||
gint64 begin_time,
|
||||
gint64 end_time)
|
||||
{
|
||||
Range range = { 0 };
|
||||
|
||||
g_return_if_fail (SP_IS_SELECTION (self));
|
||||
|
||||
int64_swap (&begin_time, &end_time);
|
||||
|
||||
range.begin = begin_time;
|
||||
range.end = end_time;
|
||||
|
||||
g_array_append_val (self->ranges, range);
|
||||
|
||||
if (self->ranges->len == 1)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SELECTION]);
|
||||
g_signal_emit (self, signals [CHANGED], 0);
|
||||
}
|
||||
|
||||
void
|
||||
sp_selection_unselect_range (SpSelection *self,
|
||||
gint64 begin,
|
||||
gint64 end)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SELECTION (self));
|
||||
|
||||
int64_swap (&begin, &end);
|
||||
|
||||
for (guint i = 0; i < self->ranges->len; i++)
|
||||
{
|
||||
const Range *range = &g_array_index (self->ranges, Range, i);
|
||||
|
||||
if (range->begin == begin && range->end == end)
|
||||
{
|
||||
g_array_remove_index (self->ranges, i);
|
||||
if (self->ranges->len == 0)
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SELECTION]);
|
||||
g_signal_emit (self, signals [CHANGED], 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sp_selection_unselect_all (SpSelection *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SELECTION (self));
|
||||
|
||||
if (self->ranges->len > 0)
|
||||
{
|
||||
g_array_remove_range (self->ranges, 0, self->ranges->len);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SELECTION]);
|
||||
g_signal_emit (self, signals [CHANGED], 0);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_selection_contains (SpSelection *self,
|
||||
gint64 time_at)
|
||||
{
|
||||
if (self == NULL || self->ranges->len == 0)
|
||||
return TRUE;
|
||||
|
||||
for (guint i = 0; i < self->ranges->len; i++)
|
||||
{
|
||||
const Range *range = &g_array_index (self->ranges, Range, i);
|
||||
|
||||
if (time_at >= range->begin && time_at <= range->end)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
SpSelection *
|
||||
sp_selection_copy (const SpSelection *self)
|
||||
{
|
||||
SpSelection *copy;
|
||||
|
||||
if (self == NULL)
|
||||
return NULL;
|
||||
|
||||
copy = g_object_new (SP_TYPE_SELECTION, NULL);
|
||||
|
||||
for (guint i = 0; i < self->ranges->len; i++)
|
||||
{
|
||||
Range range = g_array_index (self->ranges, Range, i);
|
||||
g_array_append_val (copy->ranges, range);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
52
src/libsysprof/sp-selection.h
Normal file
52
src/libsysprof/sp-selection.h
Normal file
@ -0,0 +1,52 @@
|
||||
/* sp-selection.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_SELECTION_H
|
||||
#define SP_SELECTION_H
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_SELECTION (sp_selection_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (SpSelection, sp_selection, SP, SELECTION, GObject)
|
||||
|
||||
typedef void (*SpSelectionForeachFunc) (SpSelection *self,
|
||||
gint64 begin_time,
|
||||
gint64 end_time,
|
||||
gpointer user_data);
|
||||
|
||||
gboolean sp_selection_get_has_selection (SpSelection *self);
|
||||
gboolean sp_selection_contains (SpSelection *self,
|
||||
gint64 time_at);
|
||||
void sp_selection_select_range (SpSelection *self,
|
||||
gint64 begin_time,
|
||||
gint64 end_time);
|
||||
void sp_selection_unselect_range (SpSelection *self,
|
||||
gint64 begin,
|
||||
gint64 end);
|
||||
void sp_selection_unselect_all (SpSelection *self);
|
||||
void sp_selection_foreach (SpSelection *self,
|
||||
SpSelectionForeachFunc foreach_func,
|
||||
gpointer user_data);
|
||||
SpSelection *sp_selection_copy (const SpSelection *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_SELECTION_H */
|
||||
34
src/libsysprof/sp-source-util-private.h
Normal file
34
src/libsysprof/sp-source-util-private.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* sp-source-util-private.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.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
gboolean sp_host_file_get_contents (const gchar *host_path,
|
||||
gchar **contents,
|
||||
gsize *len,
|
||||
GError **error);
|
||||
gchar **sp_host_list_directories (const gchar *directory,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
203
src/libsysprof/sp-source-util.c
Normal file
203
src/libsysprof/sp-source-util.c
Normal file
@ -0,0 +1,203 @@
|
||||
/* sp-source-util.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 <gio/gio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sp-source-util-private.h"
|
||||
|
||||
gboolean
|
||||
sp_host_file_get_contents (const gchar *host_path,
|
||||
gchar **contents,
|
||||
gsize *len,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree gchar *alt_path = NULL;
|
||||
|
||||
g_return_val_if_fail (host_path != NULL, FALSE);
|
||||
|
||||
if (!g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS))
|
||||
return g_file_get_contents (host_path, contents, len, error);
|
||||
|
||||
if (contents != NULL)
|
||||
*contents = NULL;
|
||||
|
||||
if (len != NULL)
|
||||
*len = 0;
|
||||
|
||||
alt_path = g_build_filename ("/var/run/host", host_path, NULL);
|
||||
|
||||
if (g_file_test (alt_path, G_FILE_TEST_EXISTS))
|
||||
return g_file_get_contents (alt_path, contents, len, error);
|
||||
|
||||
/*
|
||||
* Fallback to try to get it with "cat" on the host since we
|
||||
* may not have access (like for /proc) from /var/run/host.
|
||||
*/
|
||||
|
||||
{
|
||||
g_autoptr(GSubprocessLauncher) launcher = NULL;
|
||||
g_autoptr(GSubprocess) subprocess = NULL;
|
||||
g_autoptr(GBytes) stdout_buf = NULL;
|
||||
|
||||
launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE |
|
||||
G_SUBPROCESS_FLAGS_STDERR_SILENCE);
|
||||
|
||||
subprocess = g_subprocess_launcher_spawn (launcher, error,
|
||||
"flatpak-spawn",
|
||||
"--clear-env",
|
||||
"--host",
|
||||
"cat",
|
||||
host_path,
|
||||
NULL);
|
||||
|
||||
if (subprocess == NULL)
|
||||
return FALSE;
|
||||
|
||||
if (!g_subprocess_communicate (subprocess, NULL, NULL, &stdout_buf, NULL, error))
|
||||
return FALSE;
|
||||
|
||||
if (len != NULL)
|
||||
*len = g_bytes_get_size (stdout_buf);
|
||||
|
||||
if (contents != NULL)
|
||||
{
|
||||
const guint8 *data;
|
||||
gsize n;
|
||||
|
||||
/* g_file_get_contents() gurantees a trailing null byte */
|
||||
data = g_bytes_get_data (stdout_buf, &n);
|
||||
*contents = g_malloc (n + 1);
|
||||
memcpy (*contents, data, n);
|
||||
(*contents)[n] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gchar **
|
||||
sp_host_list_directories (const gchar *directory,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree gchar *alt_path = NULL;
|
||||
|
||||
g_return_val_if_fail (directory != NULL, NULL);
|
||||
|
||||
if (g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
g_autoptr(GSubprocessLauncher) launcher = NULL;
|
||||
g_autoptr(GSubprocess) subprocess = NULL;
|
||||
g_autofree gchar *stdout_buf = NULL;
|
||||
g_auto(GStrv) lines = NULL;
|
||||
gsize len;
|
||||
guint j = 0;
|
||||
|
||||
alt_path = g_build_filename ("/var/run/host", directory, NULL);
|
||||
|
||||
if (g_file_test (alt_path, G_FILE_TEST_IS_DIR))
|
||||
{
|
||||
directory = alt_path;
|
||||
goto try_native;
|
||||
}
|
||||
|
||||
launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE |
|
||||
G_SUBPROCESS_FLAGS_STDERR_SILENCE);
|
||||
subprocess = g_subprocess_launcher_spawn (launcher, error,
|
||||
"flatpak-spawn",
|
||||
"--clear-env",
|
||||
"--host",
|
||||
"ls",
|
||||
"-1",
|
||||
"-U",
|
||||
"--file-type",
|
||||
directory,
|
||||
NULL);
|
||||
if (subprocess == NULL)
|
||||
return NULL;
|
||||
|
||||
if (!g_subprocess_communicate_utf8 (subprocess, NULL, NULL, &stdout_buf, NULL, error))
|
||||
return NULL;
|
||||
|
||||
lines = g_strsplit (stdout_buf, "\n", 0);
|
||||
len = g_strv_length (lines);
|
||||
|
||||
for (gsize i = 0; i < len; i++)
|
||||
{
|
||||
gsize llen = strlen (lines[i]);
|
||||
|
||||
if (llen == 0 || lines[i][llen-1] != '/')
|
||||
{
|
||||
/* Remove this entry, we'll compress the list later */
|
||||
g_free (lines[i]);
|
||||
lines[i] = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* trim trailing / */
|
||||
lines[i][llen-1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compress the list by removing NULL links */
|
||||
for (gsize i = 0; i < len; i++)
|
||||
{
|
||||
if (lines[i] == NULL)
|
||||
{
|
||||
if (j <= i)
|
||||
j = i + 1;
|
||||
|
||||
for (; j < len; j++)
|
||||
{
|
||||
if (lines[j] != NULL)
|
||||
{
|
||||
lines[i] = g_steal_pointer (&lines[j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return g_steal_pointer (&lines);
|
||||
}
|
||||
|
||||
try_native:
|
||||
|
||||
{
|
||||
g_autoptr(GDir) dir = g_dir_open (directory, 0, error);
|
||||
g_autoptr(GPtrArray) dirs = g_ptr_array_new_with_free_func (g_free);
|
||||
const gchar *name;
|
||||
|
||||
if (dir == NULL)
|
||||
return NULL;
|
||||
|
||||
while ((name = g_dir_read_name (dir)))
|
||||
{
|
||||
g_autofree gchar *path = g_build_filename (directory, name, NULL);
|
||||
|
||||
if (g_file_test (path, G_FILE_TEST_IS_DIR))
|
||||
g_ptr_array_add (dirs, g_steal_pointer (&path));
|
||||
}
|
||||
|
||||
g_ptr_array_add (dirs, NULL);
|
||||
|
||||
return (gchar **)g_ptr_array_free (g_steal_pointer (&dirs), FALSE);
|
||||
}
|
||||
}
|
||||
137
src/libsysprof/sp-source.c
Normal file
137
src/libsysprof/sp-source.c
Normal file
@ -0,0 +1,137 @@
|
||||
/* sp-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 "sp-source.h"
|
||||
|
||||
G_DEFINE_INTERFACE (SpSource, sp_source, G_TYPE_OBJECT)
|
||||
|
||||
enum {
|
||||
FAILED,
|
||||
FINISHED,
|
||||
READY,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint signals [N_SIGNALS];
|
||||
|
||||
static void
|
||||
sp_source_default_init (SpSourceInterface *iface)
|
||||
{
|
||||
signals [FAILED] = g_signal_new ("failed",
|
||||
G_TYPE_FROM_INTERFACE (iface),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE, 1, G_TYPE_ERROR);
|
||||
|
||||
signals [FINISHED] = g_signal_new ("finished",
|
||||
G_TYPE_FROM_INTERFACE (iface),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
|
||||
signals [READY] = g_signal_new ("ready",
|
||||
G_TYPE_FROM_INTERFACE (iface),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_add_pid (SpSource *self,
|
||||
GPid pid)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
g_return_if_fail (pid != FALSE);
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->add_pid)
|
||||
SP_SOURCE_GET_IFACE (self)->add_pid (self, pid);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_emit_finished (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
g_signal_emit (self, signals [FINISHED], 0);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_emit_failed (SpSource *self,
|
||||
const GError *error)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
g_return_if_fail (error != NULL);
|
||||
|
||||
g_signal_emit (self, signals [FAILED], 0, error);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_emit_ready (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
g_signal_emit (self, signals [READY], 0);
|
||||
}
|
||||
|
||||
gboolean
|
||||
sp_source_get_is_ready (SpSource *self)
|
||||
{
|
||||
g_return_val_if_fail (SP_IS_SOURCE (self), FALSE);
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->get_is_ready)
|
||||
return SP_SOURCE_GET_IFACE (self)->get_is_ready (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_prepare (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->prepare)
|
||||
SP_SOURCE_GET_IFACE (self)->prepare (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_set_writer (SpSource *self,
|
||||
SpCaptureWriter *writer)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
g_return_if_fail (writer != NULL);
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->set_writer)
|
||||
SP_SOURCE_GET_IFACE (self)->set_writer (self, writer);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_start (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->start)
|
||||
SP_SOURCE_GET_IFACE (self)->start (self);
|
||||
}
|
||||
|
||||
void
|
||||
sp_source_stop (SpSource *self)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SOURCE (self));
|
||||
|
||||
if (SP_SOURCE_GET_IFACE (self)->stop)
|
||||
SP_SOURCE_GET_IFACE (self)->stop (self);
|
||||
}
|
||||
133
src/libsysprof/sp-source.h
Normal file
133
src/libsysprof/sp-source.h
Normal file
@ -0,0 +1,133 @@
|
||||
/* sp-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_SOURCE_H
|
||||
#define SP_SOURCE_H
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "sp-capture-writer.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_SOURCE (sp_source_get_type())
|
||||
|
||||
G_DECLARE_INTERFACE (SpSource, sp_source, SP, SOURCE, GObject)
|
||||
|
||||
struct _SpSourceInterface
|
||||
{
|
||||
GTypeInterface parent_iface;
|
||||
|
||||
/**
|
||||
* SpSource::get_is_ready:
|
||||
* @self: A SpSource.
|
||||
*
|
||||
* This function should return %TRUE if the source is ready to start
|
||||
* profiling. If the source is not ready until after sp_source_start() has
|
||||
* been called, use sp_source_emit_ready() to notify the profiler that the
|
||||
* source is ready for profiling.
|
||||
*
|
||||
* Returns: %TRUE if the source is ready to start profiling.
|
||||
*/
|
||||
gboolean (*get_is_ready) (SpSource *self);
|
||||
|
||||
/**
|
||||
* SpSource::set_writer:
|
||||
* @self: A #SpSource.
|
||||
* @writer: A #SpCaptureWriter
|
||||
*
|
||||
* Sets the #SpCaptureWriter to use when profiling. @writer is only safe to
|
||||
* use from the main thread. If you need to capture from a thread, you should
|
||||
* create a memory-based #SpCaptureWriter and then splice that into this
|
||||
* writer from the main thread when profiling completes.
|
||||
*
|
||||
* See sp_capture_writer_splice() for information on splicing writers.
|
||||
*/
|
||||
void (*set_writer) (SpSource *self,
|
||||
SpCaptureWriter *writer);
|
||||
|
||||
/**
|
||||
* SpSource::prepare:
|
||||
*
|
||||
* This function is called before profiling has started. The source should
|
||||
* prepare any pre-profiling setup here. It may perform this work
|
||||
* asynchronously, but must g_object_notify() the SpSource::is-ready
|
||||
* property once that asynchronous work has been performed. Until it
|
||||
* is ready, #SpSource::is-ready must return FALSE.
|
||||
*/
|
||||
void (*prepare) (SpSource *self);
|
||||
|
||||
/**
|
||||
* SpSource::add_pid:
|
||||
* @self: A #SpSource
|
||||
* @pid: A pid_t > -1
|
||||
*
|
||||
* This function is used to notify the #SpSource that a new process,
|
||||
* identified by @pid, should be profiled. By default, sources should
|
||||
* assume all processes, and only restrict to a given set of pids if
|
||||
* this function is called.
|
||||
*/
|
||||
void (*add_pid) (SpSource *self,
|
||||
GPid pid);
|
||||
|
||||
/**
|
||||
* SpSource::start:
|
||||
* @self: A #SpSource.
|
||||
*
|
||||
* Start profiling as configured.
|
||||
*
|
||||
* If a failure occurs while processing, the source should notify the
|
||||
* profiling session via sp_source_emit_failed() from the main thread.
|
||||
*/
|
||||
void (*start) (SpSource *self);
|
||||
|
||||
/**
|
||||
* SpSource::stop:
|
||||
* @self: A #SpSource.
|
||||
*
|
||||
* Stop capturing a profile. The source should immediately stop
|
||||
* profiling and perform any cleanup tasks required. If doing
|
||||
* off-main-thread capturing, this is a good time to splice your
|
||||
* capture into the capture file set with sp_source_set_writer().
|
||||
*
|
||||
* If you need to perform asynchronous cleanup, call
|
||||
* sp_source_emit_finished() once that work has completed. If you do
|
||||
* not need to perform asynchronous cleanup, call
|
||||
* sp_source_emit_finished() from this function.
|
||||
*
|
||||
* sp_source_emit_finished() must be called from the main-thread.
|
||||
*/
|
||||
void (*stop) (SpSource *self);
|
||||
};
|
||||
|
||||
void sp_source_add_pid (SpSource *self,
|
||||
GPid pid);
|
||||
void sp_source_emit_ready (SpSource *self);
|
||||
void sp_source_emit_finished (SpSource *self);
|
||||
void sp_source_emit_failed (SpSource *self,
|
||||
const GError *error);
|
||||
gboolean sp_source_get_is_ready (SpSource *self);
|
||||
void sp_source_prepare (SpSource *self);
|
||||
void sp_source_set_writer (SpSource *self,
|
||||
SpCaptureWriter *writer);
|
||||
void sp_source_start (SpSource *self);
|
||||
void sp_source_stop (SpSource *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_SOURCE_H */
|
||||
138
src/libsysprof/sp-symbol-dirs.c
Normal file
138
src/libsysprof/sp-symbol-dirs.c
Normal file
@ -0,0 +1,138 @@
|
||||
/* sp-symbol-dirs.c
|
||||
*
|
||||
* Copyright 2017 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 "config.h"
|
||||
|
||||
#include "sp-symbol-dirs.h"
|
||||
|
||||
static GPtrArray *sp_symbol_dirs;
|
||||
|
||||
G_LOCK_DEFINE (sp_symbol_dirs);
|
||||
|
||||
static GPtrArray *
|
||||
sp_get_symbol_dirs_locked (void)
|
||||
{
|
||||
if (sp_symbol_dirs == NULL)
|
||||
{
|
||||
sp_symbol_dirs = g_ptr_array_new ();
|
||||
g_ptr_array_add (sp_symbol_dirs, g_strdup ("/usr/lib/debug"));
|
||||
|
||||
/* Add path to host system if we have it */
|
||||
if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS))
|
||||
{
|
||||
static gchar *tries[] = {
|
||||
"/var/run/host/usr/lib/debug",
|
||||
};
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (tries); i++)
|
||||
{
|
||||
if (g_file_test (tries[i], G_FILE_TEST_EXISTS))
|
||||
g_ptr_array_add (sp_symbol_dirs, g_strdup (tries[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sp_symbol_dirs;
|
||||
}
|
||||
|
||||
void
|
||||
sp_symbol_dirs_add (const gchar *path)
|
||||
{
|
||||
GPtrArray *ar;
|
||||
|
||||
G_LOCK (sp_symbol_dirs);
|
||||
|
||||
ar = sp_get_symbol_dirs_locked ();
|
||||
|
||||
for (guint i = 0; i < ar->len; i++)
|
||||
{
|
||||
const gchar *ele = g_ptr_array_index (ar, i);
|
||||
|
||||
if (g_strcmp0 (path, ele) == 0)
|
||||
goto skip;
|
||||
}
|
||||
|
||||
g_ptr_array_add (ar, g_strdup (path));
|
||||
|
||||
skip:
|
||||
G_UNLOCK (sp_symbol_dirs);
|
||||
}
|
||||
|
||||
void
|
||||
sp_symbol_dirs_remove (const gchar *path)
|
||||
{
|
||||
GPtrArray *ar;
|
||||
|
||||
G_LOCK (sp_symbol_dirs);
|
||||
|
||||
ar = sp_get_symbol_dirs_locked ();
|
||||
|
||||
for (guint i = 0; i < ar->len; i++)
|
||||
{
|
||||
const gchar *ele = g_ptr_array_index (ar, i);
|
||||
|
||||
if (g_strcmp0 (path, ele) == 0)
|
||||
{
|
||||
g_ptr_array_remove_index (ar, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
G_UNLOCK (sp_symbol_dirs);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_symbol_dirs_get_paths:
|
||||
* @dir: the directory containing the library
|
||||
* @name: the name of the file in @dir
|
||||
*
|
||||
* This function will build an array of files to look at to resolve the
|
||||
* debug symbols for the file at path "dir/name".
|
||||
*
|
||||
* Returns: (transfer full): A #GStrv of possible paths.
|
||||
*/
|
||||
gchar **
|
||||
sp_symbol_dirs_get_paths (const gchar *dir,
|
||||
const gchar *name)
|
||||
{
|
||||
GPtrArray *ret = g_ptr_array_new ();
|
||||
GPtrArray *ar;
|
||||
|
||||
g_ptr_array_add (ret, g_build_filename (dir, name, NULL));
|
||||
|
||||
G_LOCK (sp_symbol_dirs);
|
||||
|
||||
ar = sp_get_symbol_dirs_locked ();
|
||||
|
||||
for (guint i = 0; i < ar->len; i++)
|
||||
{
|
||||
const gchar *ele = g_ptr_array_index (ar, i);
|
||||
|
||||
g_ptr_array_add (ret, g_build_filename (ele, name, NULL));
|
||||
g_ptr_array_add (ret, g_build_filename (ele, dir, name, NULL));
|
||||
}
|
||||
|
||||
g_ptr_array_add (ret, g_build_filename (dir, ".debug", name, NULL));
|
||||
g_ptr_array_add (ret, g_build_filename (DEBUGDIR, dir, name, NULL));
|
||||
|
||||
G_UNLOCK (sp_symbol_dirs);
|
||||
|
||||
g_ptr_array_add (ret, NULL);
|
||||
|
||||
return (gchar **)g_ptr_array_free (ret, FALSE);
|
||||
}
|
||||
33
src/libsysprof/sp-symbol-dirs.h
Normal file
33
src/libsysprof/sp-symbol-dirs.h
Normal file
@ -0,0 +1,33 @@
|
||||
/* sp-symbol-dirs.h
|
||||
*
|
||||
* Copyright 2017 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_SYMBOL_DIRS_H
|
||||
#define SP_SYMBOL_DIRS_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
void sp_symbol_dirs_add (const gchar *dir);
|
||||
void sp_symbol_dirs_remove (const gchar *dir);
|
||||
gchar **sp_symbol_dirs_get_paths (const gchar *dir,
|
||||
const gchar *name);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_SYMBOL_DIRS_H */
|
||||
140
src/libsysprof/sp-symbol-resolver.c
Normal file
140
src/libsysprof/sp-symbol-resolver.c
Normal file
@ -0,0 +1,140 @@
|
||||
/* sp-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-symbol-resolver.h"
|
||||
|
||||
G_DEFINE_INTERFACE (SpSymbolResolver, sp_symbol_resolver, G_TYPE_OBJECT)
|
||||
|
||||
static gchar *
|
||||
sp_symbol_resolver_real_resolve (SpSymbolResolver *self,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
*tag = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
sp_symbol_resolver_real_resolve_with_context (SpSymbolResolver *self,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpAddressContext context,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
*tag = 0;
|
||||
|
||||
if (SP_SYMBOL_RESOLVER_GET_IFACE (self)->resolve)
|
||||
return SP_SYMBOL_RESOLVER_GET_IFACE (self)->resolve (self, time, pid, address, tag);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
sp_symbol_resolver_default_init (SpSymbolResolverInterface *iface)
|
||||
{
|
||||
iface->resolve = sp_symbol_resolver_real_resolve;
|
||||
iface->resolve_with_context = sp_symbol_resolver_real_resolve_with_context;
|
||||
}
|
||||
|
||||
void
|
||||
sp_symbol_resolver_load (SpSymbolResolver *self,
|
||||
SpCaptureReader *reader)
|
||||
{
|
||||
g_return_if_fail (SP_IS_SYMBOL_RESOLVER (self));
|
||||
g_return_if_fail (reader != NULL);
|
||||
|
||||
if (SP_SYMBOL_RESOLVER_GET_IFACE (self)->load)
|
||||
SP_SYMBOL_RESOLVER_GET_IFACE (self)->load (self, reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_symbol_resolver_resolve:
|
||||
* @self: A #SpSymbolResolver
|
||||
* @time: The time of the sample
|
||||
* @pid: The process generating the sample
|
||||
* @address: the sample address
|
||||
* @tag: (out): A tag for the symbol.
|
||||
*
|
||||
* Gets the symbol name for @address that was part of process @pid
|
||||
* at @time. Optionally, you can set @tag to a quark describing the
|
||||
* symbol. This can be used to provide a bit more information when
|
||||
* rendering the treeview. You might choose to describe the library
|
||||
* such as "GObject" or "GTK+" or "Linux" for the kernel.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): A newly allocated string, or %NULL.
|
||||
*/
|
||||
gchar *
|
||||
sp_symbol_resolver_resolve (SpSymbolResolver *self,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
GQuark dummy;
|
||||
|
||||
g_return_val_if_fail (SP_IS_SYMBOL_RESOLVER (self), NULL);
|
||||
|
||||
if (tag == NULL)
|
||||
tag = &dummy;
|
||||
|
||||
*tag = 0;
|
||||
|
||||
if (SP_SYMBOL_RESOLVER_GET_IFACE (self)->resolve)
|
||||
return SP_SYMBOL_RESOLVER_GET_IFACE (self)->resolve (self, time, pid, address, tag);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* sp_symbol_resolver_resolve_with_context:
|
||||
* @self: A #SpSymbolResolver
|
||||
* @time: The time of the sample
|
||||
* @pid: The process generating the sample
|
||||
* @context: the address context
|
||||
* @address: the sample address
|
||||
* @tag: (out): A tag for the symbol.
|
||||
*
|
||||
* This function is like sp_symbol_resolver_resolve() but allows
|
||||
* access to the address context, which might be necessary to
|
||||
* determine the difference between user space and kernel space
|
||||
* addresses.
|
||||
*
|
||||
* Returns: (nullable) (transfer full): A newly allocated string, or %NULL.
|
||||
*/
|
||||
gchar *
|
||||
sp_symbol_resolver_resolve_with_context (SpSymbolResolver *self,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpAddressContext context,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag)
|
||||
{
|
||||
GQuark dummy;
|
||||
|
||||
g_return_val_if_fail (SP_IS_SYMBOL_RESOLVER (self), NULL);
|
||||
|
||||
if (tag == NULL)
|
||||
tag = &dummy;
|
||||
|
||||
*tag = 0;
|
||||
|
||||
return SP_SYMBOL_RESOLVER_GET_IFACE (self)->resolve_with_context (self, time, pid, context, address, tag);
|
||||
}
|
||||
68
src/libsysprof/sp-symbol-resolver.h
Normal file
68
src/libsysprof/sp-symbol-resolver.h
Normal file
@ -0,0 +1,68 @@
|
||||
/* sp-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_SYMBOL_RESOLVER_H
|
||||
#define SP_SYMBOL_RESOLVER_H
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "sp-address.h"
|
||||
#include "sp-capture-reader.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SP_TYPE_SYMBOL_RESOLVER (sp_symbol_resolver_get_type())
|
||||
|
||||
G_DECLARE_INTERFACE (SpSymbolResolver, sp_symbol_resolver, SP, SYMBOL_RESOLVER, GObject)
|
||||
|
||||
struct _SpSymbolResolverInterface
|
||||
{
|
||||
GTypeInterface parent_interface;
|
||||
|
||||
void (*load) (SpSymbolResolver *self,
|
||||
SpCaptureReader *reader);
|
||||
gchar *(*resolve) (SpSymbolResolver *self,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag);
|
||||
gchar *(*resolve_with_context) (SpSymbolResolver *self,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpAddressContext context,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag);
|
||||
};
|
||||
|
||||
void sp_symbol_resolver_load (SpSymbolResolver *self,
|
||||
SpCaptureReader *reader);
|
||||
gchar *sp_symbol_resolver_resolve (SpSymbolResolver *self,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag);
|
||||
gchar *sp_symbol_resolver_resolve_with_context (SpSymbolResolver *self,
|
||||
guint64 time,
|
||||
GPid pid,
|
||||
SpAddressContext context,
|
||||
SpCaptureAddress address,
|
||||
GQuark *tag);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* SP_SYMBOL_RESOLVER_H */
|
||||
387
src/libsysprof/stackstash.c
Normal file
387
src/libsysprof/stackstash.c
Normal file
@ -0,0 +1,387 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
#include "stackstash.h"
|
||||
|
||||
struct StackStash
|
||||
{
|
||||
int ref_count;
|
||||
StackNode * root;
|
||||
GHashTable * nodes_by_data;
|
||||
GDestroyNotify destroy;
|
||||
|
||||
StackNode * cached_nodes;
|
||||
GPtrArray * blocks;
|
||||
};
|
||||
|
||||
static void
|
||||
decorate_node (StackNode *node,
|
||||
StackStash *stash)
|
||||
{
|
||||
StackNode *n;
|
||||
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
decorate_node (node->siblings, stash);
|
||||
decorate_node (node->children, stash);
|
||||
|
||||
node->next = g_hash_table_lookup (stash->nodes_by_data, &node->data);
|
||||
g_hash_table_insert (stash->nodes_by_data, &node->data, node);
|
||||
|
||||
/* FIXME: This could be done more efficiently
|
||||
* by keeping track of the ancestors we have seen.
|
||||
*/
|
||||
node->toplevel = TRUE;
|
||||
for (n = node->parent; n != NULL; n = n->parent)
|
||||
{
|
||||
if (n->data == node->data)
|
||||
{
|
||||
node->toplevel = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
address_hash (gconstpointer key)
|
||||
{
|
||||
const uint64_t *addr = key;
|
||||
|
||||
return *addr;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
address_equal (gconstpointer key1, gconstpointer key2)
|
||||
{
|
||||
const uint64_t *addr1 = key1;
|
||||
const uint64_t *addr2 = key2;
|
||||
|
||||
return *addr1 == *addr2;
|
||||
}
|
||||
|
||||
static void
|
||||
stack_stash_decorate (StackStash *stash)
|
||||
{
|
||||
if (stash->nodes_by_data)
|
||||
return;
|
||||
|
||||
stash->nodes_by_data = g_hash_table_new (address_hash, address_equal);
|
||||
|
||||
decorate_node (stash->root, stash);
|
||||
}
|
||||
|
||||
static void
|
||||
free_key (gpointer key,
|
||||
gpointer value,
|
||||
gpointer data)
|
||||
{
|
||||
GDestroyNotify destroy = data;
|
||||
uint64_t u64 = *(uint64_t *)key;
|
||||
|
||||
destroy (U64_TO_POINTER (u64));
|
||||
}
|
||||
|
||||
static void
|
||||
stack_stash_undecorate (StackStash *stash)
|
||||
{
|
||||
if (stash->nodes_by_data)
|
||||
{
|
||||
if (stash->destroy)
|
||||
{
|
||||
g_hash_table_foreach (
|
||||
stash->nodes_by_data, free_key, stash->destroy);
|
||||
}
|
||||
|
||||
g_hash_table_destroy (stash->nodes_by_data);
|
||||
stash->nodes_by_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static GHashTable *
|
||||
get_nodes_by_data (StackStash *stash)
|
||||
{
|
||||
if (!stash->nodes_by_data)
|
||||
stack_stash_decorate (stash);
|
||||
|
||||
return stash->nodes_by_data;
|
||||
}
|
||||
|
||||
StackNode *
|
||||
stack_node_new (StackStash *stash)
|
||||
{
|
||||
StackNode *node;
|
||||
|
||||
if (!stash->cached_nodes)
|
||||
{
|
||||
#define BLOCK_SIZE 32768
|
||||
#define N_NODES (BLOCK_SIZE / sizeof (StackNode))
|
||||
|
||||
StackNode *block = g_malloc (BLOCK_SIZE);
|
||||
guint i;
|
||||
|
||||
for (i = 0; i < N_NODES; ++i)
|
||||
{
|
||||
block[i].next = stash->cached_nodes;
|
||||
stash->cached_nodes = &(block[i]);
|
||||
}
|
||||
|
||||
g_ptr_array_add (stash->blocks, block);
|
||||
}
|
||||
|
||||
node = stash->cached_nodes;
|
||||
stash->cached_nodes = node->next;
|
||||
|
||||
node->siblings = NULL;
|
||||
node->children = NULL;
|
||||
node->data = 0;
|
||||
node->parent = NULL;
|
||||
node->size = 0;
|
||||
node->next = NULL;
|
||||
node->total = 0;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/* "destroy", if non-NULL, is called once on every address */
|
||||
static StackStash *
|
||||
create_stack_stash (GDestroyNotify destroy)
|
||||
{
|
||||
StackStash *stash = g_new (StackStash, 1);
|
||||
|
||||
stash->root = NULL;
|
||||
stash->nodes_by_data = NULL;
|
||||
stash->ref_count = 1;
|
||||
stash->destroy = destroy;
|
||||
|
||||
stash->cached_nodes = NULL;
|
||||
stash->blocks = g_ptr_array_new ();
|
||||
|
||||
return stash;
|
||||
}
|
||||
|
||||
/* Stach */
|
||||
StackStash *
|
||||
stack_stash_new (GDestroyNotify destroy)
|
||||
{
|
||||
return create_stack_stash (destroy);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
stack_stash_free (StackStash *stash)
|
||||
{
|
||||
guint i;
|
||||
|
||||
stack_stash_undecorate (stash);
|
||||
|
||||
for (i = 0; i < stash->blocks->len; ++i)
|
||||
g_free (stash->blocks->pdata[i]);
|
||||
|
||||
g_ptr_array_free (stash->blocks, TRUE);
|
||||
|
||||
g_free (stash);
|
||||
}
|
||||
|
||||
StackNode *
|
||||
stack_stash_add_trace (StackStash *stash,
|
||||
const uint64_t *addrs,
|
||||
int n_addrs,
|
||||
int size)
|
||||
{
|
||||
StackNode **location = &(stash->root);
|
||||
StackNode *parent = NULL;
|
||||
int i;
|
||||
|
||||
if (!n_addrs)
|
||||
return NULL;
|
||||
|
||||
if (stash->nodes_by_data)
|
||||
stack_stash_undecorate (stash);
|
||||
|
||||
for (i = n_addrs - 1; i >= 0; --i)
|
||||
{
|
||||
StackNode *match = NULL;
|
||||
StackNode *prev;
|
||||
|
||||
/* FIXME: On x86-64 we don't get proper stacktraces which means
|
||||
* each node can have tons of children. That makes this loop
|
||||
* here show up on profiles.
|
||||
*
|
||||
* Not sure what can be done about it aside from actually fixing
|
||||
* x86-64 to get stacktraces.
|
||||
*/
|
||||
prev = NULL;
|
||||
for (match = *location; match; prev = match, match = match->siblings)
|
||||
{
|
||||
if (match->data == addrs[i])
|
||||
{
|
||||
if (prev)
|
||||
{
|
||||
/* move to front */
|
||||
prev->siblings = match->siblings;
|
||||
match->siblings = *location;
|
||||
*location = match;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
match = stack_node_new (stash);
|
||||
match->data = addrs[i];
|
||||
match->siblings = *location;
|
||||
match->parent = parent;
|
||||
*location = match;
|
||||
}
|
||||
|
||||
match->total += size;
|
||||
|
||||
location = &(match->children);
|
||||
parent = match;
|
||||
}
|
||||
|
||||
parent->size += size;
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
static void
|
||||
do_callback (StackNode *node,
|
||||
StackLink *trace,
|
||||
StackFunction func,
|
||||
gpointer data)
|
||||
{
|
||||
StackLink link;
|
||||
|
||||
if (trace)
|
||||
trace->prev = &link;
|
||||
|
||||
link.next = trace;
|
||||
link.prev = NULL;
|
||||
|
||||
while (node)
|
||||
{
|
||||
link.data = node->data;
|
||||
|
||||
if (node->size)
|
||||
func (&link, node->size, data);
|
||||
|
||||
do_callback (node->children, &link, func, data);
|
||||
|
||||
node = node->siblings;
|
||||
}
|
||||
|
||||
if (trace)
|
||||
trace->prev = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
stack_stash_foreach (StackStash *stash,
|
||||
StackFunction stack_func,
|
||||
gpointer data)
|
||||
{
|
||||
do_callback (stash->root, NULL, stack_func, data);
|
||||
}
|
||||
|
||||
void
|
||||
stack_node_foreach_trace (StackNode *node,
|
||||
StackFunction func,
|
||||
gpointer data)
|
||||
{
|
||||
StackLink link;
|
||||
|
||||
link.next = NULL;
|
||||
link.data = node->data;
|
||||
link.prev = NULL;
|
||||
|
||||
if (node->size)
|
||||
func (&link, node->size, data);
|
||||
|
||||
do_callback (node->children, &link, func, data);
|
||||
}
|
||||
|
||||
void
|
||||
stack_stash_unref (StackStash *stash)
|
||||
{
|
||||
stash->ref_count--;
|
||||
if (stash->ref_count == 0)
|
||||
stack_stash_free (stash);
|
||||
}
|
||||
|
||||
StackStash *
|
||||
stack_stash_ref (StackStash *stash)
|
||||
{
|
||||
stash->ref_count++;
|
||||
return stash;
|
||||
}
|
||||
|
||||
StackNode *
|
||||
stack_stash_find_node (StackStash *stash,
|
||||
gpointer data)
|
||||
{
|
||||
uint64_t u64 = POINTER_TO_U64 (data);
|
||||
|
||||
g_return_val_if_fail (stash != NULL, NULL);
|
||||
|
||||
return g_hash_table_lookup (get_nodes_by_data (stash), &u64);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
StackNodeFunc func;
|
||||
gpointer data;
|
||||
} Info;
|
||||
|
||||
static void
|
||||
do_foreach (gpointer key, gpointer value, gpointer data)
|
||||
{
|
||||
Info *info = data;
|
||||
|
||||
info->func (value, info->data);
|
||||
}
|
||||
|
||||
void
|
||||
stack_stash_foreach_by_address (StackStash *stash,
|
||||
StackNodeFunc func,
|
||||
gpointer data)
|
||||
{
|
||||
Info info;
|
||||
info.func = func;
|
||||
info.data = data;
|
||||
|
||||
g_hash_table_foreach (get_nodes_by_data (stash), do_foreach, &info);
|
||||
}
|
||||
|
||||
StackNode *
|
||||
stack_stash_get_root (StackStash *stash)
|
||||
{
|
||||
return stash->root;
|
||||
}
|
||||
|
||||
void
|
||||
stack_stash_set_root (StackStash *stash,
|
||||
StackNode *root)
|
||||
{
|
||||
g_return_if_fail (stash->root == NULL);
|
||||
|
||||
stash->root = root;
|
||||
}
|
||||
85
src/libsysprof/stackstash.h
Normal file
85
src/libsysprof/stackstash.h
Normal file
@ -0,0 +1,85 @@
|
||||
/* 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 STACK_STASH_H
|
||||
#define STACK_STASH_H
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct StackStash StackStash;
|
||||
typedef struct StackNode StackNode;
|
||||
typedef struct StackLink StackLink;
|
||||
|
||||
#define U64_TO_POINTER(u) ((void *)(intptr_t)u)
|
||||
#define POINTER_TO_U64(p) ((uint64_t)(intptr_t)p)
|
||||
|
||||
struct StackNode
|
||||
{
|
||||
uint64_t data;
|
||||
|
||||
guint total : 32;
|
||||
guint size : 31;
|
||||
guint toplevel : 1;
|
||||
|
||||
StackNode * parent;
|
||||
StackNode * siblings;
|
||||
StackNode * children;
|
||||
|
||||
StackNode * next;
|
||||
};
|
||||
|
||||
struct StackLink
|
||||
{
|
||||
uint64_t data;
|
||||
StackLink *next;
|
||||
StackLink *prev;
|
||||
};
|
||||
|
||||
typedef void (* StackFunction) (StackLink *trace,
|
||||
gint size,
|
||||
gpointer data);
|
||||
|
||||
typedef void (* StackNodeFunc) (StackNode *node,
|
||||
gpointer data);
|
||||
|
||||
StackStash *stack_stash_new (GDestroyNotify destroy);
|
||||
StackNode *stack_node_new (StackStash *stash);
|
||||
StackNode *stack_stash_add_trace (StackStash *stash,
|
||||
const uint64_t *addrs,
|
||||
gint n_addrs,
|
||||
int size);
|
||||
void stack_stash_foreach (StackStash *stash,
|
||||
StackFunction stack_func,
|
||||
gpointer data);
|
||||
void stack_node_foreach_trace (StackNode *node,
|
||||
StackFunction stack_func,
|
||||
gpointer data);
|
||||
StackNode *stack_stash_find_node (StackStash *stash,
|
||||
gpointer address);
|
||||
void stack_stash_foreach_by_address (StackStash *stash,
|
||||
StackNodeFunc func,
|
||||
gpointer data);
|
||||
StackNode *stack_stash_get_root (StackStash *stash);
|
||||
StackStash *stack_stash_ref (StackStash *stash);
|
||||
void stack_stash_unref (StackStash *stash);
|
||||
void stack_stash_set_root (StackStash *stash,
|
||||
StackNode *root);
|
||||
|
||||
#endif
|
||||
49
src/libsysprof/sysprof.h
Normal file
49
src/libsysprof/sysprof.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* sysprof.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/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sysprof-capture.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SYSPROF_INSIDE
|
||||
|
||||
# include "sp-callgraph-profile.h"
|
||||
# include "sp-capture-gobject.h"
|
||||
# include "sp-local-profiler.h"
|
||||
# include "sp-profile.h"
|
||||
# include "sp-profiler.h"
|
||||
# include "sp-gjs-source.h"
|
||||
# include "sp-hostinfo-source.h"
|
||||
# include "sp-map-lookaside.h"
|
||||
# include "sp-memory-source.h"
|
||||
# include "sp-perf-source.h"
|
||||
# include "sp-proc-source.h"
|
||||
# include "sp-source.h"
|
||||
# include "sp-elf-symbol-resolver.h"
|
||||
# include "sp-jitmap-symbol-resolver.h"
|
||||
# include "sp-kernel-symbol-resolver.h"
|
||||
# include "sp-kernel-symbol.h"
|
||||
# include "sp-symbol-dirs.h"
|
||||
# include "sp-symbol-resolver.h"
|
||||
# include "sp-map-lookaside.h"
|
||||
# include "sp-selection.h"
|
||||
#undef SYSPROF_INSIDE
|
||||
|
||||
G_END_DECLS
|
||||
Reference in New Issue
Block a user