tree: start on massive tree refactor

The big thing going on here is that we are going to split up the libraries
a bit better, and remove GObject from the capture library. The libsysprof
library will bring in the capture library statically, so we can export the
symbols we want.

Eventually, we will bump the version to sysprof-3, but not yet.
This commit is contained in:
Christian Hergert
2019-05-07 20:52:05 -07:00
parent 5323cffdb3
commit 1708ad1b48
193 changed files with 1400 additions and 1136 deletions

532
src/libsysprof/binfile.c Normal file
View File

@ -0,0 +1,532 @@
/* MemProf -- memory profiler and leak detector
* Copyright 1999, 2000, 2001, Red Hat, Inc.
* Copyright 2002, Kristian Rietveld
*
* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, 2005, 2006, 2007, Soeren Sandmann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/* Most interesting code in this file is lifted from bfdutils.c
* and process.c from Memprof,
*/
#include "config.h"
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include "binfile.h"
#include "elfparser.h"
#include "sp-symbol-dirs.h"
struct bin_file_t
{
int ref_count;
GList * elf_files;
char * filename;
char * undefined_name;
gulong text_offset;
gboolean inode_check;
ino_t inode;
};
static ino_t
read_inode (const char *filename)
{
struct stat statbuf;
if (strcmp (filename, "[vdso]") == 0)
return (ino_t)0;
if (stat (filename, &statbuf) < 0)
return (ino_t)-1;
return statbuf.st_ino;
}
static gboolean
already_warned (const char *name)
{
static GPtrArray *warnings;
guint i;
if (!warnings)
warnings = g_ptr_array_new ();
for (i = 0; i < warnings->len; ++i)
{
if (strcmp (warnings->pdata[i], name) == 0)
return TRUE;
}
g_ptr_array_add (warnings, g_strdup (name));
return FALSE;
}
static ElfParser *
get_build_id_file (ElfParser *elf)
{
const char *build_id;
GList *tries = NULL, *list;
char *init, *rest;
ElfParser *result = NULL;
char *tmp;
build_id = elf_parser_get_build_id (elf);
if (!build_id)
return NULL;
if (strlen (build_id) < 4)
return NULL;
init = g_strndup (build_id, 2);
rest = g_strdup_printf ("%s%s", build_id + 2, ".debug");
tmp = g_build_filename (
"/usr", "lib", "debug", ".build-id", init, rest, NULL);
tries = g_list_append (tries, tmp);
tmp = g_build_filename (DEBUGDIR, ".build-id", init, rest, NULL);
tries = g_list_append (tries, tmp);
for (list = tries; list != NULL; list = list->next)
{
char *name = list->data;
ElfParser *parser = elf_parser_new (name, NULL);
if (parser)
{
const char *file_id = elf_parser_get_build_id (parser);
if (file_id && strcmp (build_id, file_id) == 0)
{
result = parser;
break;
}
elf_parser_free (parser);
}
}
g_list_foreach (tries, (GFunc)g_free, NULL);
g_list_free (tries);
g_free (init);
g_free (rest);
return result;
}
static ElfParser *
get_debuglink_file (ElfParser *elf,
const char *filename,
char **new_name)
{
const char *basename;
char *dir;
guint32 crc32;
gchar **tries;
ElfParser *result = NULL;
const char *build_id;
guint i;
if (!elf)
return NULL;
basename = elf_parser_get_debug_link (elf, &crc32);
build_id = elf_parser_get_build_id (elf);
#if 0
g_print (" debug link for %s is %s\n", filename, basename);
#endif
if (!basename)
return NULL;
dir = g_path_get_dirname (filename);
tries = sp_symbol_dirs_get_paths (dir, basename);
for (i = 0; tries[i]; i++)
{
const char *name = tries[i];
ElfParser *parser = elf_parser_new (name, NULL);
guint32 file_crc;
const char *file_build_id;
if (parser)
{
/* If both files have build ids, and they don't match,
* there is no point computing a CRC32 that we know
* will fail
*/
file_build_id = elf_parser_get_build_id (parser);
if (build_id && file_build_id && strcmp (build_id, file_build_id) != 0)
goto skip;
file_crc = elf_parser_get_crc32 (parser);
if (file_crc == crc32)
{
result = parser;
*new_name = g_strdup (name);
break;
}
else
{
if (!already_warned (name))
{
g_print ("warning: %s has wrong crc %x, %s has crc %x)\n",
name, file_crc, filename, crc32);
}
}
skip:
elf_parser_free (parser);
}
}
g_free (dir);
g_strfreev (tries);
return result;
}
static GList *
get_debug_binaries (GList *files,
ElfParser *elf,
const char *filename)
{
ElfParser *build_id_file;
GHashTable *seen_names;
GList *free_us = NULL;
build_id_file = get_build_id_file (elf);
if (build_id_file)
return g_list_prepend (files, build_id_file);
/* .gnu_debuglink is actually a chain of debuglinks, and
* there have been real-world cases where following it was
* necessary to get useful debug information.
*/
seen_names = g_hash_table_new (g_str_hash, g_str_equal);
while (elf)
{
char *debug_name;
if (g_hash_table_lookup (seen_names, filename))
break;
g_hash_table_insert (seen_names, (char *)filename, (char *)filename);
elf = get_debuglink_file (elf, filename, &debug_name);
if (elf)
{
files = g_list_prepend (files, elf);
free_us = g_list_prepend (free_us, debug_name);
filename = debug_name;
}
}
g_list_foreach (free_us, (GFunc)g_free, NULL);
g_list_free (free_us);
g_hash_table_destroy (seen_names);
return files;
}
static char **
get_lines (const char *format,
...)
G_GNUC_PRINTF (1, 2);
static char **
get_lines (const char *format,
...)
{
va_list args;
char *filename;
char **result = NULL;
char *contents;
va_start (args, format);
filename = g_strdup_vprintf (format, args);
va_end (args);
if (g_file_get_contents (filename, &contents, NULL, NULL))
{
result = g_strsplit (contents, "\n", -1);
g_free (contents);
}
g_free (filename);
return result;
}
static const uint8_t *
get_vdso_bytes (size_t *length)
{
static const uint8_t *bytes = NULL;
static size_t n_bytes = 0;
static gboolean has_data;
if (!has_data)
{
char **lines = get_lines ("/proc/%d/maps", getpid());
int i;
for (i = 0; lines[i] != NULL; ++i)
{
char file[256];
gulong start;
gulong end;
int count = sscanf (
lines[i], "%lx-%lx %*15s %*x %*x:%*x %*u %255s",
&start, &end, file);
if (count == 3 && strcmp (file, "[vdso]") == 0)
{
n_bytes = end - start;
/* Dup the memory here so that valgrind will only
* report one 1 byte invalid read instead of
* a ton when the elf parser scans the vdso
*
* The reason we get a spurious invalid read from
* valgrind is that we are getting the address directly
* from /proc/maps, and valgrind knows that its mmap()
* wrapper never returned that address. But since it
* is a legal mapping, it is legal to read it.
*/
bytes = g_memdup ((uint8_t *)start, n_bytes);
has_data = TRUE;
}
}
}
if (length)
*length = n_bytes;
return bytes;
}
bin_file_t *
bin_file_new (const char *filename)
{
ElfParser *elf = NULL;
bin_file_t *bf;
bf = g_new0 (bin_file_t, 1);
bf->inode_check = FALSE;
bf->filename = g_strdup (filename);
bf->undefined_name = g_strdup_printf ("In file %s", filename);
bf->ref_count = 1;
bf->elf_files = NULL;
if (strcmp (filename, "[vdso]") == 0)
{
const guint8 *vdso_bytes;
gsize length;
vdso_bytes = get_vdso_bytes (&length);
if (vdso_bytes)
elf = elf_parser_new_from_data (vdso_bytes, length);
}
else
{
elf = elf_parser_new (filename, NULL);
}
if (elf)
{
/* We need the text offset of the actual binary, not the
* (potential) debug binaries
*/
bf->text_offset = elf_parser_get_text_offset (elf);
bf->elf_files = get_debug_binaries (bf->elf_files, elf, filename);
bf->elf_files = g_list_append (bf->elf_files, elf);
bf->inode = read_inode (filename);
}
return bf;
}
void
bin_file_free (bin_file_t *bin_file)
{
if (--bin_file->ref_count == 0)
{
g_list_foreach (bin_file->elf_files, (GFunc)elf_parser_free, NULL);
g_list_free (bin_file->elf_files);
g_free (bin_file->filename);
g_free (bin_file->undefined_name);
g_free (bin_file);
}
}
const bin_symbol_t *
bin_file_lookup_symbol (bin_file_t *bin_file,
gulong address)
{
GList *list;
#if 0
g_print ("-=-=-=- \n");
g_print ("bin file lookup lookup %d\n", address);
#endif
address -= bin_file->text_offset;
#if 0
g_print ("lookup %d in %s\n", address, bin_file->filename);
#endif
for (list = bin_file->elf_files; list != NULL; list = list->next)
{
ElfParser *elf = list->data;
const ElfSym *sym = elf_parser_lookup_symbol (elf, address);
if (sym)
{
#if 0
g_print ("found %lx => %s\n", address,
bin_symbol_get_name (bin_file, sym));
#endif
return (const bin_symbol_t *)sym;
}
}
#if 0
g_print ("%lx undefined in %s (textoffset %x)\n",
address + bin_file->text_offset,
bin_file->filename,
bin_file->text_offset);
#endif
return (const bin_symbol_t *)bin_file->undefined_name;
}
gboolean
bin_file_check_inode (bin_file_t *bin_file,
ino_t inode)
{
if (bin_file->inode == inode)
return TRUE;
if (!bin_file->elf_files)
return FALSE;
if (!bin_file->inode_check)
{
g_print ("warning: Inode mismatch for %s (disk: %"G_GUINT64_FORMAT", memory: %"G_GUINT64_FORMAT")\n",
bin_file->filename, (guint64)bin_file->inode, (guint64)inode);
bin_file->inode_check = TRUE;
}
return FALSE;
}
static const ElfSym *
get_elf_sym (bin_file_t *file,
const bin_symbol_t *symbol,
ElfParser **elf_ret)
{
GList *list;
for (list = file->elf_files; list != NULL; list = list->next)
{
const ElfSym *sym = (const ElfSym *)symbol;
ElfParser *elf = list->data;
if (elf_parser_owns_symbol (elf, sym))
{
*elf_ret = elf;
return sym;
}
}
g_critical ("Internal error: unrecognized symbol pointer");
*elf_ret = NULL;
return NULL;
}
const char *
bin_symbol_get_name (bin_file_t *file,
const bin_symbol_t *symbol)
{
if (file->undefined_name == (char *)symbol)
{
return file->undefined_name;
}
else
{
ElfParser *elf;
const ElfSym *sym;
sym = get_elf_sym (file, symbol, &elf);
return elf_parser_get_sym_name (elf, sym);
}
}
gulong
bin_symbol_get_address (bin_file_t *file,
const bin_symbol_t *symbol)
{
if (file->undefined_name == (char *)symbol)
{
return 0x0;
}
else
{
ElfParser *elf;
const ElfSym *sym;
sym = get_elf_sym (file, symbol, &elf);
return elf_parser_get_sym_address (elf, sym);
}
}

46
src/libsysprof/binfile.h Normal file
View File

@ -0,0 +1,46 @@
/* MemProf -- memory profiler and leak detector
* Copyright 1999, 2000, 2001, Red Hat, Inc.
* Copyright 2002, Kristian Rietveld
*
* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2004, Red Hat, Inc.
* Copyright 2004, 2005, Soeren Sandmann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef BIN_FILE_H
#define BIN_FILE_H
#include <glib.h>
#include <sys/types.h>
typedef struct bin_file_t bin_file_t;
typedef struct bin_symbol_t bin_symbol_t;
/* Binary File */
bin_file_t * bin_file_new (const char *filename);
void bin_file_free (bin_file_t *bin_file);
const bin_symbol_t *bin_file_lookup_symbol (bin_file_t *bin_file,
gulong address);
gboolean bin_file_check_inode (bin_file_t *bin_file,
ino_t inode);
const char * bin_symbol_get_name (bin_file_t *bin_file,
const bin_symbol_t *symbol);
gulong bin_symbol_get_address (bin_file_t *bin_file,
const bin_symbol_t *symbol);
#endif

View File

@ -0,0 +1,40 @@
/* demangle.cpp
*
* Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cxxabi.h>
#include <stdlib.h>
#include "demangle.h"
gchar *
sysprof_cplus_demangle (const gchar *name)
{
char *real_name;
gchar *ret;
int status;
real_name = abi::__cxa_demangle (name, 0, 0, &status);
if (real_name == NULL)
return NULL;
ret = g_strdup (real_name);
free (real_name);
return ret;
}

30
src/libsysprof/demangle.h Normal file
View File

@ -0,0 +1,30 @@
/* demangle.h
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DEMANGLE_H
#define DEMANGLE_H
#include <glib.h>
G_BEGIN_DECLS
gchar *sysprof_cplus_demangle (const gchar *name);
G_END_DECLS
#endif /* DEMANGLE_H */

811
src/libsysprof/elfparser.c Normal file
View File

