Files
sysprof/src/libsysprof-capture/sysprof-capture-writer.c
2019-06-05 15:10:48 -07:00

1466 lines
40 KiB
C

/* sysprof-capture-writer.c
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Subject to the terms and conditions of this license, each copyright holder
* and contributor hereby grants to those receiving rights under this license
* a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
* irrevocable (except for failure to satisfy the conditions of this license)
* patent license to make, have made, use, offer to sell, sell, import, and
* otherwise transfer this software, where such license applies only to those
* patent claims, already acquired or hereafter acquired, licensable by such
* copyright holder or contributor that are necessarily infringed by:
*
* (a) their Contribution(s) (the licensed copyrights of copyright holders
* and non-copyrightable additions of contributors, in source or binary
* form) alone; or
*
* (b) combination of their Contribution(s) with the work of authorship to
* which such Contribution(s) was added by such copyright holder or
* contributor, if, at the time the Contribution is added, such addition
* causes such combination to be necessarily infringed. The patent license
* shall not apply to any other combinations which include the
* Contribution.
*
* Except as expressly stated above, no rights or licenses from any copyright
* holder or contributor is granted under this license, whether expressly, by
* implication, estoppel or otherwise.
*
* DISCLAIMER
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
*/
#define G_LOG_DOMAIN "sysprof-capture-writer"
#include "config.h"
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <errno.h>
#include <fcntl.h>
#include <glib/gstdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "sysprof-capture-reader.h"
#include "sysprof-capture-util-private.h"
#include "sysprof-capture-writer.h"
#define DEFAULT_BUFFER_SIZE (_sysprof_getpagesize() * 64L)
#define INVALID_ADDRESS (G_GUINT64_CONSTANT(0))
#define MAX_COUNTERS ((1 << 24) - 1)
typedef struct
{
/* A pinter into the string buffer */
const gchar *str;
/* The unique address for the string */
guint64 addr;
} SysprofCaptureJitmapBucket;
struct _SysprofCaptureWriter
{
/*
* This is our buffer location for incoming strings. This is used
* similarly to GStringChunk except there is only one-page, and after
* it fills, we flush to disk.
*
* This is paired with a closed hash table for deduplication.
*/
gchar addr_buf[4096*4];
/* Our hashtable for deduplication. */
SysprofCaptureJitmapBucket addr_hash[512];
/* We keep the large fields above so that our allocation will be page
* alinged for the write buffer. This improves the performance of large
* writes to the target file-descriptor.
*/
volatile gint ref_count;
/*
* Our address sequence counter. The value that comes from
* monotonically increasing this is OR'd with JITMAP_MARK to denote
* the address name should come from the JIT map.
*/
gsize addr_seq;
/* Our position in addr_buf. */
gsize addr_buf_pos;
/*
* The number of hash table items in @addr_hash. This is an
* optimization so that we can avoid calculating the number of strings
* when flushing out the jitmap.
*/
guint addr_hash_size;
/* Capture file handle */
int fd;
/* Our write buffer for fd */
guint8 *buf;
gsize pos;
gsize len;
/* counter id sequence */
gint next_counter_id;
/* Statistics while recording */
SysprofCaptureStat stat;
};
static inline void
sysprof_capture_writer_frame_init (SysprofCaptureFrame *frame_,
gint len,
gint cpu,
gint32 pid,
gint64 time_,
SysprofCaptureFrameType type)
{
g_assert (frame_ != NULL);
frame_->len = len;
frame_->cpu = cpu;
frame_->pid = pid;
frame_->time = time_;
frame_->type = type;
frame_->padding1 = 0;
frame_->padding2 = 0;
}
static void
sysprof_capture_writer_finalize (SysprofCaptureWriter *self)
{
if (self != NULL)
{
sysprof_capture_writer_flush (self);
close (self->fd);
g_free (self->buf);
g_free (self);
}
}
SysprofCaptureWriter *
sysprof_capture_writer_ref (SysprofCaptureWriter *self)
{
g_assert (self != NULL);
g_assert (self->ref_count > 0);
g_atomic_int_inc (&self->ref_count);
return self;
}
void
sysprof_capture_writer_unref (SysprofCaptureWriter *self)
{
g_assert (self != NULL);
g_assert (self->ref_count > 0);
if (g_atomic_int_dec_and_test (&self->ref_count))
sysprof_capture_writer_finalize (self);
}
static gboolean
sysprof_capture_writer_flush_data (SysprofCaptureWriter *self)
{
const guint8 *buf;
gssize written;
gsize to_write;
g_assert (self != NULL);
g_assert (self->pos <= self->len);
g_assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0);
if (self->pos == 0)
return TRUE;
buf = self->buf;
to_write = self->pos;
while (to_write > 0)
{
written = _sysprof_write (self->fd, buf, to_write);
if (written < 0)
return FALSE;
if (written == 0 && errno != EAGAIN)
return FALSE;
g_assert (written <= (gssize)to_write);
buf += written;
to_write -= written;
}
self->pos = 0;
return TRUE;
}
static inline void
sysprof_capture_writer_realign (gsize *pos)
{
*pos = (*pos + SYSPROF_CAPTURE_ALIGN - 1) & ~(SYSPROF_CAPTURE_ALIGN - 1);
}
static inline gboolean
sysprof_capture_writer_ensure_space_for (SysprofCaptureWriter *self,
gsize len)
{
/* Check for max frame size */
if (len > G_MAXUSHORT)
return FALSE;
if ((self->len - self->pos) < len)
{
if (!sysprof_capture_writer_flush_data (self))
return FALSE;
}
return TRUE;
}
static inline gpointer
sysprof_capture_writer_allocate (SysprofCaptureWriter *self,
gsize *len)
{
gpointer p;
g_assert (self != NULL);
g_assert (len != NULL);
g_assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0);
sysprof_capture_writer_realign (len);
if (!sysprof_capture_writer_ensure_space_for (self, *len))
return NULL;
p = (gpointer)&self->buf[self->pos];
self->pos += *len;
g_assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0);
return p;
}
static gboolean
sysprof_capture_writer_flush_jitmap (SysprofCaptureWriter *self)
{
SysprofCaptureJitmap jitmap;
gssize r;
gsize len;
g_assert (self != NULL);
if (self->addr_hash_size == 0)
return TRUE;
g_assert (self->addr_buf_pos > 0);
len = sizeof jitmap + self->addr_buf_pos;
sysprof_capture_writer_realign (&len);
sysprof_capture_writer_frame_init (&jitmap.frame,
len,
-1,
_sysprof_getpid (),
SYSPROF_CAPTURE_CURRENT_TIME,
SYSPROF_CAPTURE_FRAME_JITMAP);
jitmap.n_jitmaps = self->addr_hash_size;
if (sizeof jitmap != _sysprof_write (self->fd, &jitmap, sizeof jitmap))
return FALSE;
r = _sysprof_write (self->fd, self->addr_buf, len - sizeof jitmap);
if (r < 0 || (gsize)r != (len - sizeof jitmap))
return FALSE;
self->addr_buf_pos = 0;
self->addr_hash_size = 0;
memset (self->addr_hash, 0, sizeof self->addr_hash);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_JITMAP]++;
return TRUE;
}
static gboolean
sysprof_capture_writer_lookup_jitmap (SysprofCaptureWriter *self,
const gchar *name,
SysprofCaptureAddress *addr)
{
guint hash;
guint i;
g_assert (self != NULL);
g_assert (name != NULL);
g_assert (addr != NULL);
hash = g_str_hash (name) % G_N_ELEMENTS (self->addr_hash);
for (i = hash; i < G_N_ELEMENTS (self->addr_hash); i++)
{
SysprofCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (bucket->str == NULL)
return FALSE;
if (strcmp (bucket->str, name) == 0)
{
*addr = bucket->addr;
return TRUE;
}
}
for (i = 0; i < hash; i++)
{
SysprofCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (bucket->str == NULL)
return FALSE;
if (strcmp (bucket->str, name) == 0)
{
*addr = bucket->addr;
return TRUE;
}
}
return FALSE;
}
static SysprofCaptureAddress
sysprof_capture_writer_insert_jitmap (SysprofCaptureWriter *self,
const gchar *str)
{
SysprofCaptureAddress addr;
gchar *dst;
gsize len;
guint hash;
guint i;
g_assert (self != NULL);
g_assert (str != NULL);
g_assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0);
len = sizeof addr + strlen (str) + 1;
if ((self->addr_hash_size == G_N_ELEMENTS (self->addr_hash)) ||
((sizeof self->addr_buf - self->addr_buf_pos) < len))
{
if (!sysprof_capture_writer_flush_jitmap (self))
return INVALID_ADDRESS;
g_assert (self->addr_hash_size == 0);
g_assert (self->addr_buf_pos == 0);
}
g_assert (self->addr_hash_size < G_N_ELEMENTS (self->addr_hash));
g_assert (len > sizeof addr);
/* Allocate the next unique address */
addr = SYSPROF_CAPTURE_JITMAP_MARK | ++self->addr_seq;
/* Copy the address into the buffer */
dst = (gchar *)&self->addr_buf[self->addr_buf_pos];
memcpy (dst, &addr, sizeof addr);
/*
* Copy the string into the buffer, keeping dst around for
* when we insert into the hashtable.
*/
dst += sizeof addr;
memcpy (dst, str, len - sizeof addr);
/* Advance our string cache position */
self->addr_buf_pos += len;
g_assert (self->addr_buf_pos <= sizeof self->addr_buf);
/* Now place the address into the hashtable */
hash = g_str_hash (str) % G_N_ELEMENTS (self->addr_hash);
/* Start from the current hash bucket and go forward */
for (i = hash; i < G_N_ELEMENTS (self->addr_hash); i++)
{
SysprofCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (G_LIKELY (bucket->str == NULL))
{
bucket->str = dst;
bucket->addr = addr;
self->addr_hash_size++;
return addr;
}
}
/* Wrap around to the beginning */
for (i = 0; i < hash; i++)
{
SysprofCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (G_LIKELY (bucket->str == NULL))
{
bucket->str = dst;
bucket->addr = addr;
self->addr_hash_size++;
return addr;
}
}
g_assert_not_reached ();
return INVALID_ADDRESS;
}
SysprofCaptureWriter *
sysprof_capture_writer_new_from_fd (int fd,
gsize buffer_size)
{
g_autofree gchar *nowstr = NULL;
SysprofCaptureWriter *self;
SysprofCaptureFileHeader *header;
GTimeVal tv;
gsize header_len = sizeof(*header);
if (fd < 0)
return NULL;
if (buffer_size == 0)
buffer_size = DEFAULT_BUFFER_SIZE;
g_assert (fd != -1);
g_assert (buffer_size % _sysprof_getpagesize() == 0);
/* This is only useful on files, memfd, etc */
if (ftruncate (fd, 0) != 0) { /* Do Nothing */ }
self = g_new0 (SysprofCaptureWriter, 1);
self->ref_count = 1;
self->fd = fd;
self->buf = (guint8 *)g_malloc0 (buffer_size);
self->len = buffer_size;
self->next_counter_id = 1;
g_get_current_time (&tv);
nowstr = g_time_val_to_iso8601 (&tv);
header = sysprof_capture_writer_allocate (self, &header_len);
if (header == NULL)
{
sysprof_capture_writer_finalize (self);
return NULL;
}
header->magic = SYSPROF_CAPTURE_MAGIC;
header->version = 1;
#ifdef G_LITTLE_ENDIAN
header->little_endian = TRUE;
#else
header->little_endian = FALSE;
#endif
header->padding = 0;
g_strlcpy (header->capture_time, nowstr, sizeof header->capture_time);
header->time = SYSPROF_CAPTURE_CURRENT_TIME;
header->end_time = 0;
memset (header->suffix, 0, sizeof header->suffix);
if (!sysprof_capture_writer_flush_data (self))
{
sysprof_capture_writer_finalize (self);
return NULL;
}
g_assert (self->pos == 0);
g_assert (self->len > 0);
g_assert (self->len % _sysprof_getpagesize() == 0);
g_assert (self->buf != NULL);
g_assert (self->addr_hash_size == 0);
g_assert (self->fd != -1);
return self;
}
SysprofCaptureWriter *
sysprof_capture_writer_new (const gchar *filename,
gsize buffer_size)
{
SysprofCaptureWriter *self;
int fd;
g_assert (filename != NULL);
g_assert (buffer_size % _sysprof_getpagesize() == 0);
if ((-1 == (fd = open (filename, O_CREAT | O_RDWR, 0640))) ||
(-1 == ftruncate (fd, 0L)))
return NULL;
self = sysprof_capture_writer_new_from_fd (fd, buffer_size);
if (self == NULL)
close (fd);
return self;
}
gboolean
sysprof_capture_writer_add_map (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
guint64 start,
guint64 end,
guint64 offset,
guint64 inode,
const gchar *filename)
{
SysprofCaptureMap *ev;
gsize len;
if (filename == NULL)
filename = "";
g_assert (self != NULL);
g_assert (filename != NULL);
len = sizeof *ev + strlen (filename) + 1;
ev = (SysprofCaptureMap *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_MAP);
ev->start = start;
ev->end = end;
ev->offset = offset;
ev->inode = inode;
g_strlcpy (ev->filename, filename, len - sizeof *ev);
ev->filename[len - sizeof *ev - 1] = '\0';
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_MAP]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_mark (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
guint64 duration,
const gchar *group,
const gchar *name,
const gchar *message)
{
SysprofCaptureMark *ev;
gsize message_len;
gsize len;
g_assert (self != NULL);
g_assert (name != NULL);
g_assert (group != NULL);
if (message == NULL)
message = "";
message_len = strlen (message) + 1;
len = sizeof *ev + message_len;
ev = (SysprofCaptureMark *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_MARK);
ev->duration = duration;
g_strlcpy (ev->group, group, sizeof ev->group);
g_strlcpy (ev->name, name, sizeof ev->name);
memcpy (ev->message, message, message_len);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_MARK]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_metadata (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *id,
const gchar *metadata,
gssize metadata_len)
{
SysprofCaptureMetadata *ev;
gsize len;
g_assert (self != NULL);
g_assert (id != NULL);
if (metadata == NULL)
{
metadata = "";
len = 0;
}
if (metadata_len < 0)
metadata_len = strlen (metadata);
len = sizeof *ev + metadata_len + 1;
ev = (SysprofCaptureMetadata *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_METADATA);
g_strlcpy (ev->id, id, sizeof ev->id);
memcpy (ev->metadata, metadata, metadata_len);
ev->metadata[metadata_len] = 0;
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_METADATA]++;
return TRUE;
}
SysprofCaptureAddress
sysprof_capture_writer_add_jitmap (SysprofCaptureWriter *self,
const gchar *name)
{
SysprofCaptureAddress addr = INVALID_ADDRESS;
if (name == NULL)
name = "";
g_assert (self != NULL);
g_assert (name != NULL);
if (!sysprof_capture_writer_lookup_jitmap (self, name, &addr))
addr = sysprof_capture_writer_insert_jitmap (self, name);
return addr;
}
gboolean
sysprof_capture_writer_add_process (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *cmdline)
{
SysprofCaptureProcess *ev;
gsize len;
if (cmdline == NULL)
cmdline = "";
g_assert (self != NULL);
g_assert (cmdline != NULL);
len = sizeof *ev + strlen (cmdline) + 1;
ev = (SysprofCaptureProcess *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_PROCESS);
g_strlcpy (ev->cmdline, cmdline, len - sizeof *ev);
ev->cmdline[len - sizeof *ev - 1] = '\0';
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_PROCESS]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_sample (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 tid,
const SysprofCaptureAddress *addrs,
guint n_addrs)
{
SysprofCaptureSample *ev;
gsize len;
g_assert (self != NULL);
len = sizeof *ev + (n_addrs * sizeof (SysprofCaptureAddress));
ev = (SysprofCaptureSample *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_SAMPLE);
ev->n_addrs = n_addrs;
ev->tid = tid;
memcpy (ev->addrs, addrs, (n_addrs * sizeof (SysprofCaptureAddress)));
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_SAMPLE]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_fork (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 child_pid)
{
SysprofCaptureFork *ev;
gsize len = sizeof *ev;
g_assert (self != NULL);
ev = (SysprofCaptureFork *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_FORK);
ev->child_pid = child_pid;
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_FORK]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_exit (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid)
{
SysprofCaptureExit *ev;
gsize len = sizeof *ev;
g_assert (self != NULL);
ev = (SysprofCaptureExit *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_EXIT);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_EXIT]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_timestamp (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid)
{
SysprofCaptureTimestamp *ev;
gsize len = sizeof *ev;
g_assert (self != NULL);
ev = (SysprofCaptureTimestamp *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_TIMESTAMP);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_TIMESTAMP]++;
return TRUE;
}
static gboolean
sysprof_capture_writer_flush_end_time (SysprofCaptureWriter *self)
{
gint64 end_time = SYSPROF_CAPTURE_CURRENT_TIME;
ssize_t ret;
g_assert (self != NULL);
/* This field is opportunistic, so a failure is okay. */
again:
ret = _sysprof_pwrite (self->fd,
&end_time,
sizeof (end_time),
G_STRUCT_OFFSET (SysprofCaptureFileHeader, end_time));
if (ret < 0 && errno == EAGAIN)
goto again;
return TRUE;
}
gboolean
sysprof_capture_writer_flush (SysprofCaptureWriter *self)
{
g_assert (self != NULL);
return (sysprof_capture_writer_flush_jitmap (self) &&
sysprof_capture_writer_flush_data (self) &&
sysprof_capture_writer_flush_end_time (self));
}
/**
* sysprof_capture_writer_save_as:
* @self: A #SysprofCaptureWriter
* @filename: the file to save the capture as
* @error: a location for a #GError or %NULL.
*
* Saves the captured data as the file @filename.
*
* This is primarily useful if the writer was created with a memory-backed
* file-descriptor such as a memfd or tmpfs file on Linux.
*
* Returns: %TRUE if successful, otherwise %FALSE and @error is set.
*/
gboolean
sysprof_capture_writer_save_as (SysprofCaptureWriter *self,
const gchar *filename,
GError **error)
{
gsize to_write;
off_t in_off;
off_t pos;
int fd = -1;
g_assert (self != NULL);
g_assert (self->fd != -1);
g_assert (filename != NULL);
if (-1 == (fd = open (filename, O_CREAT | O_RDWR, 0640)))
goto handle_errno;
if (!sysprof_capture_writer_flush (self))
goto handle_errno;
if (-1 == (pos = lseek (self->fd, 0L, SEEK_CUR)))
goto handle_errno;
to_write = pos;
in_off = 0;
while (to_write > 0)
{
gssize written;
written = _sysprof_sendfile (fd, self->fd, &in_off, pos);
if (written < 0)
goto handle_errno;
if (written == 0 && errno != EAGAIN)
goto handle_errno;
g_assert (written <= (gssize)to_write);
to_write -= written;
}
close (fd);
return TRUE;
handle_errno:
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
if (fd != -1)
{
close (fd);
g_unlink (filename);
}
return FALSE;
}
/**
* _sysprof_capture_writer_splice_from_fd:
* @self: An #SysprofCaptureWriter
* @fd: the fd to read from.
* @error: A location for a #GError, or %NULL.
*
* This is internal API for SysprofCaptureWriter and SysprofCaptureReader to
* communicate when splicing a reader into a writer.
*
* This should not be used outside of #SysprofCaptureReader or
* #SysprofCaptureWriter.
*
* This will not advance the position of @fd.
*
* Returns: %TRUE if successful; otherwise %FALSE and @error is set.
*/
gboolean
_sysprof_capture_writer_splice_from_fd (SysprofCaptureWriter *self,
int fd,
GError **error)
{
struct stat stbuf;
off_t in_off;
gsize to_write;
g_assert (self != NULL);
g_assert (self->fd != -1);
if (-1 == fstat (fd, &stbuf))
goto handle_errno;
if (stbuf.st_size < 256)
{
g_set_error (error,
G_FILE_ERROR,
G_FILE_ERROR_INVAL,
"Cannot splice, possibly corrupt file.");
return FALSE;
}
in_off = 256;
to_write = stbuf.st_size - in_off;
while (to_write > 0)
{
gssize written;
written = _sysprof_sendfile (self->fd, fd, &in_off, to_write);
if (written < 0)
goto handle_errno;
if (written == 0 && errno != EAGAIN)
goto handle_errno;
g_assert (written <= (gssize)to_write);
to_write -= written;
}
return TRUE;
handle_errno:
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return FALSE;
}
/**
* sysprof_capture_writer_splice:
* @self: An #SysprofCaptureWriter
* @dest: An #SysprofCaptureWriter
* @error: A location for a #GError, or %NULL.
*
* This function will copy the capture @self into the capture @dest. This
* tries to be semi-efficient by using sendfile() to copy the contents between
* the captures. @self and @dest will be flushed before the contents are copied
* into the @dest file-descriptor.
*
* Returns: %TRUE if successful, otherwise %FALSE and and @error is set.
*/
gboolean
sysprof_capture_writer_splice (SysprofCaptureWriter *self,
SysprofCaptureWriter *dest,
GError **error)
{
gboolean ret;
off_t pos;
g_assert (self != NULL);
g_assert (self->fd != -1);
g_assert (dest != NULL);
g_assert (dest->fd != -1);
/* Flush before writing anything to ensure consistency */
if (!sysprof_capture_writer_flush (self) || !sysprof_capture_writer_flush (dest))
goto handle_errno;
/* Track our current position so we can reset */
if ((off_t)-1 == (pos = lseek (self->fd, 0L, SEEK_CUR)))
goto handle_errno;
/* Perform the splice */
ret = _sysprof_capture_writer_splice_from_fd (dest, self->fd, error);
/* Now reset or file-descriptor position (it should be the same */
if (pos != lseek (self->fd, pos, SEEK_SET))
{
ret = FALSE;
goto handle_errno;
}
return ret;
handle_errno:
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return FALSE;
}
/**
* sysprof_capture_writer_create_reader:
* @self: A #SysprofCaptureWriter
* @error: a location for a #GError, or %NULL
*
* Creates a new reader for the writer.
*
* Since readers use positioned reads, this uses the same file-descriptor for
* the #SysprofCaptureReader. Therefore, if you are writing to the capture while
* also consuming from the reader, you could get transient failures unless you
* synchronize the operations.
*
* Returns: (transfer full): A #SysprofCaptureReader.
*/
SysprofCaptureReader *
sysprof_capture_writer_create_reader (SysprofCaptureWriter *self,
GError **error)
{
SysprofCaptureReader *ret;
int copy;
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (self->fd != -1, NULL);
if (!sysprof_capture_writer_flush (self))
{
g_set_error (error,
G_FILE_ERROR,
g_file_error_from_errno (errno),
"%s", g_strerror (errno));
return NULL;
}
/*
* We don't care about the write position, since the reader
* uses positioned reads.
*/
if (-1 == (copy = dup (self->fd)))
return NULL;
if ((ret = sysprof_capture_reader_new_from_fd (copy, error)))
sysprof_capture_reader_set_stat (ret, &self->stat);
return g_steal_pointer (&ret);
}
/**
* sysprof_capture_writer_stat:
* @self: A #SysprofCaptureWriter
* @stat: (out): A location for an #SysprofCaptureStat
*
* This function will fill @stat with statistics generated while capturing
* the profiler session.
*/
void
sysprof_capture_writer_stat (SysprofCaptureWriter *self,
SysprofCaptureStat *stat)
{
g_return_if_fail (self != NULL);
g_return_if_fail (stat != NULL);
*stat = self->stat;
}
gboolean
sysprof_capture_writer_define_counters (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const SysprofCaptureCounter *counters,
guint n_counters)
{
SysprofCaptureCounterDefine *def;
gsize len;
guint i;
g_assert (self != NULL);
g_assert (counters != NULL);
if (n_counters == 0)
return TRUE;
len = sizeof *def + (sizeof *counters * n_counters);
def = (SysprofCaptureCounterDefine *)sysprof_capture_writer_allocate (self, &len);
if (!def)
return FALSE;
sysprof_capture_writer_frame_init (&def->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_CTRDEF);
def->padding1 = 0;
def->padding2 = 0;
def->n_counters = n_counters;
for (i = 0; i < n_counters; i++)
{
if (counters[i].id >= self->next_counter_id)
{
g_warning ("Counter %u has not been registered.", counters[i].id);
continue;
}
def->counters[i] = counters[i];
}
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_CTRDEF]++;
return TRUE;
}
gboolean
sysprof_capture_writer_set_counters (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const guint *counters_ids,
const SysprofCaptureCounterValue *values,
guint n_counters)
{
SysprofCaptureCounterSet *set;
gsize len;
guint n_groups;
guint group;
guint field;
guint i;
g_assert (self != NULL);
g_assert (counters_ids != NULL);
g_assert (values != NULL || !n_counters);
if (n_counters == 0)
return TRUE;
/* Determine how many value groups we need */
n_groups = n_counters / G_N_ELEMENTS (set->values[0].values);
if ((n_groups * G_N_ELEMENTS (set->values[0].values)) != n_counters)
n_groups++;
len = sizeof *set + (n_groups * sizeof (SysprofCaptureCounterValues));
set = (SysprofCaptureCounterSet *)sysprof_capture_writer_allocate (self, &len);
if (!set)
return FALSE;
memset (set, 0, len);
sysprof_capture_writer_frame_init (&set->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_CTRSET);
set->padding1 = 0;
set->padding2 = 0;
set->n_values = n_groups;
for (i = 0, group = 0, field = 0; i < n_counters; i++)
{
set->values[group].ids[field] = counters_ids[i];
set->values[group].values[field] = values[i];
field++;
if (field == G_N_ELEMENTS (set->values[0].values))
{
field = 0;
group++;
}
}
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_CTRSET]++;
return TRUE;
}
/**
* sysprof_capture_writer_request_counter:
*
* This requests a series of counter identifiers for the capture.
*
* The returning number is always greater than zero. The resulting counter
* values are monotonic starting from the resulting value.
*
* For example, if you are returned 5, and requested 3 counters, the counter
* ids you should use are 5, 6, and 7.
*
* Returns: The next series of counter values or zero on failure.
*/
guint
sysprof_capture_writer_request_counter (SysprofCaptureWriter *self,
guint n_counters)
{
gint ret;
g_assert (self != NULL);
if (MAX_COUNTERS - n_counters < self->next_counter_id)
return 0;
ret = self->next_counter_id;
self->next_counter_id += n_counters;
return ret;
}
gboolean
_sysprof_capture_writer_set_time_range (SysprofCaptureWriter *self,
gint64 start_time,
gint64 end_time)
{
ssize_t ret;
g_assert (self != NULL);
do_start:
ret = _sysprof_pwrite (self->fd,
&start_time,
sizeof (start_time),
G_STRUCT_OFFSET (SysprofCaptureFileHeader, time));
if (ret < 0 && errno == EAGAIN)
goto do_start;
do_end:
ret = _sysprof_pwrite (self->fd,
&end_time,
sizeof (end_time),
G_STRUCT_OFFSET (SysprofCaptureFileHeader, end_time));
if (ret < 0 && errno == EAGAIN)
goto do_end;
return TRUE;
}
SysprofCaptureWriter *
sysprof_capture_writer_new_from_env (gsize buffer_size)
{
const gchar *fdstr;
int fd;
if (!(fdstr = g_getenv ("SYSPROF_TRACE_FD")))
return NULL;
/* Make sure the clock is initialized */
sysprof_clock_init ();
if (!(fd = atoi (fdstr)))
return NULL;
/* ignore stdin/stdout/stderr */
if (fd < 2)
return NULL;
return sysprof_capture_writer_new_from_fd (dup (fd), buffer_size);
}
gsize
sysprof_capture_writer_get_buffer_size (SysprofCaptureWriter *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->len;
}
gboolean
sysprof_capture_writer_add_log (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
GLogLevelFlags severity,
const gchar *domain,
const gchar *message)
{
SysprofCaptureLog *ev;
gsize message_len;
gsize len;
g_assert (self != NULL);
if (domain == NULL)
domain = "";
if (message == NULL)
message = "";
message_len = strlen (message) + 1;
len = sizeof *ev + message_len;
ev = (SysprofCaptureLog *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_LOG);
ev->severity = severity & 0xFFFF;
ev->padding1 = 0;
ev->padding2 = 0;
g_strlcpy (ev->domain, domain, sizeof ev->domain);
memcpy (ev->message, message, message_len);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_LOG]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_file (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *path,
gboolean is_last,
const guint8 *data,
gsize data_len)
{
SysprofCaptureFileChunk *ev;
gsize len;
g_assert (self != NULL);
len = sizeof *ev + data_len;
ev = (SysprofCaptureFileChunk *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_FILE_CHUNK);
ev->padding1 = 0;
ev->is_last = !!is_last;
ev->len = data_len;
g_strlcpy (ev->path, path, sizeof ev->path);
memcpy (ev->data, data, data_len);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_FILE_CHUNK]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_file_fd (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *path,
gint fd)
{
guint8 data[(4096*4L) - sizeof(SysprofCaptureFileChunk)];
g_assert (self != NULL);
for (;;)
{
gboolean is_last;
gssize n_read;
again:
n_read = read (fd, data, sizeof data);
if (n_read < 0 && errno == EAGAIN)
goto again;
is_last = n_read == 0;
if (!sysprof_capture_writer_add_file (self, time, cpu, pid, path, is_last, data, n_read))
return FALSE;
if (is_last)
break;
}
return TRUE;
}