@ -0,0 +1,811 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2006, 2007, Soeren Sandmann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <stdlib.h>
#include <string.h>
#include <elf.h>
#include <sys/mman.h>
#include "demangle.h"
#include "elfparser.h"
typedef struct Section Section;
struct ElfSym
{
gulong table;
gulong offset;
gulong address;
};
struct Section
{
const gchar * name;
gsize offset;
gsize size;
gboolean allocated;
gulong load_address;
guint type;
};
struct ElfParser
{
gboolean is_64;
const guchar * data;
gsize length;
guint n_sections;
Section ** sections;
guint n_symbols;
ElfSym * symbols;
gsize sym_strings;
GMappedFile * file;
char * filename;
gboolean checked_build_id;
char * build_id;
const Section * text_section;
};
/* FIXME: All of these should in principle do endian swapping,
* but sysprof never has to deal with binaries of a different
* endianness than sysprof itself
*/
#define GET_FIELD(parser, offset, struct_name, idx, field_name) \
(((parser))->is_64? \
((Elf64_ ## struct_name *)(gpointer)(((parser)->data + offset)) + (idx))->field_name : \
((Elf32_ ## struct_name *)(gpointer)(((parser)->data + offset)) + (idx))->field_name)
#define GET_UINT32(parser, offset) \
*((uint32_t *)(gpointer)(parser->data + offset)) \
#define GET_SIZE(parser, struct_name) \
(((parser)->is_64? \
sizeof (Elf64_ ## struct_name) : \
sizeof (Elf32_ ## struct_name)))
#define MAKE_ELF_UINT_ACCESSOR(field_name) \
static uint64_t field_name (ElfParser *parser) \
{ \
return GET_FIELD (parser, 0, Ehdr, 0, field_name); \
}
MAKE_ELF_UINT_ACCESSOR (e_shoff)
MAKE_ELF_UINT_ACCESSOR (e_shnum)
MAKE_ELF_UINT_ACCESSOR (e_shstrndx)
#define MAKE_SECTION_HEADER_ACCESSOR(field_name) \
static uint64_t field_name (ElfParser *parser, int nth_section) \
{ \
gsize offset = e_shoff (parser); \
\
return GET_FIELD (parser, offset, Shdr, nth_section, field_name); \
}
MAKE_SECTION_HEADER_ACCESSOR (sh_name);
MAKE_SECTION_HEADER_ACCESSOR (sh_type);
MAKE_SECTION_HEADER_ACCESSOR (sh_flags);
MAKE_SECTION_HEADER_ACCESSOR (sh_addr);
MAKE_SECTION_HEADER_ACCESSOR (sh_offset);
MAKE_SECTION_HEADER_ACCESSOR (sh_size);
#define MAKE_SYMBOL_ACCESSOR(field_name) \
static uint64_t field_name (ElfParser *parser, gulong offset, gulong nth) \
{ \
return GET_FIELD (parser, offset, Sym, nth, field_name); \
}
MAKE_SYMBOL_ACCESSOR(st_name);
MAKE_SYMBOL_ACCESSOR(st_info);
MAKE_SYMBOL_ACCESSOR(st_value);
MAKE_SYMBOL_ACCESSOR(st_size);
MAKE_SYMBOL_ACCESSOR(st_shndx);
static void
section_free (Section *section)
{
g_free (section);
}
static const Section *
find_section (ElfParser *parser,
const char *name,
guint type)
{
guint i;
for (i = 0; i < parser->n_sections; ++i)
{
Section *section = parser->sections[i];
if (strcmp (section->name, name) == 0 && section->type == type)
return section;
}
return NULL;
}
static gboolean
parse_elf_signature (const guchar *data,
gsize length,
gboolean *is_64,
gboolean *is_be)
{
/* FIXME: this function should be able to return an error */
if (length < EI_NIDENT)
{
/* FIXME set error */
return FALSE;
}
if (data[EI_CLASS] != ELFCLASS32 &&
data[EI_CLASS] != ELFCLASS64)
{
/* FIXME set error */
return FALSE;
}
if (data[EI_DATA] != ELFDATA2LSB &&
data[EI_DATA] != ELFDATA2MSB)
{
/* FIXME set error */
return FALSE;
}
if (is_64)
*is_64 = (data[EI_CLASS] == ELFCLASS64);
if (is_be)
*is_be = (data[EI_DATA] == ELFDATA2MSB);
return TRUE;
}
ElfParser *
elf_parser_new_from_data (const guchar *data,
gsize length)
{
ElfParser *parser;
gboolean is_64, is_big_endian;
int section_names_idx;
const guchar *section_names;
G_GNUC_UNUSED gsize section_headers;
guint i;
if (!parse_elf_signature (data, length, &is_64, &is_big_endian))
{
/* FIXME: set error */
return NULL;
}
parser = g_new0 (ElfParser, 1);
parser->is_64 = is_64;
parser->data = data;
parser->length = length;
#if 0
g_print (" new parser : %p\n", parser);
#endif
/* Read ELF header */
parser->n_sections = e_shnum (parser);
section_names_idx = e_shstrndx (parser);
section_headers = e_shoff (parser);
/* Read section headers */
parser->sections = g_new0 (Section *, parser->n_sections);
section_names = parser->data + sh_offset (parser, section_names_idx);
for (i = 0; i < parser->n_sections; ++i)
{
Section *section = g_new (Section, 1);
section->name = (char *)(section_names + sh_name (parser, i));
section->size = sh_size (parser, i);
section->offset = sh_offset (parser, i);
section->allocated = !!(sh_flags (parser, i) & SHF_ALLOC);
if (section->allocated)
section->load_address = sh_addr (parser, i);
else
section->load_address = 0;
section->type = sh_type (parser, i);
parser->sections[i] = section;
}
/* Cache the text section */
parser->text_section = find_section (parser, ".text", SHT_PROGBITS);
if (!parser->text_section)
parser->text_section = find_section (parser, ".text", SHT_NOBITS);
parser->filename = NULL;
parser->build_id = NULL;
return parser;
}
ElfParser *
elf_parser_new (const char *filename,
GError **err)
{
const guchar *data;
gsize length;
ElfParser *parser;
GMappedFile *file = g_mapped_file_new (filename, FALSE, NULL);
if (!file)
return NULL;
#if 0
g_print ("elf parser new : %s\n", filename);
#endif
data = (guchar *)g_mapped_file_get_contents (file);
length = g_mapped_file_get_length (file);
#if 0
g_print ("data %p: for %s\n", data, filename);
#endif
parser = elf_parser_new_from_data (data, length);
#if 0
g_print ("Parser for %s: %p\n", filename, parser);
#endif
if (!parser)
{
g_mapped_file_unref (file);
return NULL;
}
parser->filename = g_strdup (filename);
parser->file = file;
#if 0
g_print ("Elf file: %s (debug: %s)\n",
filename, elf_parser_get_debug_link (parser, NULL));
if (!parser->symbols)
g_print ("at this point %s has no symbols\n", filename);
#endif
return parser;
}
guint32
elf_parser_get_crc32 (ElfParser *parser)
{
static const unsigned long crc32_table[256] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
const guchar *data;
gsize length;
gulong crc;
gsize i;
data = parser->data;
length = parser->length;
crc = 0xffffffff;
madvise ((char *)data, length, MADV_SEQUENTIAL);
for (i = 0; i < length; ++i)
crc = crc32_table[(crc ^ data[i]) & 0xff] ^ (crc >> 8);
/* We just read the entire file into memory, but we only really
* need the symbol table, so swap the whole thing out.
*
* We could be more exact here, but it's only a few minor
* pagefaults.
*/
if (parser->file)
madvise ((char *)data, length, MADV_DONTNEED);
return ~crc & 0xffffffff;
}
void
elf_parser_free (ElfParser *parser)
{
guint i;
for (i = 0; i < parser->n_sections; ++i)
section_free (parser->sections[i]);
g_free (parser->sections);
if (parser->file)
g_mapped_file_unref (parser->file);
g_free (parser->symbols);
if (parser->filename)
g_free (parser->filename);
if (parser->build_id)
g_free (parser->build_id);
g_free (parser);
}
gchar *
elf_demangle (const char *name)
{
gchar *demangled = sysprof_cplus_demangle (name);
if (demangled)
return demangled;
else
return g_strdup (name);
}
/*
* Looking up symbols
*/
static int
compare_sym (const void *a, const void *b)
{
const ElfSym *sym_a = a;
const ElfSym *sym_b = b;
if (sym_a->address < sym_b->address)
return -1;
else if (sym_a->address == sym_b->address)
return 0;
else
return 1;
}
#if 0
static void
dump_symbols (ElfParser *parser, ElfSym *syms, guint n_syms)
{
int i;
for (i = 0; i < n_syms; ++i)
{
ElfSym *s = &(syms[i]);
g_print (" %s: %lx\n", elf_parser_get_sym_name (parser, s), s->address);
}
}
#endif
static void
read_table (ElfParser *parser,
const Section *sym_table,
const Section *str_table)
{
int sym_size = GET_SIZE (parser, Sym);
guint i, n_symbols;
#if 0
g_print ("elf: Reading table for %s\n", parser->filename? parser->filename : "<unknown>");
#endif
parser->n_symbols = sym_table->size / sym_size;
parser->symbols = g_new (ElfSym, parser->n_symbols);
#if 0
g_print ("sym table offset: %d\n", sym_table->offset);
#endif
n_symbols = 0;
#if 0
g_print ("n syms: %d\n", parser->n_symbols);
#endif
for (i = 0; i < parser->n_symbols; ++i)
{
guint info;
gulong addr;
gulong shndx;
info = st_info (parser, sym_table->offset, i);
addr = st_value (parser, sym_table->offset, i);
shndx = st_shndx (parser, sym_table->offset, i);
#if 0
g_print ("read symbol: %s (section: %d)\n", get_string_indirct (parser->parser,
parser->sym_format, "st_name",
str_table->offset),
shndx);
#endif
if (addr != 0 &&
shndx < parser->n_sections &&
parser->sections[shndx] == parser->text_section &&
(info & 0xf) == STT_FUNC &&
((info >> 4) == STB_GLOBAL ||
(info >> 4) == STB_LOCAL ||
(info >> 4) == STB_WEAK))
{
parser->symbols[n_symbols].address = addr;
parser->symbols[n_symbols].table = sym_table->offset;
parser->symbols[n_symbols].offset = i;
n_symbols++;
#if 0
g_print (" symbol: %s: %lx\n",
get_string_indirect (parser->parser,
parser->sym_format, "st_name",
str_table->offset),
addr - parser->text_section->load_address);
g_print (" sym %d in %p (info: %d:%d) (func:global %d:%d)\n",
addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL);
#endif
}
else if (addr != 0)
{
#if 0
g_print (" rejecting %d in %p (info: %d:%d) (func:global %d:%d)\n",
addr, parser, info & 0xf, info >> 4, STT_FUNC, STB_GLOBAL);
#endif
}
}
parser->sym_strings = str_table->offset;
parser->n_symbols = n_symbols;
/* Allocate space for at least one symbol, so that parser->symbols will be
* non-NULL. If it ends up being NULL, we will be parsing the file over and
* over.
*/
parser->symbols = g_renew (ElfSym, parser->symbols, parser->n_symbols + 1);
qsort (parser->symbols, parser->n_symbols, sizeof (ElfSym), compare_sym);
}
static void
read_symbols (ElfParser *parser)
{
const Section *symtab = find_section (parser, ".symtab", SHT_SYMTAB);
const Section *strtab = find_section (parser, ".strtab", SHT_STRTAB);
const Section *dynsym = find_section (parser, ".dynsym", SHT_DYNSYM);
const Section *dynstr = find_section (parser, ".dynstr", SHT_STRTAB);
if (symtab && strtab)
{
#if 0
g_print ("reading symbol table of %s\n", parser->filename);
#endif
read_table (parser, symtab, strtab);
}
else if (dynsym && dynstr)
{
#if 0
g_print ("reading dynamic symbol table of %s\n", parser->filename);
#endif
read_table (parser, dynsym, dynstr);
}
else
{
/* To make sure parser->symbols is non-NULL */
parser->n_symbols = 0;
parser->symbols = g_new (ElfSym, 1);
}
}
static ElfSym *
do_lookup (ElfSym *symbols,
gulong address,
int first,
int last)
{
if (address >= symbols[last].address)
{
return &(symbols[last]);
}
else if (last - first < 3)
{
while (last >= first)
{
if (address >= symbols[last].address)
return &(symbols[last]);
last--;
}
return NULL;
}
else
{
int mid = (first + last) / 2;
if (symbols[mid].address > address)
return do_lookup (symbols, address, first, mid);
else
return do_lookup (symbols, address, mid, last);
}
}
/* Address should be given in 'offset into text segment' */
const ElfSym *
elf_parser_lookup_symbol (ElfParser *parser,
gulong address)
{
const ElfSym *result;
if (!parser->symbols)
{
#if 0
g_print ("reading symbols at %p\n", parser);
#endif
read_symbols (parser);
}
if (parser->n_symbols == 0)
return NULL;
if (!parser->text_section)
return NULL;
address += parser->text_section->load_address;
#if 0
g_print ("elf: the address we are looking up is %p\n", address);
#endif
result = do_lookup (parser->symbols, address, 0, parser->n_symbols - 1);
#if 0
if (result)
{
g_print (" elf: found %s at %lx\n", elf_parser_get_sym_name (parser, result), result->address);
}
else
{
g_print ("elf: not found\n");
}
#endif
if (result)
{
gulong size = st_size (parser, result->table, result->offset);
if (size > 0 && result->address + size <= address)
{
#if 0
g_print (" elf: ends at %lx, so rejecting\n",
result->address + size);
#endif
result = NULL;
}
}
if (result)
{
/* Reject the symbols if the address is outside the text section */
if (address > parser->text_section->load_address + parser->text_section->size)
result = NULL;
}
return result;
}
gulong
elf_parser_get_text_offset (ElfParser *parser)
{
g_return_val_if_fail (parser != NULL, (gulong)-1);
if (!parser->text_section)
return (gulong)-1;
return parser->text_section->offset;
}
static gchar *
make_hex_string (const guchar *data, int n_bytes)
{
static const char hex_digits[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
GString *string = g_string_new (NULL);
int i;
for (i = 0; i < n_bytes; ++i)
{
char c = data[i];
g_string_append_c (string, hex_digits[(c & 0xf0) >> 4]);
g_string_append_c (string, hex_digits[(c & 0x0f)]);
}
return g_string_free (string, FALSE);
}
const gchar *
elf_parser_get_build_id (ElfParser *parser)
{
if (!parser->checked_build_id)
{
const Section *build_id =
find_section (parser, ".note.gnu.build-id", SHT_NOTE);
guint64 name_size;
guint64 desc_size;
guint64 type;
const char *name;
guint64 offset;
parser->checked_build_id = TRUE;
if (!build_id)
return NULL;
offset = build_id->offset;
name_size = GET_FIELD (parser, offset, Nhdr, 0, n_namesz);
desc_size = GET_FIELD (parser, offset, Nhdr, 0, n_descsz);
type = GET_FIELD (parser, offset, Nhdr, 0, n_type);
offset += GET_SIZE (parser, Nhdr);
name = (char *)(parser->data + offset);
if (strncmp (name, ELF_NOTE_GNU, name_size) != 0 || type != NT_GNU_BUILD_ID)
return NULL;
offset += strlen (name);
offset = (offset + 3) & (~0x3);
parser->build_id = make_hex_string (parser->data + offset, desc_size);
}
return parser->build_id;
}
const char *
elf_parser_get_debug_link (ElfParser *parser, guint32 *crc32)
{
guint64 offset;
const Section *debug_link = find_section (parser, ".gnu_debuglink",
SHT_PROGBITS);
const gchar *result;
if (!debug_link)
return NULL;
offset = debug_link->offset;
result = (char *)(parser->data + offset);
if (crc32)
{
int len = strlen (result) + 1;
offset = (offset + len + 3) & ~0x3;
*crc32 = GET_UINT32 (parser, offset);
}
return result;
}
static const guchar *
get_section (ElfParser *parser,
const char *name)
{
const Section *section = find_section (parser, name, SHT_PROGBITS);
if (section)
return parser->data + section->offset;
else
return NULL;
}
const guchar *
elf_parser_get_eh_frame (ElfParser *parser)
{
return get_section (parser, ".eh_frame");
}
const guchar *
elf_parser_get_debug_frame (ElfParser *parser)
{
return get_section (parser, ".debug_frame");
}
const char *
elf_parser_get_sym_name (ElfParser *parser,
const ElfSym *sym)
{
g_return_val_if_fail (parser != NULL, NULL);
return (char *)(parser->data + parser->sym_strings +
st_name (parser, sym->table, sym->offset));
}
gboolean
elf_parser_owns_symbol (ElfParser *parser,
const ElfSym *sym)
{
ElfSym *first, *last;
if (!parser->n_symbols)
return FALSE;
first = parser->symbols;
last = parser->symbols + parser->n_symbols - 1;
return first <= sym && sym <= last;
}
gulong
elf_parser_get_sym_address (ElfParser *parser,
const ElfSym *sym)
{
return sym->address - parser->text_section->load_address;
}
/*
* Utility functions
*/

View File

@ -0,0 +1,57 @@
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2006, 2007, Soeren Sandmann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <glib.h>
typedef struct ElfSym ElfSym;
typedef struct ElfParser ElfParser;
ElfParser * elf_parser_new_from_data (const guchar *data,
gsize length);
ElfParser * elf_parser_new (const char *filename,
GError **err);
void elf_parser_free (ElfParser *parser);
const char * elf_parser_get_debug_link (ElfParser *parser,
guint32 *crc32);
const gchar *elf_parser_get_build_id (ElfParser *parser);
const guchar *elf_parser_get_eh_frame (ElfParser *parser);
const guchar *elf_parser_get_debug_frame (ElfParser *parser);
gulong elf_parser_get_text_offset (ElfParser *parser);
/* Lookup a symbol in the file.
*
* The symbol returned is const, so don't free it. It is valid until
* elf_parser_free() is called on the parser.
*
* The address should be given in "file coordinates". This means that
* if the file is mapped at address m and offset o, then an address a
* should be looked up as "a - (m - o)". (m - o) is where the start
* of the file would have been mapped, so a - (m - o) is the position
* in the file of a.
*/
const ElfSym *elf_parser_lookup_symbol (ElfParser *parser,
gulong address);
guint32 elf_parser_get_crc32 (ElfParser *parser);
const char * elf_parser_get_sym_name (ElfParser *parser,
const ElfSym *sym);
gulong elf_parser_get_sym_address (ElfParser *parser,
const ElfSym *sym);
gboolean elf_parser_owns_symbol (ElfParser *parser,
const ElfSym *sym);
char * elf_demangle (const char *name);

View File

@ -0,0 +1,97 @@
libsysprof_public_sources = [
'sp-callgraph-profile.c',
'sp-capture-gobject.c',
'sp-elf-symbol-resolver.c',
'sp-gjs-source.c',
'sp-hostinfo-source.c',
'sp-jitmap-symbol-resolver.c',
'sp-kernel-symbol.c',
'sp-kernel-symbol-resolver.c',
'sp-local-profiler.c',
'sp-map-lookaside.c',
'sp-memory-source.c',
'sp-perf-counter.c',
'sp-perf-source.c',
'sp-process-model.c',
'sp-process-model-item.c',
'sp-proc-source.c',
'sp-profile.c',
'sp-profiler.c',
'sp-selection.c',
'sp-source.c',
'sp-symbol-dirs.c',
'sp-symbol-resolver.c',
]
libsysprof_public_headers = [
'sp-callgraph-profile.h',
'sp-callgraph-profile-private.h',
'sp-capture-gobject.h',
'sp-elf-symbol-resolver.h',
'sp-gjs-source.h',
'sp-hostinfo-source.h',
'sp-jitmap-symbol-resolver.h',
'sp-kernel-symbol.h',
'sp-kernel-symbol-resolver.h',
'sp-local-profiler.h',
'sp-map-lookaside.h',
'sp-memory-source.h',
'sp-perf-counter.h',
'sp-perf-source.h',
'sp-process-model.h',
'sp-process-model-item.h',
'sp-proc-source.h',
'sp-profile.h',
'sp-profiler.h',
'sp-selection.h',
'sp-source.h',
'sp-symbol-dirs.h',
'sp-symbol-resolver.h',
'sysprof.h',
]
libsysprof_private_sources = [
'binfile.c',
'demangle.cpp',
'elfparser.c',
'stackstash.c',
'sp-source-util.c',
]
libsysprof_deps = [
cxx.find_library('stdc++'),
dependency('gio-2.0', version: glib_req_version),
dependency('gio-unix-2.0', version: glib_req_version),
libsysprof_capture_dep,
libshared_dep,
]
if get_option('with_sysprofd') != 'none'
libsysprof_deps += dependency('polkit-gobject-1')
endif
libsysprof = shared_library('sysprof-@0@'.format(libsysprof_api_version),
libsysprof_public_sources + libsysprof_private_sources,
dependencies: libsysprof_deps,
c_args: [ '-DSYSPROF_COMPILATION' ],
install: true,
install_dir: get_option('libdir'),
)
libsysprof_dep = declare_dependency(
link_with: libsysprof,
dependencies: libsysprof_deps,
include_directories: include_directories('.'),
)
pkgconfig.generate(
subdirs: [ sysprof_header_subdir ],
version: meson.project_version(),
name: 'sysprof-@0@'.format(libsysprof_api_version),
filebase: 'sysprof-@0@'.format(libsysprof_api_version),
description: 'The library for console applications embedding sysprof',
install_dir: join_paths(get_option('libdir'), 'pkgconfig'),
requires: [ 'gio-2.0' ],
)
install_headers(libsysprof_public_headers, subdir: sysprof_header_subdir)

View File

@ -0,0 +1,31 @@
/* sp-callgraph-profile-private.h
*
* Copyright 2016 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CALLGRAPH_PROFILE_PRIVATE_H
#define SP_CALLGRAPH_PROFILE_PRIVATE_H
#include "sp-callgraph-profile.h"
#include "stackstash.h"
G_BEGIN_DECLS
StackStash *sp_callgraph_profile_get_stash (SpCallgraphProfile *self);
G_END_DECLS
#endif /* SP_CALLGRAPH_PROFILE_PRIVATE_H */

View File

@ -0,0 +1,517 @@
/* sp-callgraph-profile.c
*
* Copyright 2016 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Sysprof -- Sampling, systemwide CPU profiler
* Copyright 2009-2012 Soeren Sandmann and others
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <glib/gi18n.h>
#include <string.h>
#include <sysprof-capture.h>
#include <unistd.h>
#include "sp-callgraph-profile.h"
#include "sp-callgraph-profile-private.h"
#include "sp-capture-reader.h"
#include "sp-elf-symbol-resolver.h"
#include "sp-jitmap-symbol-resolver.h"
#include "sp-map-lookaside.h"
#include "sp-kernel-symbol-resolver.h"
#include "sp-selection.h"
#include "stackstash.h"
#define CHECK_CANCELLABLE_INTERVAL 100
struct _SpCallgraphProfile
{
GObject parent_instance;
SpCaptureReader *reader;
SpSelection *selection;
StackStash *stash;
GStringChunk *symbols;
GHashTable *tags;
};
typedef struct
{
SpCaptureReader *reader;
SpSelection *selection;
} Generate;
static void profile_iface_init (SpProfileInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpCallgraphProfile, sp_callgraph_profile, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (SP_TYPE_PROFILE, profile_iface_init))
enum {
PROP_0,
PROP_SELECTION,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
SpProfile *
sp_callgraph_profile_new (void)
{
return g_object_new (SP_TYPE_CALLGRAPH_PROFILE, NULL);
}
SpProfile *
sp_callgraph_profile_new_with_selection (SpSelection *selection)
{
return g_object_new (SP_TYPE_CALLGRAPH_PROFILE,
"selection", selection,
NULL);
}
static void
sp_callgraph_profile_finalize (GObject *object)
{
SpCallgraphProfile *self = (SpCallgraphProfile *)object;
g_clear_pointer (&self->symbols, g_string_chunk_free);
g_clear_pointer (&self->stash, stack_stash_unref);
g_clear_pointer (&self->reader, sp_capture_reader_unref);
g_clear_pointer (&self->tags, g_hash_table_unref);
g_clear_object (&self->selection);
G_OBJECT_CLASS (sp_callgraph_profile_parent_class)->finalize (object);
}
static void
sp_callgraph_profile_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpCallgraphProfile *self = SP_CALLGRAPH_PROFILE (object);
switch (prop_id)
{
case PROP_SELECTION:
g_value_set_object (value, self->selection);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_callgraph_profile_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpCallgraphProfile *self = SP_CALLGRAPH_PROFILE (object);
switch (prop_id)
{
case PROP_SELECTION:
self->selection = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_callgraph_profile_class_init (SpCallgraphProfileClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_callgraph_profile_finalize;
object_class->get_property = sp_callgraph_profile_get_property;
object_class->set_property = sp_callgraph_profile_set_property;
properties [PROP_SELECTION] =
g_param_spec_object ("selection",
"Selection",
"The selection for filtering the callgraph",
SP_TYPE_SELECTION,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sp_callgraph_profile_init (SpCallgraphProfile *self)
{
self->symbols = g_string_chunk_new (getpagesize ());
self->tags = g_hash_table_new (g_str_hash, g_str_equal);
}
static void
sp_callgraph_profile_set_reader (SpProfile *profile,
SpCaptureReader *reader)
{
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (reader != NULL);
g_clear_pointer (&self->reader, sp_capture_reader_unref);
self->reader = sp_capture_reader_ref (reader);
}
static const gchar *
sp_callgraph_profile_intern_string_take (SpCallgraphProfile *self,
gchar *str)
{
const gchar *ret;
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (str != NULL);
ret = g_string_chunk_insert_const (self->symbols, str);
g_free (str);
return ret;
}
static const gchar *
sp_callgraph_profile_intern_string (SpCallgraphProfile *self,
const gchar *str)
{
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (str != NULL);
return g_string_chunk_insert_const (self->symbols, str);
}
static void
sp_callgraph_profile_generate_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
SpCallgraphProfile *self = source_object;
Generate *gen = task_data;
SpCaptureReader *reader;
SpSelection *selection;
g_autoptr(GArray) resolved = NULL;
g_autoptr(GHashTable) maps_by_pid = NULL;
g_autoptr(GHashTable) cmdlines = NULL;
g_autoptr(GPtrArray) resolvers = NULL;
SpCaptureFrameType type;
StackStash *stash = NULL;
StackStash *resolved_stash = NULL;
guint count = 0;
gboolean ret = FALSE;
g_assert (G_IS_TASK (task));
g_assert (gen != NULL);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
reader = gen->reader;
selection = gen->selection;
maps_by_pid = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)sp_map_lookaside_free);
cmdlines = g_hash_table_new (NULL, NULL);
stash = stack_stash_new (NULL);
resolved_stash = stack_stash_new (NULL);
resolvers = g_ptr_array_new_with_free_func (g_object_unref);
g_ptr_array_add (resolvers, sp_kernel_symbol_resolver_new ());
g_ptr_array_add (resolvers, sp_elf_symbol_resolver_new ());
g_ptr_array_add (resolvers, sp_jitmap_symbol_resolver_new ());
for (guint j = 0; j < resolvers->len; j++)
{
SpSymbolResolver *resolver = g_ptr_array_index (resolvers, j);
sp_capture_reader_reset (reader);
sp_symbol_resolver_load (resolver, reader);
}
sp_capture_reader_reset (reader);
/*
* The resolved pointer array is where we stash the names for the
* instruction pointers to pass to the stash stack. All the strings
* need to be deduplicated so that pointer comparison works as if we
* did instruction-pointer comparison.
*/
resolved = g_array_new (FALSE, TRUE, sizeof (guint64));
while (sp_capture_reader_peek_type (reader, &type))
{
const SpCaptureProcess *pr;
const gchar *cmdline;
if (type != SP_CAPTURE_FRAME_PROCESS)
{
if (!sp_capture_reader_skip (reader))
goto failure;
continue;
}
if (NULL == (pr = sp_capture_reader_read_process (reader)))
goto failure;
cmdline = g_strdup_printf ("[%s]", pr->cmdline);
g_hash_table_insert (cmdlines,
GINT_TO_POINTER (pr->frame.pid),
(gchar *)sp_callgraph_profile_intern_string (self, cmdline));
}
if (g_task_return_error_if_cancelled (task))
goto cleanup;
sp_capture_reader_reset (reader);
/*
* Walk through all of the sample events and resolve instruction-pointers
* to symbol names by loading the particular map and extracting the symbol
* name. If we wanted to support dynamic systems, we'd want to extend this
* to parse information from captured data about the languages jit'd code.
*/
while (sp_capture_reader_peek_type (reader, &type))
{
SpAddressContext last_context = SP_ADDRESS_CONTEXT_NONE;
const SpCaptureSample *sample;
StackNode *node;
StackNode *iter;
const gchar *cmdline;
guint len = 5;
if (type != SP_CAPTURE_FRAME_SAMPLE)
{
if (!sp_capture_reader_skip (reader))
goto failure;
continue;
}
if (++count == CHECK_CANCELLABLE_INTERVAL)
{
if (g_task_return_error_if_cancelled (task))
goto cleanup;
}
if (NULL == (sample = sp_capture_reader_read_sample (reader)))
goto failure;
if (!sp_selection_contains (selection, sample->frame.time))
continue;
if (sample->n_addrs == 0)
continue;
cmdline = g_hash_table_lookup (cmdlines, GINT_TO_POINTER (sample->frame.pid));
#if 0
/* This assertion appears to hold true, but since we're taking in
* untrusted data from capture files, it's not safe to assume. But in
* practice it is.
*/
g_assert (sp_address_is_context_switch (sample->addrs[0], &last_context));
last_context = SP_ADDRESS_CONTEXT_NONE;
#endif
node = stack_stash_add_trace (stash, sample->addrs, sample->n_addrs, 1);
for (iter = node; iter != NULL; iter = iter->parent)
len++;
if (G_UNLIKELY (resolved->len < len))
g_array_set_size (resolved, len);
len = 0;
for (iter = node; iter != NULL; iter = iter->parent)
{
SpAddressContext context = SP_ADDRESS_CONTEXT_NONE;
SpAddress address = iter->data;
const gchar *symbol = NULL;
if (sp_address_is_context_switch (address, &context))
{
if (last_context)
symbol = sp_address_context_to_string (last_context);
else
symbol = NULL;
last_context = context;
}
else
{
for (guint j = 0; j < resolvers->len; j++)
{
SpSymbolResolver *resolver = g_ptr_array_index (resolvers, j);
GQuark tag = 0;
gchar *str;
str = sp_symbol_resolver_resolve_with_context (resolver,
sample->frame.time,
sample->frame.pid,
last_context,
address,
&tag);
if (str != NULL)
{
symbol = sp_callgraph_profile_intern_string_take (self, str);
if (tag != 0)
g_hash_table_insert (self->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag));
break;
}
}
}
if (symbol != NULL)
g_array_index (resolved, SpAddress, len++) = POINTER_TO_U64 (symbol);
}
if (last_context && last_context != SP_ADDRESS_CONTEXT_USER)
{
/* Kernel threads do not have a user part, so we end up here
* without ever getting a user context. If this happens,
* add the '- - kernel - - ' name, so that kernel threads
* are properly blamed on the kernel
*/
const gchar *name = sp_address_context_to_string (last_context);
g_array_index (resolved, SpAddress, len++) = POINTER_TO_U64 (name);
}
if (cmdline != NULL)
g_array_index (resolved, guint64, len++) = POINTER_TO_U64 (cmdline);
g_array_index (resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]");
stack_stash_add_trace (resolved_stash, (SpAddress *)(gpointer)resolved->data, len, 1);
}
ret = TRUE;
failure:
if (ret == FALSE)
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"%s",
_("Sysprof was unable to generate a callgraph from the system capture."));
else
g_task_return_pointer (task, g_steal_pointer (&resolved_stash), (GDestroyNotify)stack_stash_unref);
cleanup:
g_clear_pointer (&resolved_stash, stack_stash_unref);
g_clear_pointer (&stash, stack_stash_unref);
}
static void
generate_free (Generate *generate)
{
sp_capture_reader_unref (generate->reader);
g_clear_object (&generate->selection);
g_slice_free (Generate, generate);
}
static void
sp_callgraph_profile_generate (SpProfile *profile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
Generate *gen;
g_autoptr(GTask) task = NULL;
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
gen = g_slice_new0 (Generate);
gen->reader = sp_capture_reader_copy (self->reader);
gen->selection = sp_selection_copy (self->selection);
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_task_data (task, gen, (GDestroyNotify)generate_free);
g_task_run_in_thread (task, sp_callgraph_profile_generate_worker);
}
static gboolean
sp_callgraph_profile_generate_finish (SpProfile *profile,
GAsyncResult *result,
GError **error)
{
SpCallgraphProfile *self = (SpCallgraphProfile *)profile;
StackStash *stash;
g_assert (SP_IS_CALLGRAPH_PROFILE (self));
g_assert (G_IS_TASK (result));
stash = g_task_propagate_pointer (G_TASK (result), error);
if (stash != NULL)
{
if (stash != self->stash)
{
g_clear_pointer (&self->stash, stack_stash_unref);
self->stash = g_steal_pointer (&stash);
}
g_clear_pointer (&stash, stack_stash_unref);
return TRUE;
}
return FALSE;
}
static void
profile_iface_init (SpProfileInterface *iface)
{
iface->generate = sp_callgraph_profile_generate;
iface->generate_finish = sp_callgraph_profile_generate_finish;
iface->set_reader = sp_callgraph_profile_set_reader;
}
StackStash *
sp_callgraph_profile_get_stash (SpCallgraphProfile *self)
{
g_return_val_if_fail (SP_IS_CALLGRAPH_PROFILE (self), NULL);
return self->stash;
}
GQuark
sp_callgraph_profile_get_tag (SpCallgraphProfile *self,
const gchar *symbol)
{
g_return_val_if_fail (SP_IS_CALLGRAPH_PROFILE (self), 0);
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tags, symbol));
}

View File

@ -0,0 +1,38 @@
/* sp-callgraph-profile.h
*
* Copyright 2016 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_CALLGRAPH_PROFILE_H
#define SP_CALLGRAPH_PROFILE_H
#include "sp-profile.h"
#include "sp-selection.h"
G_BEGIN_DECLS
#define SP_TYPE_CALLGRAPH_PROFILE (sp_callgraph_profile_get_type())
G_DECLARE_FINAL_TYPE (SpCallgraphProfile, sp_callgraph_profile, SP, CALLGRAPH_PROFILE, GObject)
SpProfile *sp_callgraph_profile_new (void);
SpProfile *sp_callgraph_profile_new_with_selection (SpSelection *selection);
GQuark sp_callgraph_profile_get_tag (SpCallgraphProfile *self,
const gchar *symbol);
G_END_DECLS
#endif /* SP_CALLGRAPH_PROFILE_H */

View File

@ -0,0 +1,25 @@
/* sp-capture-gobject.c
*
* Copyright 2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "sp-capture-gobject.h"
G_DEFINE_BOXED_TYPE (SpCaptureReader, sp_capture_reader, (GBoxedCopyFunc)sp_capture_reader_ref, (GBoxedFreeFunc)sp_capture_reader_unref)
G_DEFINE_BOXED_TYPE (SpCaptureWriter, sp_capture_writer, (GBoxedCopyFunc)sp_capture_writer_ref, (GBoxedFreeFunc)sp_capture_writer_unref)
G_DEFINE_BOXED_TYPE (SpCaptureCursor, sp_capture_cursor, (GBoxedCopyFunc)sp_capture_cursor_ref, (GBoxedFreeFunc)sp_capture_cursor_unref)

View File

@ -0,0 +1,36 @@
/* sp-capture-gobject.h
*
* Copyright 2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <glib-object.h>
#include <sysprof-capture.h>
G_BEGIN_DECLS
#define SP_TYPE_CAPTURE_READER (sp_capture_reader_get_type())
#define SP_TYPE_CAPTURE_WRITER (sp_capture_writer_get_type())
#define SP_TYPE_CAPTURE_CURSOR (sp_capture_cursor_get_type())
GType sp_capture_reader_get_type (void);
GType sp_capture_writer_get_type (void);
GType sp_capture_cursor_get_type (void);
G_END_DECLS

View File

@ -0,0 +1,310 @@
/* sp-elf-symbol-resolver.c
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "sp-elf-symbol-resolver.h"
#include "binfile.h"
#include "elfparser.h"
#include "sp-map-lookaside.h"
struct _SpElfSymbolResolver
{
GObject parent_instance;
GHashTable *lookasides;
GHashTable *bin_files;
GHashTable *tag_cache;
};
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpElfSymbolResolver,
sp_elf_symbol_resolver,
G_TYPE_OBJECT,
0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
symbol_resolver_iface_init))
static void
sp_elf_symbol_resolver_finalize (GObject *object)
{
SpElfSymbolResolver *self = (SpElfSymbolResolver *)object;
g_clear_pointer (&self->bin_files, g_hash_table_unref);
g_clear_pointer (&self->lookasides, g_hash_table_unref);
g_clear_pointer (&self->tag_cache, g_hash_table_unref);
G_OBJECT_CLASS (sp_elf_symbol_resolver_parent_class)->finalize (object);
}
static void
sp_elf_symbol_resolver_class_init (SpElfSymbolResolverClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_elf_symbol_resolver_finalize;
}
static void
sp_elf_symbol_resolver_init (SpElfSymbolResolver *self)
{
self->lookasides = g_hash_table_new_full (NULL,
NULL,
NULL,
(GDestroyNotify)sp_map_lookaside_free);
self->bin_files = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify)bin_file_free);
self->tag_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
}
static void
sp_elf_symbol_resolver_load (SpSymbolResolver *resolver,
SpCaptureReader *reader)
{
SpElfSymbolResolver *self = (SpElfSymbolResolver *)resolver;
SpCaptureFrameType type;
g_assert (SP_IS_SYMBOL_RESOLVER (resolver));
g_assert (reader != NULL);
sp_capture_reader_reset (reader);
while (sp_capture_reader_peek_type (reader, &type))
{
const SpCaptureMap *ev;
SpMapLookaside *lookaside;
SpMap map;
if (type != SP_CAPTURE_FRAME_MAP)
{
if (!sp_capture_reader_skip (reader))
return;
continue;
}
ev = sp_capture_reader_read_map (reader);
map.start = ev->start;
map.end = ev->end;
map.offset = ev->offset;
map.inode = ev->inode;
map.filename = ev->filename;
lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (ev->frame.pid));
if (lookaside == NULL)
{
lookaside = sp_map_lookaside_new ();
g_hash_table_insert (self->lookasides, GINT_TO_POINTER (ev->frame.pid), lookaside);
}
sp_map_lookaside_insert (lookaside, &map);
}
}
static bin_file_t *
sp_elf_symbol_resolver_get_bin_file (SpElfSymbolResolver *self,
const gchar *filename)
{
bin_file_t *bin_file;
g_assert (SP_IS_ELF_SYMBOL_RESOLVER (self));
bin_file = g_hash_table_lookup (self->bin_files, filename);
if (bin_file == NULL)
{
const gchar *alternate = filename;
/*
* If we are in a new mount namespace, then rely on the sp_symbol_dirs
* to find us a locate to resolve the file where the CRC will match.
*
* TODO: We need to translate the path here so that we can locate the
* binary behind it (which then has links to the debug file in
* the section header).
*/
if (g_str_has_prefix (filename, "/newroot/"))
alternate += strlen ("/newroot");
bin_file = bin_file_new (alternate);
g_hash_table_insert (self->bin_files, g_strdup (filename), bin_file);
}
return bin_file;
}
static GQuark
guess_tag (SpElfSymbolResolver *self,
const SpMap *map)
{
g_assert (map != NULL);
g_assert (map->filename != NULL);
if (!g_hash_table_contains (self->tag_cache, map->filename))
{
GQuark tag = 0;
if (strstr (map->filename, "/libgobject-2.0."))
tag = g_quark_from_static_string ("GObject");
else if (strstr (map->filename, "/libglib-2.0."))
tag = g_quark_from_static_string ("GLib");
else if (strstr (map->filename, "/libgio-2.0."))
tag = g_quark_from_static_string ("Gio");
else if (strstr (map->filename, "/libgirepository-1.0."))
tag = g_quark_from_static_string ("Introspection");
else if (strstr (map->filename, "/libgtk-3."))
tag = g_quark_from_static_string ("Gtk+");
else if (strstr (map->filename, "/libgdk-3."))
tag = g_quark_from_static_string ("Gdk");
else if (strstr (map->filename, "/libgtksourceview-3.0"))
tag = g_quark_from_static_string ("GtkSourceView");
else if (strstr (map->filename, "/libpixman-1"))
tag = g_quark_from_static_string ("Pixman");
else if (strstr (map->filename, "/libcairo."))
tag = g_quark_from_static_string ("cairo");
else if (strstr (map->filename, "/libgstreamer-1."))
tag = g_quark_from_static_string ("GStreamer");
else if (strstr (map->filename, "/libX11."))
tag = g_quark_from_static_string ("X11");
else if (strstr (map->filename, "/libpango-1.0."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libpangocairo-1.0."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libpangomm-1.4."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libpangoft2-1.0"))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libpangoxft-1.0."))
tag = g_quark_from_static_string ("Pango");
else if (strstr (map->filename, "/libclutter-"))
tag = g_quark_from_static_string ("Clutter");
else if (strstr (map->filename, "/libcogl.") ||
strstr (map->filename, "/libcogl-"))
tag = g_quark_from_static_string ("Cogl");
else if (strstr (map->filename, "/libffi."))
tag = g_quark_from_static_string ("libffi");
else if (strstr (map->filename, "/libwayland-"))
tag = g_quark_from_static_string ("Wayland");
else if (strstr (map->filename, "/libinput."))
tag = g_quark_from_static_string ("libinput");
else if (strstr (map->filename, "/libgjs."))
tag = g_quark_from_static_string ("Gjs");
else if (strstr (map->filename, "/libmozjs-"))
tag = g_quark_from_static_string ("MozJS");
else if (strstr (map->filename, "/libGL."))
tag = g_quark_from_static_string ("GL");
else if (strstr (map->filename, "/libEGL."))
tag = g_quark_from_static_string ("EGL");
g_hash_table_insert (self->tag_cache,
g_strdup (map->filename),
GSIZE_TO_POINTER (tag));
}
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tag_cache, map->filename));
}
static gchar *
sp_elf_symbol_resolver_resolve_with_context (SpSymbolResolver *resolver,
guint64 time,
GPid pid,
SpAddressContext context,
SpCaptureAddress address,
GQuark *tag)
{
SpElfSymbolResolver *self = (SpElfSymbolResolver *)resolver;
const bin_symbol_t *bin_sym;
SpMapLookaside *lookaside;
const gchar *bin_sym_name;
const SpMap *map;
bin_file_t *bin_file;
g_assert (SP_IS_ELF_SYMBOL_RESOLVER (self));
if (context != SP_ADDRESS_CONTEXT_USER)
return NULL;
lookaside = g_hash_table_lookup (self->lookasides, GINT_TO_POINTER (pid));
if (lookaside == NULL)
return NULL;
map = sp_map_lookaside_lookup (lookaside, address);
if (map == NULL)
return NULL;
address -= map->start;
address += map->offset;
bin_file = sp_elf_symbol_resolver_get_bin_file (self, map->filename);
g_assert (bin_file != NULL);
if (map->inode && !bin_file_check_inode (bin_file, map->inode))
return g_strdup_printf ("%s: inode mismatch", map->filename);
bin_sym = bin_file_lookup_symbol (bin_file, address);
bin_sym_name = bin_symbol_get_name (bin_file, bin_sym);
if (map->filename)
*tag = guess_tag (self, map);
return elf_demangle (bin_sym_name);
}
static void
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
{
iface->load = sp_elf_symbol_resolver_load;
iface->resolve_with_context = sp_elf_symbol_resolver_resolve_with_context;
}
SpSymbolResolver *
sp_elf_symbol_resolver_new (void)
{
return g_object_new (SP_TYPE_ELF_SYMBOL_RESOLVER, NULL);
}

View File

@ -0,0 +1,34 @@
/* sp-elf-symbol-resolver.h
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_ELF_SYMBOL_RESOLVER_H
#define SP_ELF_SYMBOL_RESOLVER_H
#include "sp-symbol-resolver.h"
G_BEGIN_DECLS
#define SP_TYPE_ELF_SYMBOL_RESOLVER (sp_elf_symbol_resolver_get_type())
G_DECLARE_FINAL_TYPE (SpElfSymbolResolver, sp_elf_symbol_resolver, SP, ELF_SYMBOL_RESOLVER, GObject)
SpSymbolResolver *sp_elf_symbol_resolver_new (void);
G_END_DECLS
#endif /* SP_ELF_SYMBOL_RESOLVER_H */

View File

@ -0,0 +1,245 @@
/* sp-gjs-source.c
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
#include <string.h>
#include "sp-capture-reader.h"
#include "sp-gjs-source.h"
struct _SpGjsSource
{
GObject parent_instance;
SpCaptureWriter *writer;
GArray *pids;
GArray *enabled;
};
#define ENABLE_PROFILER 0x1
#define DISABLE_PROFILER 0x0
static void source_iface_init (SpSourceInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpGjsSource, sp_gjs_source, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
static void
sp_gjs_source_finalize (GObject *object)
{
SpGjsSource *self = (SpGjsSource *)object;
g_clear_pointer (&self->pids, g_array_unref);
g_clear_pointer (&self->enabled, g_array_unref);
g_clear_pointer (&self->writer, sp_capture_writer_unref);
G_OBJECT_CLASS (sp_gjs_source_parent_class)->finalize (object);
}
static void
sp_gjs_source_class_init (SpGjsSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_gjs_source_finalize;
}
static void
sp_gjs_source_init (SpGjsSource *self)
{
self->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
self->enabled = g_array_new (FALSE, FALSE, sizeof (GPid));
}
static void
sp_gjs_source_process_capture (SpGjsSource *self,
GPid pid,
const gchar *path)
{
g_autoptr(GError) error = NULL;
g_autoptr(SpCaptureReader) reader = NULL;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (self->writer != NULL);
g_assert (path != NULL);
if (!(reader = sp_capture_reader_new (path, &error)))
{
g_warning ("Failed to load capture: %s", error->message);
return;
}
if (!sp_capture_reader_splice (reader, self->writer, &error))
{
g_warning ("Failed to load capture: %s", error->message);
return;
}
}
static void
sp_gjs_source_process_captures (SpGjsSource *self)
{
guint i;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (self->writer != NULL);
for (i = 0; i < self->enabled->len; i++)
{
g_autofree gchar *filename = NULL;
g_autofree gchar *path = NULL;
GPid pid = g_array_index (self->enabled, GPid, i);
filename = g_strdup_printf ("gjs-profile-%u", (guint)pid);
path = g_build_filename (g_get_tmp_dir (), filename, NULL);
sp_gjs_source_process_capture (self, pid, path);
}
}
static void
sp_gjs_source_set_writer (SpSource *source,
SpCaptureWriter *writer)
{
SpGjsSource *self = (SpGjsSource *)source;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (writer != NULL);
self->writer = sp_capture_writer_ref (writer);
}
static gboolean
pid_is_profileable (GPid pid)
{
g_autofree gchar *path = NULL;
g_autofree gchar *contents = NULL;
const gchar *libgjs;
gsize len = 0;
g_assert (pid != -1);
/*
* Make sure this process has linked in libgjs. No sense in sending it a
* signal unless we know it can handle it.
*/
path = g_strdup_printf ("/proc/%d/maps", pid);
if (!g_file_get_contents (path, &contents, &len, NULL))
return FALSE;
if (NULL == (libgjs = strstr (contents, "libgjs."G_MODULE_SUFFIX)))
return FALSE;
return TRUE;
}
static void
sp_gjs_source_enable_pid (SpGjsSource *self,
GPid pid)
{
union sigval si;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (pid != -1);
si.sival_int = ENABLE_PROFILER;
if (0 != sigqueue (pid, SIGUSR2, si))
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
else
g_array_append_val (self->enabled, pid);
}
static void
sp_gjs_source_disable_pid (SpGjsSource *self,
GPid pid)
{
union sigval si;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (pid != -1);
si.sival_int = DISABLE_PROFILER;
if (0 != sigqueue (pid, SIGUSR2, si))
g_warning ("Failed to queue SIGUSR2 to pid %u", (guint)pid);
}
static void
sp_gjs_source_start (SpSource *source)
{
SpGjsSource *self = (SpGjsSource *)source;
guint i;
g_assert (SP_IS_GJS_SOURCE (self));
for (i = 0; i < self->pids->len; i++)
{
GPid pid = g_array_index (self->pids, GPid, i);
if (pid_is_profileable (pid))
sp_gjs_source_enable_pid (self, pid);
}
}
static void
sp_gjs_source_stop (SpSource *source)
{
SpGjsSource *self = (SpGjsSource *)source;
guint i;
g_assert (SP_IS_GJS_SOURCE (self));
for (i = 0; i < self->pids->len; i++)
{
GPid pid = g_array_index (self->pids, GPid, i);
if (pid_is_profileable (pid))
sp_gjs_source_disable_pid (self, pid);
}
sp_gjs_source_process_captures (self);
}
static void
sp_gjs_source_add_pid (SpSource *source,
GPid pid)
{
SpGjsSource *self = (SpGjsSource *)source;
g_assert (SP_IS_GJS_SOURCE (self));
g_assert (pid > -1);
g_array_append_val (self->pids, pid);
}
static void
source_iface_init (SpSourceInterface *iface)
{
iface->set_writer = sp_gjs_source_set_writer;
iface->start = sp_gjs_source_start;
iface->stop = sp_gjs_source_stop;
iface->add_pid = sp_gjs_source_add_pid;
}
SpSource *
sp_gjs_source_new (void)
{
return g_object_new (SP_TYPE_GJS_SOURCE, NULL);
}

View File

@ -0,0 +1,34 @@
/* sp-gjs-source.h
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_GJS_SOURCE_H
#define SP_GJS_SOURCE_H
#include "sp-source.h"
G_BEGIN_DECLS
#define SP_TYPE_GJS_SOURCE (sp_gjs_source_get_type())
G_DECLARE_FINAL_TYPE (SpGjsSource, sp_gjs_source, SP, GJS_SOURCE, GObject)
SpSource *sp_gjs_source_new (void);
G_END_DECLS
#endif /* SP_GJS_SOURCE_H */

View File

@ -0,0 +1,384 @@
/* sp-hostinfo-source.c
*
* Copyright 2016 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include "sp-hostinfo-source.h"
#define PROC_STAT_BUF_SIZE 4096
struct _SpHostinfoSource
{
GObject parent_instance;
guint handler;
gint n_cpu;
gint stat_fd;
SpCaptureWriter *writer;
GArray *cpu_info;
gchar *stat_buf;
};
typedef struct
{
gint counter_base;
gdouble total;
gdouble freq;
glong last_user;
glong last_idle;
glong last_system;
glong last_nice;
glong last_iowait;
glong last_irq;
glong last_softirq;
glong last_steal;
glong last_guest;
glong last_guest_nice;
} CpuInfo;
static void source_iface_init (SpSourceInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpHostinfoSource, sp_hostinfo_source, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
SpSource *
sp_hostinfo_source_new (void)
{
return g_object_new (SP_TYPE_HOSTINFO_SOURCE, NULL);
}
static gboolean
read_stat (SpHostinfoSource *self)
{
gssize len;
g_assert (self != NULL);
g_assert (self->stat_fd != -1);
g_assert (self->stat_buf != NULL);
if (lseek (self->stat_fd, 0, SEEK_SET) != 0)
return FALSE;
len = read (self->stat_fd, self->stat_buf, PROC_STAT_BUF_SIZE);
if (len <= 0)
return FALSE;
if (len < PROC_STAT_BUF_SIZE)
self->stat_buf[len] = 0;
else
self->stat_buf[PROC_STAT_BUF_SIZE-1] = 0;
return TRUE;
}
static void
poll_cpu (SpHostinfoSource *self)
{
gchar cpu[64] = { 0 };
glong user;
glong sys;
glong nice;
glong idle;
glong iowait;
glong irq;
glong softirq;
glong steal;
glong guest;
glong guest_nice;
glong user_calc;
glong system_calc;
glong nice_calc;
glong idle_calc;
glong iowait_calc;
glong irq_calc;
glong softirq_calc;
glong steal_calc;
glong guest_calc;
glong guest_nice_calc;
glong total;
gchar *line;
gint ret;
gint id;
if (read_stat (self))
{
line = self->stat_buf;
for (gsize i = 0; self->stat_buf[i]; i++)
{
if (self->stat_buf[i] == '\n')
{
self->stat_buf[i] = '\0';
if (strncmp (line, "cpu", 3) == 0)
{
if (isdigit (line[3]))
{
CpuInfo *cpu_info;
user = nice = sys = idle = id = 0;
ret = sscanf (line, "%s %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
cpu, &user, &nice, &sys, &idle,
&iowait, &irq, &softirq, &steal, &guest, &guest_nice);
if (ret != 11)
goto next;
ret = sscanf(cpu, "cpu%d", &id);
if (ret != 1 || id < 0 || id >= self->n_cpu)
goto next;
cpu_info = &g_array_index (self->cpu_info, CpuInfo, id);
user_calc = user - cpu_info->last_user;
nice_calc = nice - cpu_info->last_nice;
system_calc = sys - cpu_info->last_system;
idle_calc = idle - cpu_info->last_idle;
iowait_calc = iowait - cpu_info->last_iowait;
irq_calc = irq - cpu_info->last_irq;
softirq_calc = softirq - cpu_info->last_softirq;
steal_calc = steal - cpu_info->last_steal;
guest_calc = guest - cpu_info->last_guest;
guest_nice_calc = guest_nice - cpu_info->last_guest_nice;
total = user_calc + nice_calc + system_calc + idle_calc + iowait_calc + irq_calc + softirq_calc + steal_calc + guest_calc + guest_nice_calc;
cpu_info->total = ((total - idle_calc) / (gdouble)total) * 100.0;
cpu_info->last_user = user;
cpu_info->last_nice = nice;
cpu_info->last_idle = idle;
cpu_info->last_system = sys;
cpu_info->last_iowait = iowait;
cpu_info->last_irq = irq;
cpu_info->last_softirq = softirq;
cpu_info->last_steal = steal;
cpu_info->last_guest = guest;
cpu_info->last_guest_nice = guest_nice;
}
}
else
{
/* CPU info comes first. Skip further lines. */
break;
}
next:
line = &self->stat_buf[i + 1];
}
}
}
}
static void
publish_cpu (SpHostinfoSource *self)
{
SpCaptureCounterValue *counter_values;
guint *counter_ids;
counter_ids = alloca (sizeof *counter_ids * self->n_cpu * 2);
counter_values = alloca (sizeof *counter_values * self->n_cpu * 2);
for (guint i = 0; i < self->n_cpu; i++)
{
CpuInfo *info = &g_array_index (self->cpu_info, CpuInfo, i);
SpCaptureCounterValue *value = &counter_values[i*2];
guint *id = &counter_ids[i*2];
*id = info->counter_base;
value->vdbl = info->total;
id++;
value++;
*id = info->counter_base + 1;
value->vdbl = info->freq;
}
sp_capture_writer_set_counters (self->writer,
SP_CAPTURE_CURRENT_TIME,
-1,
getpid (),
counter_ids,
counter_values,
self->n_cpu * 2);
}
static gboolean
collect_hostinfo_cb (gpointer data)
{
SpHostinfoSource *self = data;
g_assert (SP_IS_HOSTINFO_SOURCE (self));
poll_cpu (self);
publish_cpu (self);
return G_SOURCE_CONTINUE;
}
static void
sp_hostinfo_source_finalize (GObject *object)
{
SpHostinfoSource *self = (SpHostinfoSource *)object;
if (self->handler)
{
g_source_remove (self->handler);
self->handler = 0;
}
g_clear_pointer (&self->writer, sp_capture_writer_unref);
g_clear_pointer (&self->cpu_info, g_array_unref);
g_clear_pointer (&self->stat_buf, g_free);
G_OBJECT_CLASS (sp_hostinfo_source_parent_class)->finalize (object);
}
static void
sp_hostinfo_source_class_init (SpHostinfoSourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_hostinfo_source_finalize;
}
static void
sp_hostinfo_source_init (SpHostinfoSource *self)
{
self->stat_fd = -1;
self->cpu_info = g_array_new (FALSE, TRUE, sizeof (CpuInfo));
self->stat_buf = g_malloc (PROC_STAT_BUF_SIZE);
}
static void
sp_hostinfo_source_set_writer (SpSource *source,
SpCaptureWriter *writer)
{
SpHostinfoSource *self = (SpHostinfoSource *)source;
g_assert (SP_IS_HOSTINFO_SOURCE (self));
g_assert (writer != NULL);
g_clear_pointer (&self->writer, sp_capture_writer_unref);
self->writer = sp_capture_writer_ref (writer);
}
static void
sp_hostinfo_source_start (SpSource *source)
{
SpHostinfoSource *self = (SpHostinfoSource *)source;
g_assert (SP_IS_HOSTINFO_SOURCE (self));
/* 20 samples per second */
self->handler = g_timeout_add (1000/20, collect_hostinfo_cb, self);
}
static void
sp_hostinfo_source_stop (SpSource *source)
{
SpHostinfoSource *self = (SpHostinfoSource *)source;
g_assert (SP_IS_HOSTINFO_SOURCE (self));
g_source_remove (self->handler);
self->handler = 0;
if (self->stat_fd != -1)
{
close (self->stat_fd);
self->stat_fd = -1;
}
sp_source_emit_finished (SP_SOURCE (self));
}
static void
sp_hostinfo_source_prepare (SpSource *source)
{
SpHostinfoSource *self = (SpHostinfoSource *)source;
SpCaptureCounter *counters;
g_assert (SP_IS_HOSTINFO_SOURCE (self));
self->stat_fd = open ("/proc/stat", O_RDONLY);
self->n_cpu = g_get_num_processors ();
g_array_set_size (self->cpu_info, 0);
counters = alloca (sizeof *counters * self->n_cpu * 2);
for (guint i = 0; i < self->n_cpu; i++)
{
SpCaptureCounter *ctr = &counters[i*2];
CpuInfo info = { 0 };
/*
* Request 2 counter values.
* One for CPU and one for Frequency.
*/
info.counter_base = sp_capture_writer_request_counter (self->writer, 2);
/*
* Define counters for capture file.
*/
ctr->id = info.counter_base;
ctr->type = SP_CAPTURE_COUNTER_DOUBLE;
ctr->value.vdbl = 0;
g_strlcpy (ctr->category, "CPU Percent", sizeof ctr->category);
g_snprintf (ctr->name, sizeof ctr->name, "Total CPU %d", i);
g_snprintf (ctr->description, sizeof ctr->description,
"Total CPU usage %d", i);
ctr++;
ctr->id = info.counter_base + 1;
ctr->type = SP_CAPTURE_COUNTER_DOUBLE;
ctr->value.vdbl = 0;
g_strlcpy (ctr->category, "CPU Frequency", sizeof ctr->category);
g_snprintf (ctr->name, sizeof ctr->name, "CPU %d", i);
g_snprintf (ctr->description, sizeof ctr->description,
"Frequency of CPU %d", i);
g_array_append_val (self->cpu_info, info);
}
sp_capture_writer_define_counters (self->writer,
SP_CAPTURE_CURRENT_TIME,
-1,
getpid (),
counters,
self->n_cpu * 2);
sp_source_emit_ready (SP_SOURCE (self));
}
static void
source_iface_init (SpSourceInterface *iface)
{
iface->set_writer = sp_hostinfo_source_set_writer;
iface->prepare = sp_hostinfo_source_prepare;
iface->start = sp_hostinfo_source_start;
iface->stop = sp_hostinfo_source_stop;
}

View File

@ -0,0 +1,35 @@
/* sp-hostinfo-source.h
*
* Copyright 2016 Christian Hergert <christian@hergert.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_HOSTINFO_SOURCE_H
#define SP_HOSTINFO_SOURCE_H
#include "sp-source.h"
G_BEGIN_DECLS
#define SP_TYPE_HOSTINFO_SOURCE (sp_hostinfo_source_get_type())
G_DECLARE_FINAL_TYPE (SpHostinfoSource, sp_hostinfo_source, SP, HOSTINFO_SOURCE, GObject)
SpSource *sp_hostinfo_source_new (void);
G_END_DECLS
#endif /* SP_HOSTINFO_SOURCE_H */

View File

@ -0,0 +1,121 @@
/* sp-jitmap-symbol-resolver.c
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "sp-kernel-symbol.h"
#include "sp-jitmap-symbol-resolver.h"
struct _SpJitmapSymbolResolver
{
GObject parent_instance;
GHashTable *jitmap;
};
static void symbol_resolver_iface_init (SpSymbolResolverInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpJitmapSymbolResolver,
sp_jitmap_symbol_resolver,
G_TYPE_OBJECT,
0,
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
symbol_resolver_iface_init))
static void
sp_jitmap_symbol_resolver_finalize (GObject *object)
{
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)object;
g_clear_pointer (&self->jitmap, g_hash_table_unref);
G_OBJECT_CLASS (sp_jitmap_symbol_resolver_parent_class)->finalize (object);
}
static void
sp_jitmap_symbol_resolver_class_init (SpJitmapSymbolResolverClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_jitmap_symbol_resolver_finalize;
}
static void
sp_jitmap_symbol_resolver_init (SpJitmapSymbolResolver *self)
{
self->jitmap = g_hash_table_new_full (NULL, NULL, NULL, g_free);
}
static void
sp_jitmap_symbol_resolver_load (SpSymbolResolver *resolver,
SpCaptureReader *reader)
{
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)resolver;
SpCaptureFrameType type;
g_assert (SP_IS_JITMAP_SYMBOL_RESOLVER (self));
g_assert (reader != NULL);
while (sp_capture_reader_peek_type (reader, &type))
{
g_autoptr(GHashTable) jitmap = NULL;
GHashTableIter iter;
SpCaptureAddress addr;
const gchar *str;
if (type != SP_CAPTURE_FRAME_JITMAP)
{
if (!sp_capture_reader_skip (reader))
return;
continue;
}
if (!(jitmap = sp_capture_reader_read_jitmap (reader)))
return;
g_hash_table_iter_init (&iter, jitmap);
while (g_hash_table_iter_next (&iter, (gpointer *)&addr, (gpointer *)&str))
g_hash_table_insert (self->jitmap, GSIZE_TO_POINTER (addr), g_strdup (str));
}
}
static gchar *
sp_jitmap_symbol_resolver_resolve (SpSymbolResolver *resolver,
guint64 time,
GPid pid,
SpCaptureAddress address,
GQuark *tag)
{
SpJitmapSymbolResolver *self = (SpJitmapSymbolResolver *)resolver;
g_assert (SP_IS_JITMAP_SYMBOL_RESOLVER (self));
*tag = 0;
return g_strdup (g_hash_table_lookup (self->jitmap, GSIZE_TO_POINTER (address)));
}
static void
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
{
iface->load = sp_jitmap_symbol_resolver_load;
iface->resolve = sp_jitmap_symbol_resolver_resolve;
}
SpSymbolResolver *
sp_jitmap_symbol_resolver_new (void)
{
return g_object_new (SP_TYPE_JITMAP_SYMBOL_RESOLVER, NULL);
}

View File

@ -0,0 +1,34 @@
/* sp-jitmap-symbol-resolver.h
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_JITMAP_SYMBOL_RESOLVER_H
#define SP_JITMAP_SYMBOL_RESOLVER_H
#include "sp-symbol-resolver.h"
G_BEGIN_DECLS
#define SP_TYPE_JITMAP_SYMBOL_RESOLVER (sp_jitmap_symbol_resolver_get_type())
G_DECLARE_FINAL_TYPE (SpJitmapSymbolResolver, sp_jitmap_symbol_resolver, SP, JITMAP_SYMBOL_RESOLVER, GObject)
SpSymbolResolver *sp_jitmap_symbol_resolver_new (void);
G_END_DECLS
#endif /* SP_JITMAP_SYMBOL_RESOLVER_H */

View File

@ -0,0 +1,82 @@
/* sp-kernel-symbol-resolver.c
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "sp-kernel-symbol.h"
#include "sp-kernel-symbol-resolver.h"
struct _SpKernelSymbolResolver
{
GObject parent_instance;
};
static GQuark linux_quark;
static gchar *
sp_kernel_symbol_resolver_resolve_with_context (SpSymbolResolver *resolver,
guint64 time,
GPid pid,
SpAddressContext context,
SpCaptureAddress address,
GQuark *tag)
{
const SpKernelSymbol *sym;
g_assert (SP_IS_SYMBOL_RESOLVER (resolver));
if (context != SP_ADDRESS_CONTEXT_KERNEL)
return NULL;
sym = sp_kernel_symbol_from_address (address);
if (sym != NULL)
{
*tag = linux_quark;
return g_strdup (sym->name);
}
return NULL;
}
static void
symbol_resolver_iface_init (SpSymbolResolverInterface *iface)
{
iface->resolve_with_context = sp_kernel_symbol_resolver_resolve_with_context;
}
G_DEFINE_TYPE_WITH_CODE (SpKernelSymbolResolver,
sp_kernel_symbol_resolver,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (SP_TYPE_SYMBOL_RESOLVER,
symbol_resolver_iface_init))
static void
sp_kernel_symbol_resolver_class_init (SpKernelSymbolResolverClass *klass)
{
linux_quark = g_quark_from_static_string ("Kernel");
}
static void
sp_kernel_symbol_resolver_init (SpKernelSymbolResolver *skernel)
{
}
SpSymbolResolver *
sp_kernel_symbol_resolver_new (void)
{
return g_object_new (SP_TYPE_KERNEL_SYMBOL_RESOLVER, NULL);
}

View File

@ -0,0 +1,34 @@
/* sp-kernel-symbol-resolver.h
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_KERNEL_SYMBOL_RESOLVER_H
#define SP_KERNEL_SYMBOL_RESOLVER_H
#include "sp-symbol-resolver.h"
G_BEGIN_DECLS
#define SP_TYPE_KERNEL_SYMBOL_RESOLVER (sp_kernel_symbol_resolver_get_type())
G_DECLARE_FINAL_TYPE (SpKernelSymbolResolver, sp_kernel_symbol_resolver, SP, KERNEL_SYMBOL_RESOLVER, GObject)
SpSymbolResolver *sp_kernel_symbol_resolver_new (void);
G_END_DECLS
#endif /* SP_KERNEL_SYMBOL_RESOLVER_H */

View File

@ -0,0 +1,328 @@
/* sp-kernel-symbol.c
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "sp-kernel-symbol"
#include "config.h"
#include <gio/gio.h>
#ifdef ENABLE_POLKIT
# include <polkit/polkit.h>
#endif
#include <sysprof-capture.h>
#include "sp-kallsyms.h"
#include "sp-line-reader.h"
#include "sp-kernel-symbol.h"
static GArray *kernel_symbols;
static GStringChunk *kernel_symbol_strs;
static GHashTable *kernel_symbols_skip_hash;
static const gchar *kernel_symbols_skip[] = {
/* IRQ stack */
"common_interrupt",
"apic_timer_interrupt",
"smp_apic_timer_interrupt",
"hrtimer_interrupt",
"__run_hrtimer",
"perf_swevent_hrtimer",
"perf_event_overflow",
"__perf_event_overflow",
"perf_prepare_sample",
"perf_callchain",
"perf_swcounter_hrtimer",
"perf_counter_overflow",
"__perf_counter_overflow",
"perf_counter_output",
/* NMI stack */
"nmi_stack_correct",
"do_nmi",
"notify_die",
"atomic_notifier_call_chain",
"notifier_call_chain",
"perf_event_nmi_handler",
"perf_counter_nmi_handler",
"intel_pmu_handle_irq",
"perf_event_overflow",
"perf_counter_overflow",
"__perf_event_overflow",
"perf_prepare_sample",
"perf_callchain",
};
static gint
sp_kernel_symbol_compare (gconstpointer a,
gconstpointer b)
{
const SpKernelSymbol *syma = a;
const SpKernelSymbol *symb = b;
if (syma->address > symb->address)
return 1;
else if (syma->address == symb->address)
return 0;
else
return -1;
}
static inline gboolean
type_is_ignored (guint8 type)
{
/* Only allow symbols in the text (code) section */
return (type != 't' && type != 'T');
}
static gboolean
authorize_proxy (GDBusConnection *conn)
{
#ifdef ENABLE_POLKIT
PolkitSubject *subject = NULL;
GPermission *permission = NULL;
const gchar *name;
g_assert (G_IS_DBUS_CONNECTION (conn));
name = g_dbus_connection_get_unique_name (conn);
if (name == NULL)
goto failure;
subject = polkit_system_bus_name_new (name);
if (subject == NULL)
goto failure;
permission = polkit_permission_new_sync ("org.gnome.sysprof2.get-kernel-symbols", subject, NULL, NULL);
if (permission == NULL)
goto failure;
if (!g_permission_acquire (permission, NULL, NULL))
goto failure;
return TRUE;
failure:
g_clear_object (&subject);
g_clear_object (&permission);
#endif
return FALSE;
}
static gboolean
sp_kernel_symbol_load_from_sysprofd (void)
{
g_autoptr(GDBusConnection) conn = NULL;
g_autoptr(GVariant) ret = NULL;
g_autoptr(GVariant) results = NULL;
g_autoptr(GArray) ar = NULL;
g_autoptr(GError) error = NULL;
GVariantIter iter;
const gchar *name;
guint64 addr;
guint8 type;
if (!(conn = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL)))
return FALSE;
if (!authorize_proxy (conn))
{
g_warning ("Failed to acquire sufficient credentials to read kernel symbols");
return FALSE;
}
ret = g_dbus_connection_call_sync (conn,
"org.gnome.Sysprof2",
"/org/gnome/Sysprof2",
"org.gnome.Sysprof2",
"GetKernelSymbols",
NULL,
G_VARIANT_TYPE ("(a(tys))"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if (error != NULL)
{
g_warning ("Failed to load symbols from sysprofd: %s", error->message);
return FALSE;
}
ar = g_array_new (FALSE, TRUE, sizeof (SpKernelSymbol));
results = g_variant_get_child_value (ret, 0);
g_variant_iter_init (&iter, results);
while (g_variant_iter_loop (&iter, "(ty&s)", &addr, &type, &name))
{
SpKernelSymbol sym;
if (type_is_ignored (type))
continue;
sym.address = addr;
sym.name = g_string_chunk_insert_const (kernel_symbol_strs, name);
g_array_append_val (ar, sym);
}
g_array_sort (ar, sp_kernel_symbol_compare);
#if 0
g_print ("First: 0x%lx Last: 0x%lx\n",
g_array_index (ar, SpKernelSymbol, 0).address,
g_array_index (ar, SpKernelSymbol, ar->len - 1).address);
#endif
kernel_symbols = g_steal_pointer (&ar);
return TRUE;
}
static gboolean
sp_kernel_symbol_load (void)
{
g_autoptr(GHashTable) skip = NULL;
g_autoptr(SpKallsyms) kallsyms = NULL;
g_autoptr(GArray) ar = NULL;
const gchar *name;
guint64 addr;
guint8 type;
skip = g_hash_table_new (g_str_hash, g_str_equal);
for (guint i = 0; i < G_N_ELEMENTS (kernel_symbols_skip); i++)
g_hash_table_insert (skip, (gchar *)kernel_symbols_skip[i], NULL);
kernel_symbols_skip_hash = g_steal_pointer (&skip);
kernel_symbol_strs = g_string_chunk_new (4096);
ar = g_array_new (FALSE, TRUE, sizeof (SpKernelSymbol));
if (!(kallsyms = sp_kallsyms_new (NULL)))
goto query_daemon;
while (sp_kallsyms_next (kallsyms, &name, &addr, &type))
{
SpKernelSymbol sym;
if (type_is_ignored (type))
continue;
sym.address = addr;
sym.name = g_string_chunk_insert_const (kernel_symbol_strs, name);
g_array_append_val (ar, sym);
}
if (ar->len == 0)
goto query_daemon;
g_array_sort (ar, sp_kernel_symbol_compare);
kernel_symbols = g_steal_pointer (&ar);
return TRUE;
query_daemon:
if (sp_kernel_symbol_load_from_sysprofd ())
return TRUE;
g_warning ("Kernel symbols will not be available.");
return FALSE;
}
static const SpKernelSymbol *
sp_kernel_symbol_lookup (SpKernelSymbol *symbols,
SpCaptureAddress address,
guint first,
guint last)
{
if (address >= symbols [last].address)
{
return &symbols [last];
}
else if (last - first < 3)
{
while (last >= first)
{
if (address >= symbols[last].address)
return &symbols [last];
last--;
}
return NULL;
}
else
{
int mid = (first + last) / 2;
if (symbols [mid].address > address)
return sp_kernel_symbol_lookup (symbols, address, first, mid);
else
return sp_kernel_symbol_lookup (symbols, address, mid, last);
}
}
/**
* sp_kernel_symbol_from_address:
* @address: the address of the instruction pointer
*
* Locates the kernel symbol that contains @address.
*
* Returns: (transfer none): An #SpKernelSymbol or %NULL.
*/
const SpKernelSymbol *
sp_kernel_symbol_from_address (SpCaptureAddress address)
{
const SpKernelSymbol *first;
const SpKernelSymbol *ret;
if G_UNLIKELY (kernel_symbols == NULL)
{
static gboolean failed;
if (failed)
return NULL;
if (!sp_kernel_symbol_load ())
{
failed = TRUE;
return NULL;
}
}
g_assert (kernel_symbols != NULL);
g_assert (kernel_symbols->len > 0);
/* Short circuit if this is out of range */
first = &g_array_index (kernel_symbols, SpKernelSymbol, 0);
if (address < first->address)
return NULL;
ret = sp_kernel_symbol_lookup ((SpKernelSymbol *)(gpointer)kernel_symbols->data,
address,
0,
kernel_symbols->len - 1);
/* We resolve all symbols, including ignored symbols so that we
* don't give back the wrong function juxtapose an ignored func.
*/
if (ret != NULL && g_hash_table_contains (kernel_symbols_skip_hash, ret->name))
return NULL;
return ret;
}

View File

@ -0,0 +1,36 @@
/* sp-kernel-symbol.h
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_KERNEL_SYMBOL_H
#define SP_KERNEL_SYMBOL_H
#include "sp-capture-types.h"
G_BEGIN_DECLS
typedef struct
{
SpCaptureAddress address;
const gchar *name;
} SpKernelSymbol;
const SpKernelSymbol *sp_kernel_symbol_from_address (SpCaptureAddress address);
G_END_DECLS
#endif /* SP_KERNEL_SYMBOL_H */

View File

@ -0,0 +1,824 @@
/* sp-local-profiler.c
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <glib.h>
#include <glib/gstdio.h>
#include <errno.h>
#include <unistd.h>
#include "sp-local-profiler.h"
#include "sp-platform.h"
typedef struct
{
SpCaptureWriter *writer;
/* All sources added */
GPtrArray *sources;
/* Array of GError failures */
GPtrArray *failures;
/* Sources currently starting */
GPtrArray *starting;
/* Sources currently stopping */
GPtrArray *stopping;
/* Sources that have failed or finished */
GPtrArray *finished_or_failed;
/* Pids to notify children about before prepare */
GArray *pids;
/* Timer for simple time tracking */
GTimer *timer;
guint timer_notify_source;
/* Arguments and environment variables for spawning */
gchar **spawn_argv;
gchar **spawn_env;
/* State flags */
guint is_running : 1;
guint is_stopping : 1;
guint is_starting : 1;
/*
* If we should spawn argv when starting up. This allows UI to set
* spawn argv/env but enable disable with a toggle.
*/
guint spawn : 1;
/* If we should inherit the environment when spawning */
guint spawn_inherit_environ : 1;
/*
* If we should profile the entire system. Setting this results in pids
* being ignored. This is primarily useful for UI to toggle on/off the
* feature of per-process vs whole-system.
*/
guint whole_system : 1;
/*
* If we got a stop request after calling start() but before we have had
* a chance to settle, then we need to stop immediately after starting.
* We do this to avoid a more complex state machine (for now).
*/
guint stop_after_starting : 1;
} SpLocalProfilerPrivate;
static void profiler_iface_init (SpProfilerInterface *iface);
G_DEFINE_TYPE_EXTENDED (SpLocalProfiler, sp_local_profiler, G_TYPE_OBJECT, 0,
G_ADD_PRIVATE (SpLocalProfiler)
G_IMPLEMENT_INTERFACE (SP_TYPE_PROFILER, profiler_iface_init))
enum {
PROP_0,
N_PROPS,
PROP_ELAPSED,
PROP_IS_MUTABLE,
PROP_IS_RUNNING,
PROP_SPAWN,
PROP_SPAWN_ARGV,
PROP_SPAWN_ENV,
PROP_SPAWN_INHERIT_ENVIRON,
PROP_WHOLE_SYSTEM,
};
static inline gint
_g_ptr_array_find (GPtrArray *ar,
gpointer item)
{
guint i;
for (i = 0; i < ar->len; i++)
{
if (item == g_ptr_array_index (ar, i))
return i;
}
return -1;
}
static inline gboolean
_g_ptr_array_contains (GPtrArray *ar,
gpointer item)
{
return (-1 != _g_ptr_array_find (ar, item));
}
static void
sp_local_profiler_clear_timer (SpLocalProfiler *self)
{
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_assert (SP_IS_LOCAL_PROFILER (self));
g_clear_pointer (&priv->timer, g_timer_destroy);
if (priv->timer_notify_source != 0)
{
g_source_remove (priv->timer_notify_source);
priv->timer_notify_source = 0;
}
}
static void
sp_local_profiler_real_stopped (SpProfiler *profiler)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
g_assert (SP_IS_LOCAL_PROFILER (self));
sp_local_profiler_clear_timer (self);
}
static gboolean
sp_local_profiler_notify_elapsed_cb (gpointer data)
{
SpLocalProfiler *self = data;
g_assert (SP_IS_LOCAL_PROFILER (self));
g_object_notify (G_OBJECT (self), "elapsed");
return G_SOURCE_CONTINUE;
}
static void
sp_local_profiler_finish_stopping (SpLocalProfiler *self)
{
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_assert (SP_IS_LOCAL_PROFILER (self));
g_assert (priv->is_starting == FALSE);
g_assert (priv->is_stopping == TRUE);
g_assert (priv->stopping->len == 0);
if (priv->failures->len > 0)
{
const GError *error = g_ptr_array_index (priv->failures, 0);
sp_profiler_emit_failed (SP_PROFILER (self), error);
}
priv->is_running = FALSE;
priv->is_stopping = FALSE;
sp_profiler_emit_stopped (SP_PROFILER (self));
g_object_notify (G_OBJECT (self), "is-mutable");
g_object_notify (G_OBJECT (self), "is-running");
}
static void
sp_local_profiler_stop (SpProfiler *profiler)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
guint i;
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
if (priv->is_starting)
{
priv->stop_after_starting = TRUE;
return;
}
if (priv->is_stopping || !priv->is_running)
return;
priv->is_stopping = TRUE;
/*
* First we add everything to the stopping list, so that we can
* be notified of when they have completed. If everything stopped
* synchronously, the stopping list will be empty after calling
* sp_source_stop() for every source. Otherwise, we need to delay
* stopping for a little bit.
*/
for (i = 0; i < priv->sources->len; i++)
{
SpSource *source = g_ptr_array_index (priv->sources, i);
if (!_g_ptr_array_contains (priv->finished_or_failed, source))
g_ptr_array_add (priv->stopping, g_object_ref (source));
}
for (i = 0; i < priv->sources->len; i++)
{
SpSource *source = g_ptr_array_index (priv->sources, i);
sp_source_stop (source);
}
if (priv->is_stopping && priv->stopping->len == 0)
sp_local_profiler_finish_stopping (self);
}
static void
sp_local_profiler_dispose (GObject *object)
{
SpLocalProfiler *self = (SpLocalProfiler *)object;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
if (priv->is_running || priv->is_starting)
{
sp_local_profiler_stop (SP_PROFILER (self));
return;
}
sp_local_profiler_clear_timer (self);
G_OBJECT_CLASS (sp_local_profiler_parent_class)->dispose (object);
}
static void
sp_local_profiler_finalize (GObject *object)
{
SpLocalProfiler *self = (SpLocalProfiler *)object;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_clear_pointer (&priv->writer, sp_capture_writer_unref);
g_clear_pointer (&priv->sources, g_ptr_array_unref);
g_clear_pointer (&priv->starting, g_ptr_array_unref);
g_clear_pointer (&priv->stopping, g_ptr_array_unref);
g_clear_pointer (&priv->failures, g_ptr_array_unref);
g_clear_pointer (&priv->finished_or_failed, g_ptr_array_unref);
g_clear_pointer (&priv->pids, g_array_unref);
G_OBJECT_CLASS (sp_local_profiler_parent_class)->finalize (object);
}
static void
sp_local_profiler_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SpLocalProfiler *self = SP_LOCAL_PROFILER (object);
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
switch (prop_id)
{
case PROP_ELAPSED:
g_value_set_double (value, priv->timer ? g_timer_elapsed (priv->timer, NULL) : 0.0);
break;
case PROP_IS_MUTABLE:
g_value_set_boolean (value, !(priv->is_starting || priv->is_starting || priv->is_running));
break;
case PROP_IS_RUNNING:
g_value_set_boolean (value, priv->is_running);
break;
case PROP_WHOLE_SYSTEM:
g_value_set_boolean (value, priv->whole_system);
break;
case PROP_SPAWN:
g_value_set_boolean (value, priv->spawn);
break;
case PROP_SPAWN_INHERIT_ENVIRON:
g_value_set_boolean (value, priv->spawn_inherit_environ);
break;
case PROP_SPAWN_ARGV:
g_value_set_boxed (value, priv->spawn_argv);
break;
case PROP_SPAWN_ENV:
g_value_set_boxed (value, priv->spawn_env);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_local_profiler_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SpLocalProfiler *self = SP_LOCAL_PROFILER (object);
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
switch (prop_id)
{
case PROP_WHOLE_SYSTEM:
priv->whole_system = g_value_get_boolean (value);
break;
case PROP_SPAWN:
priv->spawn = g_value_get_boolean (value);
break;
case PROP_SPAWN_INHERIT_ENVIRON:
priv->spawn_inherit_environ = g_value_get_boolean (value);
break;
case PROP_SPAWN_ARGV:
g_strfreev (priv->spawn_argv);
priv->spawn_argv = g_value_dup_boxed (value);
break;
case PROP_SPAWN_ENV:
g_strfreev (priv->spawn_env);
priv->spawn_env = g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sp_local_profiler_class_init (SpLocalProfilerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = sp_local_profiler_dispose;
object_class->finalize = sp_local_profiler_finalize;
object_class->get_property = sp_local_profiler_get_property;
object_class->set_property = sp_local_profiler_set_property;
g_object_class_override_property (object_class, PROP_ELAPSED, "elapsed");
g_object_class_override_property (object_class, PROP_IS_MUTABLE, "is-mutable");
g_object_class_override_property (object_class, PROP_IS_RUNNING, "is-running");
g_object_class_override_property (object_class, PROP_SPAWN, "spawn");
g_object_class_override_property (object_class, PROP_SPAWN_ARGV, "spawn-argv");
g_object_class_override_property (object_class, PROP_SPAWN_ENV, "spawn-env");
g_object_class_override_property (object_class, PROP_SPAWN_INHERIT_ENVIRON, "spawn-inherit-environ");
g_object_class_override_property (object_class, PROP_WHOLE_SYSTEM, "whole-system");
}
static void
sp_local_profiler_init (SpLocalProfiler *self)
{
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
priv->whole_system = TRUE;
priv->failures = g_ptr_array_new_with_free_func ((GDestroyNotify)g_error_free);
priv->sources = g_ptr_array_new_with_free_func (g_object_unref);
priv->starting = g_ptr_array_new_with_free_func (g_object_unref);
priv->stopping = g_ptr_array_new_with_free_func (g_object_unref);
priv->finished_or_failed = g_ptr_array_new_with_free_func (g_object_unref);
priv->pids = g_array_new (FALSE, FALSE, sizeof (GPid));
}
SpProfiler *
sp_local_profiler_new (void)
{
return g_object_new (SP_TYPE_LOCAL_PROFILER, NULL);
}
static void
sp_local_profiler_finish_startup (SpLocalProfiler *self)
{
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
guint i;
g_assert (SP_IS_LOCAL_PROFILER (self));
g_assert (priv->is_starting == TRUE);
g_assert (priv->starting->len == 0);
sp_local_profiler_clear_timer (self);
priv->timer = g_timer_new ();
/*
* Add a source to update our watchers of elapsed time.
* We use 1000 instead of add_seconds(1) so that we are
* not subject to as much drift.
*/
priv->timer_notify_source =
g_timeout_add (1000,
sp_local_profiler_notify_elapsed_cb,
self);
for (i = 0; i < priv->sources->len; i++)
{
SpSource *source = g_ptr_array_index (priv->sources, i);
sp_source_start (source);
}
priv->is_starting = FALSE;
/*
* If any of the sources failed during startup, we will have a non-empty
* failures list.
*/
if (priv->failures->len > 0)
{
const GError *error = g_ptr_array_index (priv->failures, 0);
g_object_ref (self);
sp_profiler_emit_failed (SP_PROFILER (self), error);
sp_local_profiler_stop (SP_PROFILER (self));
g_object_unref (self);
return;
}
priv->is_running = TRUE;
g_object_notify (G_OBJECT (self), "is-mutable");
g_object_notify (G_OBJECT (self), "is-running");
/*
* If all the sources are transient (in that they just generate information
* and then exit), we could be finished as soon as we complete startup.
*
* If we detect this, we stop immediately.
*/
if (priv->finished_or_failed->len == priv->sources->len || priv->stop_after_starting)
sp_local_profiler_stop (SP_PROFILER (self));
}
static void
sp_local_profiler_start (SpProfiler *profiler)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
guint i;
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
g_return_if_fail (priv->is_running == FALSE);
g_return_if_fail (priv->is_stopping == FALSE);
g_return_if_fail (priv->is_starting == FALSE);
g_clear_pointer (&priv->timer, g_timer_destroy);
g_object_notify (G_OBJECT (self), "elapsed");
if (priv->writer == NULL)
{
SpCaptureWriter *writer;
int fd;
if ((-1 == (fd = sp_memfd_create ("[sysprof]"))) ||
(NULL == (writer = sp_capture_writer_new_from_fd (fd, 0))))
{
const GError error = {
G_FILE_ERROR,
g_file_error_from_errno (errno),
(gchar *)g_strerror (errno)
};
if (fd != -1)
close (fd);
sp_profiler_emit_failed (SP_PROFILER (self), &error);
return;
}
sp_profiler_set_writer (SP_PROFILER (self), writer);
g_clear_pointer (&writer, sp_capture_writer_unref);
}
priv->is_running = TRUE;
priv->is_starting = TRUE;
if (priv->failures->len > 0)
g_ptr_array_remove_range (priv->failures, 0, priv->failures->len);
if (priv->spawn && priv->spawn_argv && priv->spawn_argv[0])
{
g_autoptr(GPtrArray) ar = g_ptr_array_new_with_free_func (g_free);
GPid pid;
GError *error = NULL;
if (priv->spawn_inherit_environ)
{
gchar **environ = g_get_environ ();
for (i = 0; environ[i]; i++)
g_ptr_array_add (ar, environ[i]);
g_free (environ);
}
if (priv->spawn_env)
{
for (i = 0; priv->spawn_env[i]; i++)
g_ptr_array_add (ar, g_strdup (priv->spawn_env[i]));
}
g_ptr_array_add (ar, NULL);
if (!g_spawn_async (g_get_home_dir (),
priv->spawn_argv,
(gchar **)ar->pdata,
(G_SPAWN_SEARCH_PATH |
G_SPAWN_STDOUT_TO_DEV_NULL |
G_SPAWN_STDOUT_TO_DEV_NULL),
NULL,
NULL,
&pid,
&error))
g_ptr_array_add (priv->failures, error);
else
g_array_append_val (priv->pids, pid);
}
for (i = 0; i < priv->sources->len; i++)
{
SpSource *source = g_ptr_array_index (priv->sources, i);
guint j;
if (priv->whole_system == FALSE)
{
for (j = 0; j < priv->pids->len; j++)
{
GPid pid = g_array_index (priv->pids, GPid, j);
sp_source_add_pid (source, pid);
}
}
sp_source_set_writer (source, priv->writer);
sp_source_prepare (source);
}
for (i = 0; i < priv->sources->len; i++)
{
SpSource *source = g_ptr_array_index (priv->sources, i);
if (!sp_source_get_is_ready (source))
g_ptr_array_add (priv->starting, g_object_ref (source));
}
if (priv->starting->len == 0)
sp_local_profiler_finish_startup (self);
}
static void
sp_local_profiler_set_writer (SpProfiler *profiler,
SpCaptureWriter *writer)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
g_return_if_fail (priv->is_running == FALSE);
g_return_if_fail (priv->is_stopping == FALSE);
g_return_if_fail (writer != NULL);
if (priv->writer != writer)
{
g_clear_pointer (&priv->writer, sp_capture_writer_unref);
if (writer != NULL)
priv->writer = sp_capture_writer_ref (writer);
}
}
static void
sp_local_profiler_track_completed (SpLocalProfiler *self,
SpSource *source)
{
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
gint i;
g_assert (SP_IS_LOCAL_PROFILER (self));
g_assert (SP_IS_SOURCE (source));
if (!_g_ptr_array_contains (priv->finished_or_failed, source))
g_ptr_array_add (priv->finished_or_failed, g_object_ref (source));
if (priv->is_starting)
{
i = _g_ptr_array_find (priv->starting, source);
if (i >= 0)
{
g_ptr_array_remove_index (priv->starting, i);
if (priv->starting->len == 0)
sp_local_profiler_finish_startup (self);
}
}
if (priv->is_stopping)
{
i = _g_ptr_array_find (priv->stopping, source);
if (i >= 0)
{
g_ptr_array_remove_index_fast (priv->stopping, i);
if ((priv->is_stopping == TRUE) && (priv->stopping->len == 0))
sp_local_profiler_finish_stopping (self);
}
}
if (!priv->is_starting)
{
if (priv->finished_or_failed->len == priv->sources->len)
sp_local_profiler_stop (SP_PROFILER (self));
}
}
static void
sp_local_profiler_source_finished (SpLocalProfiler *self,
SpSource *source)
{
g_assert (SP_IS_LOCAL_PROFILER (self));
g_assert (SP_IS_SOURCE (source));
sp_local_profiler_track_completed (self, source);
}
static void
sp_local_profiler_source_ready (SpLocalProfiler *self,
SpSource *source)
{
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
guint i;
g_assert (SP_IS_LOCAL_PROFILER (self));
g_assert (SP_IS_SOURCE (source));
for (i = 0; i < priv->starting->len; i++)
{
SpSource *ele = g_ptr_array_index (priv->starting, i);
if (ele == source)
{
g_ptr_array_remove_index_fast (priv->starting, i);
if ((priv->is_starting == TRUE) && (priv->starting->len == 0))
sp_local_profiler_finish_startup (self);
break;
}
}
}
static void
sp_local_profiler_source_failed (SpLocalProfiler *self,
const GError *reason,
SpSource *source)
{
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_assert (SP_IS_LOCAL_PROFILER (self));
g_assert (reason != NULL);
g_assert (SP_IS_SOURCE (source));
sp_local_profiler_track_completed (self, source);
/* Failure emitted out of band */
if (!priv->is_starting && !priv->is_stopping && !priv->is_running)
return;
g_ptr_array_add (priv->failures, g_error_copy (reason));
/* Ignore during start/stop, we handle this in other places */
if (priv->is_starting || priv->is_stopping)
return;
if (priv->is_running)
sp_local_profiler_stop (SP_PROFILER (self));
}
static void
sp_local_profiler_add_source (SpProfiler *profiler,
SpSource *source)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
g_return_if_fail (SP_IS_SOURCE (source));
g_return_if_fail (priv->is_running == FALSE);
g_return_if_fail (priv->is_starting == FALSE);
g_return_if_fail (priv->is_stopping == FALSE);
g_signal_connect_object (source,
"failed",
G_CALLBACK (sp_local_profiler_source_failed),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (source,
"finished",
G_CALLBACK (sp_local_profiler_source_finished),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (source,
"ready",
G_CALLBACK (sp_local_profiler_source_ready),
self,
G_CONNECT_SWAPPED);
g_ptr_array_add (priv->sources, g_object_ref (source));
}
static void
sp_local_profiler_add_pid (SpProfiler *profiler,
GPid pid)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
g_return_if_fail (pid > -1);
g_return_if_fail (priv->is_starting == FALSE);
g_return_if_fail (priv->is_stopping == FALSE);
g_return_if_fail (priv->is_running == FALSE);
g_array_append_val (priv->pids, pid);
}
static void
sp_local_profiler_remove_pid (SpProfiler *profiler,
GPid pid)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
guint i;
g_return_if_fail (SP_IS_LOCAL_PROFILER (self));
g_return_if_fail (pid > -1);
g_return_if_fail (priv->is_starting == FALSE);
g_return_if_fail (priv->is_stopping == FALSE);
g_return_if_fail (priv->is_running == FALSE);
for (i = 0; i < priv->pids->len; i++)
{
GPid ele = g_array_index (priv->pids, GPid, i);
if (ele == pid)
{
g_array_remove_index_fast (priv->pids, i);
break;
}
}
}
static const GPid *
sp_local_profiler_get_pids (SpProfiler *profiler,
guint *n_pids)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_return_val_if_fail (SP_IS_LOCAL_PROFILER (self), NULL);
g_return_val_if_fail (n_pids != NULL, NULL);
*n_pids = priv->pids->len;
return (GPid *)(gpointer)priv->pids->data;
}
static SpCaptureWriter *
sp_local_profiler_get_writer (SpProfiler *profiler)
{
SpLocalProfiler *self = (SpLocalProfiler *)profiler;
SpLocalProfilerPrivate *priv = sp_local_profiler_get_instance_private (self);
g_return_val_if_fail (SP_IS_LOCAL_PROFILER (self), NULL);
return priv->writer;
}
static void
profiler_iface_init (SpProfilerInterface *iface)
{
iface->add_pid = sp_local_profiler_add_pid;
iface->add_source = sp_local_profiler_add_source;
iface->get_pids = sp_local_profiler_get_pids;
iface->get_writer = sp_local_profiler_get_writer;
iface->remove_pid = sp_local_profiler_remove_pid;
iface->set_writer = sp_local_profiler_set_writer;
iface->start = sp_local_profiler_start;
iface->stop = sp_local_profiler_stop;
iface->stopped = sp_local_profiler_real_stopped;
}

View File

@ -0,0 +1,40 @@
/* sp-local-profiler.h
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_LOCAL_PROFILER_H
#define SP_LOCAL_PROFILER_H
#include "sp-profiler.h"
G_BEGIN_DECLS
#define SP_TYPE_LOCAL_PROFILER (sp_local_profiler_get_type())
G_DECLARE_DERIVABLE_TYPE (SpLocalProfiler, sp_local_profiler, SP, LOCAL_PROFILER, GObject)
struct _SpLocalProfilerClass
{
GObjectClass parent_class;
gpointer padding[8];
};
SpProfiler *sp_local_profiler_new (void);
G_END_DECLS
#endif /* SP_LOCAL_PROFILER_H */

View File

@ -0,0 +1,119 @@
/* sp-map-lookaside.c
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "sp-map-lookaside.h"
struct _SpMapLookaside
{
GSequence *seq;
GStringChunk *chunk;
};
static gint
sp_map_compare (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
const SpMap *map_a = a;
const SpMap *map_b = b;
return sp_capture_address_compare (map_a->start, map_b->start);
}
static gint
sp_map_compare_in_range (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
const SpMap *map_a = a;
const SpMap *map_b = b;
/*
* map_b is the needle for the search.
* Only map_b->start is set.
*/
if ((map_b->start >= map_a->start) && (map_b->start < map_a->end))
return 0;
return sp_capture_address_compare (map_a->start, map_b->start);
}
static void
sp_map_free (gpointer data)
{
SpMap *map = data;
g_slice_free (SpMap, map);
}
SpMapLookaside *
sp_map_lookaside_new (void)
{
SpMapLookaside *ret;
ret = g_slice_new (SpMapLookaside);
ret->seq = g_sequence_new (sp_map_free);
ret->chunk = g_string_chunk_new (4096);
return ret;
}
void
sp_map_lookaside_free (SpMapLookaside *self)
{
g_sequence_free (self->seq);
g_string_chunk_free (self->chunk);
g_slice_free (SpMapLookaside, self);
}
void
sp_map_lookaside_insert (SpMapLookaside *self,
const SpMap *map)
{
SpMap *copy;
g_assert (self != NULL);
g_assert (map != NULL);
copy = g_slice_new (SpMap);
copy->start = map->start;
copy->end = map->end;
copy->offset = map->offset;
copy->inode = map->inode;
copy->filename = g_string_chunk_insert_const (self->chunk, map->filename);
g_sequence_insert_sorted (self->seq, copy, sp_map_compare, NULL);
}
const SpMap *
sp_map_lookaside_lookup (SpMapLookaside *self,
SpCaptureAddress address)
{
SpMap map = { address };
GSequenceIter *iter;
g_assert (self != NULL);
iter = g_sequence_lookup (self->seq, &map, sp_map_compare_in_range, NULL);
if (iter != NULL)
return g_sequence_get (iter);
return NULL;
}

View File

@ -0,0 +1,50 @@
/* sp-map-lookaside.h
*
* Copyright 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SP_MAP_LOOKASIDE_H
#define SP_MAP_LOOKASIDE_H
#include <glib.h>
#include "sp-capture-types.h"
G_BEGIN_DECLS
typedef struct _SpMapLookaside SpMapLookaside;
typedef struct
{
SpCaptureAddress start;
SpCaptureAddress end;
off_t offset;
ino_t inode;
const gchar *filename;
} SpMap;
SpMapLookaside *sp_map_lookaside_new (void);
void sp_map_lookaside_insert (SpMapLookaside *self,
const SpMap *map);
const SpMap *sp_map_lookaside_lookup (SpMapLookaside *self,
SpCaptureAddress address);
void sp_map_lookaside_free (SpMapLookaside *self);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SpMapLookaside, sp_map_lookaside_free)
G_END_DECLS
#endif /* SP_MAP_LOOKASIDE_H */

View File

@ -0,0 +1,467 @@
/* sp-memory-source.c
*
* Copyright 2018 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#define G_LOG_DOMAIN "sp-memory-source"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <sysprof-capture.h>
#include <unistd.h>
#include "sp-memory-source.h"
#define BUF_SIZE 4096
struct _SpMemorySource
{
GObject parent_instance;
/* Capture writer to deliver samples */
SpCaptureWriter *writer;
/* 4k stat buffer for reading proc */
gchar *stat_buf;
/* Array of MemStat */
GArray *mem_stats;
/* Timeout to perform polling */
guint timer_source;
};
typedef struct
{
GPid pid;
int stat_fd;
union {
struct {
SpCaptureCounterValue used;
gint64 total;
gint64 avail;
gint64 free;
} sys;
struct {
SpCaptureCounterValue used;
gint64 size;
gint64 resident;
gint64 shared;
gint64 text;
gint64 data;
} proc;
};
guint counter_ids[1];
} MemStat;
static void source_iface_init (SpSourceInterface *iface);
G_DEFINE_TYPE_WITH_CODE (SpMemorySource, sp_memory_source, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init))
static GHashTable *keys;
static void
mem_stat_open (MemStat *st)
{
g_assert (st != NULL);
g_assert (st->stat_fd == -1);
if (st->pid != -1)
{
g_autofree gchar *path = g_strdup_printf ("/proc/%d/statm", st->pid);
if (-1 == (st->stat_fd = open (path, O_RDONLY)))
g_warning ("Failed to access statm for pid %d", st->pid);
}
else
{
st->stat_fd = open ("/proc/meminfo", O_RDONLY);
}
}
static void
mem_stat_close (MemStat *st)
{
g_assert (st != NULL);
if (st->stat_fd != -1)
{
close (st->stat_fd);
st->stat_fd = -1;
}
}
static void
mem_stat_parse_statm (MemStat *st,
gchar *buf)
{
g_assert (st != NULL);
g_assert (buf != NULL);
sscanf (buf,
"%"G_GINT64_FORMAT" "
"%"G_GINT64_FORMAT" "
"%"G_GINT64_FORMAT" "
"%"G_GINT64_FORMAT" "
"%*1c "
"%"G_GINT64_FORMAT,
&st->proc.size,
&st->proc.resident,
&st->proc.shared,
&st->proc.text,
&st->proc.data);
st->proc.used.vdbl = st->proc.size - st->proc.shared - st->proc.text - st->proc.data;
}
static void
mem_stat_parse_meminfo (MemStat *st,
gchar *buf)
{
gchar *bufptr = buf;
gchar *save = NULL;
g_assert (st != NULL);
g_assert (buf != NULL);
for (;;)
{
goffset off;
gchar *key;
gchar *value;
gchar *unit;
gint64 v64;
gint64 *v64ptr;
/* Get the data key name */
if (!(key = strtok_r (bufptr, " \n\t:", &save)))
break;
bufptr = NULL;
/* Offset from self to save value. Stop after getting to
* last value we care about.
*/
if (!(off = GPOINTER_TO_UINT (g_hash_table_lookup (keys, key))))
break;
/* Get the data value */
if (!(value = strtok_r (bufptr, " \n\t:", &save)))
break;
/* Parse the numeric value of this column */
v64 = g_ascii_strtoll (value, NULL, 10);
if ((v64 == G_MININT64 || v64 == G_MAXINT64) && errno == ERANGE)
break;
/* Get the data unit */
unit = strtok_r (bufptr, " \n\t:", &save);
if (g_strcmp0 (unit, "kB") == 0)
v64 *= 1024;
else if (g_strcmp0 (unit, "mB") == 0)
v64 *= 1024 * 1024;
v64ptr = (gint64 *)(gpointer)(((gchar *)st) + off);
*v64ptr = v64;
}
/* Create pre-compiled value for used to simplify display */
st->sys.used.vdbl = (gdouble)st->sys.total - (gdouble)st->sys.avail;
}
static void
mem_stat_poll (MemStat *st,
gchar *stat_buf)
{
gssize r;
g_assert (st != NULL);
g_assert (st->stat_fd != -1);
/* Seek to begin of FD to reset the proc read */
if ((r = lseek (st->stat_fd, 0, SEEK_SET)) < 0)
return;
/* Now read the updated proc buffer */
if ((r = read (st->stat_fd, stat_buf, BUF_SIZE)) < 0)
return;
if (r < BUF_SIZE)
stat_buf[r] = '\0';
stat_buf[BUF_SIZE-1] = '\0';
if (st->pid == -1)
mem_stat_parse_meminfo (st, stat_buf);
else
mem_stat_parse_statm (st, stat_buf);
}
static void
mem_stat_publish (MemStat *st,
SpCaptureWriter *writer,
gint64 current_time)
{
g_assert (st != NULL);
g_assert (writer != NULL);
sp_capture_writer_set_counters (writer,
current_time,
-1,
st->pid,
st->counter_ids,
st->pid == -1 ? &st->sys.used : &st->proc.used,
1);
}
/**
* sp_memory_source_new:
*
* Create a new #SpMemorySource.
*
* Use this source to record basic memory statistics about the system
* such as Available, Free, and Total Memory.
*
* Returns: (transfer full): a newly created #SpMemorySource
* Since: 3.32
*/
SpSource *
sp_memory_source_new (void)
{
return g_object_new (SP_TYPE_MEMORY_SOURCE, NULL);
}
static void
sp_memory_source_finalize (GObject *object)
{
SpMemorySource *self = (SpMemorySource *)object;
if (self->timer_source != 0)
{
g_source_remove (self->timer_source);
self->timer_source = 0;
}
g_clear_pointer (&self->stat_buf, g_free);
g_clear_pointer (&self->writer, sp_capture_writer_unref);
g_clear_pointer (&self->mem_stats, g_array_unref);
G_OBJECT_CLASS (sp_memory_source_parent_class)->finalize (object);
}
static void
sp_memory_source_class_init (SpMemorySourceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sp_memory_source_finalize;
keys = g_hash_table_new (g_str_hash, g_str_equal);
#define ADD_OFFSET(n,o) \
g_hash_table_insert (keys, (gchar *)n, GUINT_TO_POINTER (o))
ADD_OFFSET ("MemTotal", G_STRUCT_OFFSET (MemStat, sys.total));
ADD_OFFSET ("MemFree", G_STRUCT_OFFSET (MemStat, sys.free));
ADD_OFFSET ("MemAvailable", G_STRUCT_OFFSET (MemStat, sys.avail));
#undef ADD_OFFSET
}
static void
sp_memory_source_init (SpMemorySource *self)
{
self->stat_buf = g_malloc (BUF_SIZE);
self->mem_stats = g_array_new (FALSE, FALSE, sizeof (MemStat));
}
static void
sp_memory_source_set_writer (SpSource *source,
SpCaptureWriter *writer)
{
SpMemorySource *self = (SpMemorySource *)source;
g_assert (SP_IS_SOURCE (self));
g_assert (writer != NULL);
g_assert (self->writer == NULL);
self->writer = sp_capture_writer_ref (writer);
}
static void
sp_memory_source_prepare (SpSource *source)
{
SpMemorySource *self = (SpMemorySource *)source;
g_assert (SP_IS_MEMORY_SOURCE (self));
g_assert (self->writer != NULL);
/* If no pids are registered, setup a system memory stat */
if (self->mem_stats->len == 0)
{
MemStat st = {0};
st.pid = -1;
st.stat_fd = -1;
g_array_append_val (self->mem_stats, st);
}
/* Open FDs to all the necessary files in proc. We will re-use
* them to avoid re-opening files later.
*/
for (guint i = 0; i < self->mem_stats->len; i++)
{
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
SpCaptureCounter counters[5];
guint base;
mem_stat_open (st);
if (st->pid == -1)
{
base = sp_capture_writer_request_counter (self->writer, 1);
g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category);
g_strlcpy (counters[0].name, "Used", sizeof counters[0].name);
g_strlcpy (counters[0].description, "Memory used by system", sizeof counters[0].description);
counters[0].id = st->counter_ids[0] = base;
counters[0].type = SP_CAPTURE_COUNTER_DOUBLE;
counters[0].value.vdbl = 0;
sp_capture_writer_define_counters (self->writer,
SP_CAPTURE_CURRENT_TIME,
-1,
-1,
counters,
1);
}
else
{
base = sp_capture_writer_request_counter (self->writer, 1);
g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category);
g_strlcpy (counters[0].name, "Used", sizeof counters[0].name);
g_strlcpy (counters[0].description, "Memory used by process", sizeof counters[0].description);
counters[0].id = st->counter_ids[0] = base;
counters[0].type = SP_CAPTURE_COUNTER_DOUBLE;
counters[0].value.vdbl = 0;
sp_capture_writer_define_counters (self->writer,
SP_CAPTURE_CURRENT_TIME,
-1,
st->pid,
counters,
1);
}
}
sp_source_emit_ready (SP_SOURCE (self));
}
static gboolean
sp_memory_source_timer_cb (SpMemorySource *self)
{
gint64 current_time;
g_assert (SP_IS_MEMORY_SOURCE (self));
g_assert (self->writer != NULL);
current_time = sp_clock_get_current_time ();
for (guint i = 0; i < self->mem_stats->len; i++)
{
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
mem_stat_poll (st, self->stat_buf);
mem_stat_publish (st, self->writer, current_time);
}
return G_SOURCE_CONTINUE;
}
static void
sp_memory_source_start (SpSource *source)
{
SpMemorySource *self = (SpMemorySource *)source;
g_assert (SP_IS_MEMORY_SOURCE (self));
/* Poll 4x/sec for memory stats */
self->timer_source = g_timeout_add_full (G_PRIORITY_HIGH,
1000 / 4,
(GSourceFunc)sp_memory_source_timer_cb,
self,
NULL);
}
static void
sp_memory_source_stop (SpSource *source)
{
SpMemorySource *self = (SpMemorySource *)source;
g_assert (SP_IS_MEMORY_SOURCE (self));
if (self->timer_source != 0)
{
g_source_remove (self->timer_source);
self->timer_source = 0;
}
for (guint i = 0; i < self->mem_stats->len; i++)
{
MemStat *st = &g_array_index (self->mem_stats, MemStat, i);
mem_stat_close (st);
}
sp_source_emit_finished (source);
}
static void
sp_memory_source_add_pid (SpSource *source,
GPid pid)
{
SpMemorySource *self = (SpMemorySource *)source;
MemStat st = {0};
g_assert (SP_IS_MEMORY_SOURCE (self));
st.pid = pid;
st.stat_fd = -1;
g_array_append_val (self->mem_stats, st);
}
static void
source_iface_init (SpSourceInterface *iface)
{
iface->set_writer = sp_memory_source_set_writer;
iface->prepare = sp_memory_source_prepare;
iface->start = sp_memory_source_start;
iface->stop = sp_memory_source_stop;
iface->add_pid = sp_memory_source_add_pid;
}

View File

@ -0,0 +1,33 @@
/* sp-memory-source.h
*
* Copyright 2018 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "sp-source.h"
G_BEGIN_DECLS
#define SP_TYPE_MEMORY_SOURCE (sp_memory_source_get_type())
G_DECLARE_FINAL_TYPE (SpMemorySource, sp_memory_source, SP, MEMORY_SOURCE, GObject)
SpSource *sp_memory_source_new (void);
G_END_DECLS

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View 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

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

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

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

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

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

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