mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
This is a major redesign a modernization of Sysprof. The core data structures and design are largely the same, but it has been ported to Gtk3 and has lots of additions that should make your profiling experience smoother. Especially for those that are new to profiling. There are some very simple help docs added, but we really need the experts to come in and write some documentation here.
955 lines
23 KiB
C
955 lines
23 KiB
C
/* sp-capture-writer.c
|
|
*
|
|
* Copyright (C) 2016 Christian Hergert <christian@hergert.me>
|
|
*
|
|
* This file is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This file is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef _GNU_SOURCE
|
|
# define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <glib/gstdio.h>
|
|
#include <string.h>
|
|
#include <sys/sendfile.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "sp-capture-reader.h"
|
|
#include "sp-capture-writer.h"
|
|
|
|
#define DEFAULT_BUFFER_SIZE (getpagesize() * 64L)
|
|
#define INVALID_ADDRESS (G_GUINT64_CONSTANT(0))
|
|
|
|
typedef struct
|
|
{
|
|
/* A pinter into the string buffer */
|
|
const gchar *str;
|
|
|
|
/* The unique address for the string */
|
|
guint64 addr;
|
|
} SpCaptureJitmapBucket;
|
|
|
|
struct _SpCaptureWriter
|
|
{
|
|
/*
|
|
* 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. */
|
|
SpCaptureJitmapBucket 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;
|
|
|
|
/* Statistics while recording */
|
|
SpCaptureStat stat;
|
|
};
|
|
|
|
#ifndef SP_DISABLE_GOBJECT
|
|
G_DEFINE_BOXED_TYPE (SpCaptureWriter, sp_capture_writer,
|
|
sp_capture_writer_ref, sp_capture_writer_unref)
|
|
#endif
|
|
|
|
static void
|
|
sp_capture_writer_finalize (SpCaptureWriter *self)
|
|
{
|
|
if (self != NULL)
|
|
{
|
|
sp_capture_writer_flush (self);
|
|
close (self->fd);
|
|
g_free (self->buf);
|
|
g_free (self);
|
|
}
|
|
}
|
|
|
|
SpCaptureWriter *
|
|
sp_capture_writer_ref (SpCaptureWriter *self)
|
|
{
|
|
g_assert (self != NULL);
|
|
g_assert (self->ref_count > 0);
|
|
|
|
g_atomic_int_inc (&self->ref_count);
|
|
|
|
return self;
|
|
}
|
|
|
|
void
|
|
sp_capture_writer_unref (SpCaptureWriter *self)
|
|
{
|
|
g_assert (self != NULL);
|
|
g_assert (self->ref_count > 0);
|
|
|
|
if (g_atomic_int_dec_and_test (&self->ref_count))
|
|
sp_capture_writer_finalize (self);
|
|
}
|
|
|
|
static gboolean
|
|
sp_capture_writer_flush_data (SpCaptureWriter *self)
|
|
{
|
|
const guint8 *buf;
|
|
gssize written;
|
|
gsize to_write;
|
|
|
|
g_assert (self != NULL);
|
|
g_assert (self->pos <= self->len);
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
buf = self->buf;
|
|
to_write = self->pos;
|
|
|
|
while (to_write > 0)
|
|
{
|
|
written = 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
|
|
sp_capture_writer_realign (gsize *pos)
|
|
{
|
|
*pos = (*pos + SP_CAPTURE_ALIGN - 1) & ~(SP_CAPTURE_ALIGN - 1);
|
|
}
|
|
|
|
static inline gboolean
|
|
sp_capture_writer_ensure_space_for (SpCaptureWriter *self,
|
|
gsize len)
|
|
{
|
|
if ((self->len - self->pos) < len)
|
|
{
|
|
if (!sp_capture_writer_flush_data (self))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
sp_capture_writer_flush_jitmap (SpCaptureWriter *self)
|
|
{
|
|
SpCaptureJitmap 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;
|
|
|
|
sp_capture_writer_realign (&len);
|
|
|
|
jitmap.frame.len = len;
|
|
jitmap.frame.cpu = -1;
|
|
jitmap.frame.pid = getpid ();
|
|
jitmap.frame.time = g_get_monotonic_time ();
|
|
jitmap.frame.type = SP_CAPTURE_FRAME_JITMAP;
|
|
jitmap.n_jitmaps = self->addr_hash_size;
|
|
|
|
if (sizeof jitmap != write (self->fd, &jitmap, sizeof jitmap))
|
|
return FALSE;
|
|
|
|
r = 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[SP_CAPTURE_FRAME_JITMAP]++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
sp_capture_writer_lookup_jitmap (SpCaptureWriter *self,
|
|
const gchar *name,
|
|
SpCaptureAddress *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++)
|
|
{
|
|
SpCaptureJitmapBucket *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++)
|
|
{
|
|
SpCaptureJitmapBucket *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 SpCaptureAddress
|
|
sp_capture_writer_insert_jitmap (SpCaptureWriter *self,
|
|
const gchar *str)
|
|
{
|
|
SpCaptureAddress addr;
|
|
gchar *dst;
|
|
gsize len;
|
|
guint hash;
|
|
guint i;
|
|
|
|
g_assert (self != NULL);
|
|
g_assert (str != NULL);
|
|
g_assert ((self->pos % SP_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 (!sp_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 = SP_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++)
|
|
{
|
|
SpCaptureJitmapBucket *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++)
|
|
{
|
|
SpCaptureJitmapBucket *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;
|
|
}
|
|
|
|
SpCaptureWriter *
|
|
sp_capture_writer_new_from_fd (int fd,
|
|
gsize buffer_size)
|
|
{
|
|
g_autofree gchar *nowstr = NULL;
|
|
SpCaptureWriter *self;
|
|
SpCaptureFileHeader *header;
|
|
GTimeVal tv;
|
|
|
|
if (buffer_size == 0)
|
|
buffer_size = DEFAULT_BUFFER_SIZE;
|
|
|
|
g_assert (fd != -1);
|
|
g_assert (buffer_size % getpagesize() == 0);
|
|
|
|
self = g_new0 (SpCaptureWriter, 1);
|
|
self->ref_count = 1;
|
|
self->fd = fd;
|
|
self->buf = (guint8 *)g_malloc (buffer_size);
|
|
self->len = buffer_size;
|
|
|
|
g_get_current_time (&tv);
|
|
nowstr = g_time_val_to_iso8601 (&tv);
|
|
|
|
header = (SpCaptureFileHeader *)(gpointer)self->buf;
|
|
header->magic = SP_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);
|
|
memset (header->suffix, 0, sizeof header->suffix);
|
|
|
|
self->pos += sizeof *header;
|
|
|
|
if (!sp_capture_writer_flush_data (self))
|
|
{
|
|
sp_capture_writer_finalize (self);
|
|
return NULL;
|
|
}
|
|
|
|
g_assert (self->pos == 0);
|
|
g_assert (self->len > 0);
|
|
g_assert (self->len % getpagesize() == 0);
|
|
g_assert (self->buf != NULL);
|
|
g_assert (self->addr_hash_size == 0);
|
|
g_assert (self->fd != -1);
|
|
|
|
return self;
|
|
}
|
|
|
|
SpCaptureWriter *
|
|
sp_capture_writer_new (const gchar *filename,
|
|
gsize buffer_size)
|
|
{
|
|
SpCaptureWriter *self;
|
|
int fd;
|
|
|
|
g_assert (filename != NULL);
|
|
g_assert (buffer_size % getpagesize() == 0);
|
|
|
|
if ((-1 == (fd = open (filename, O_CREAT | O_RDWR, 0640))) ||
|
|
(-1 == ftruncate (fd, 0L)))
|
|
return NULL;
|
|
|
|
self = sp_capture_writer_new_from_fd (fd, buffer_size);
|
|
|
|
if (self == NULL)
|
|
close (fd);
|
|
|
|
return self;
|
|
}
|
|
|
|
gboolean
|
|
sp_capture_writer_add_map (SpCaptureWriter *self,
|
|
gint64 time,
|
|
gint cpu,
|
|
GPid pid,
|
|
guint64 start,
|
|
guint64 end,
|
|
guint64 offset,
|
|
guint64 inode,
|
|
const gchar *filename)
|
|
{
|
|
SpCaptureMap *ev;
|
|
gsize len;
|
|
|
|
if (filename == NULL)
|
|
filename = "";
|
|
|
|
g_assert (self != NULL);
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
g_assert (filename != NULL);
|
|
|
|
len = sizeof *ev + strlen (filename) + 1;
|
|
|
|
sp_capture_writer_realign (&len);
|
|
|
|
if (!sp_capture_writer_ensure_space_for (self, len))
|
|
return FALSE;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
ev = (SpCaptureMap *)(gpointer)&self->buf[self->pos];
|
|
ev->frame.len = len;
|
|
ev->frame.cpu = cpu;
|
|
ev->frame.pid = pid;
|
|
ev->frame.time = time;
|
|
ev->frame.type = SP_CAPTURE_FRAME_MAP;
|
|
ev->frame.padding = 0;
|
|
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->pos += ev->frame.len;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
self->stat.frame_count[SP_CAPTURE_FRAME_MAP]++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
SpCaptureAddress
|
|
sp_capture_writer_add_jitmap (SpCaptureWriter *self,
|
|
const gchar *name)
|
|
{
|
|
SpCaptureAddress addr = INVALID_ADDRESS;
|
|
|
|
if (name == NULL)
|
|
name = "";
|
|
|
|
g_assert (self != NULL);
|
|
g_assert (name != NULL);
|
|
|
|
if (!sp_capture_writer_lookup_jitmap (self, name, &addr))
|
|
addr = sp_capture_writer_insert_jitmap (self, name);
|
|
|
|
return addr;
|
|
}
|
|
|
|
gboolean
|
|
sp_capture_writer_add_process (SpCaptureWriter *self,
|
|
gint64 time,
|
|
gint cpu,
|
|
GPid pid,
|
|
const gchar *cmdline)
|
|
{
|
|
SpCaptureProcess *ev;
|
|
gsize len;
|
|
|
|
if (cmdline == NULL)
|
|
cmdline = "";
|
|
|
|
g_assert (self != NULL);
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
g_assert (cmdline != NULL);
|
|
|
|
len = sizeof *ev + strlen (cmdline) + 1;
|
|
|
|
sp_capture_writer_realign (&len);
|
|
|
|
if (!sp_capture_writer_ensure_space_for (self, len))
|
|
return FALSE;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
ev = (SpCaptureProcess *)(gpointer)&self->buf[self->pos];
|
|
ev->frame.len = len;
|
|
ev->frame.cpu = cpu;
|
|
ev->frame.pid = pid;
|
|
ev->frame.time = time;
|
|
ev->frame.type = SP_CAPTURE_FRAME_PROCESS;
|
|
ev->frame.padding = 0;
|
|
|
|
g_strlcpy (ev->cmdline, cmdline, len - sizeof *ev);
|
|
ev->cmdline[len - sizeof *ev - 1] = '\0';
|
|
|
|
self->pos += ev->frame.len;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
self->stat.frame_count[SP_CAPTURE_FRAME_PROCESS]++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
sp_capture_writer_add_sample (SpCaptureWriter *self,
|
|
gint64 time,
|
|
gint cpu,
|
|
GPid pid,
|
|
const SpCaptureAddress *addrs,
|
|
guint n_addrs)
|
|
{
|
|
SpCaptureSample *ev;
|
|
gsize len;
|
|
|
|
g_assert (self != NULL);
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
len = sizeof *ev + (n_addrs * sizeof (SpCaptureAddress));
|
|
|
|
sp_capture_writer_realign (&len);
|
|
|
|
if (!sp_capture_writer_ensure_space_for (self, len))
|
|
return FALSE;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
ev = (SpCaptureSample *)(gpointer)&self->buf[self->pos];
|
|
ev->frame.len = len;
|
|
ev->frame.cpu = cpu;
|
|
ev->frame.pid = pid;
|
|
ev->frame.time = time;
|
|
ev->frame.type = SP_CAPTURE_FRAME_SAMPLE;
|
|
ev->frame.padding = 0;
|
|
ev->n_addrs = n_addrs;
|
|
|
|
memcpy (ev->addrs, addrs, (n_addrs * sizeof (SpCaptureAddress)));
|
|
|
|
self->pos += ev->frame.len;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
self->stat.frame_count[SP_CAPTURE_FRAME_SAMPLE]++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
sp_capture_writer_add_fork (SpCaptureWriter *self,
|
|
gint64 time,
|
|
gint cpu,
|
|
GPid pid,
|
|
GPid child_pid)
|
|
{
|
|
SpCaptureFork *ev;
|
|
gsize len = sizeof *ev;
|
|
|
|
g_assert (self != NULL);
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
sp_capture_writer_realign (&len);
|
|
|
|
if (!sp_capture_writer_ensure_space_for (self, len))
|
|
return FALSE;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
ev = (SpCaptureFork *)(gpointer)&self->buf[self->pos];
|
|
ev->frame.len = len;
|
|
ev->frame.cpu = cpu;
|
|
ev->frame.pid = pid;
|
|
ev->frame.time = time;
|
|
ev->frame.type = SP_CAPTURE_FRAME_FORK;
|
|
ev->frame.padding = 0;
|
|
ev->child_pid = child_pid;
|
|
|
|
self->pos += ev->frame.len;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
self->stat.frame_count[SP_CAPTURE_FRAME_FORK]++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
sp_capture_writer_add_exit (SpCaptureWriter *self,
|
|
gint64 time,
|
|
gint cpu,
|
|
GPid pid)
|
|
{
|
|
SpCaptureExit *ev;
|
|
gsize len = sizeof *ev;
|
|
|
|
g_assert (self != NULL);
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
sp_capture_writer_realign (&len);
|
|
|
|
if (!sp_capture_writer_ensure_space_for (self, len))
|
|
return FALSE;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
ev = (SpCaptureExit *)(gpointer)&self->buf[self->pos];
|
|
ev->frame.len = len;
|
|
ev->frame.cpu = cpu;
|
|
ev->frame.pid = pid;
|
|
ev->frame.time = time;
|
|
ev->frame.type = SP_CAPTURE_FRAME_EXIT;
|
|
ev->frame.padding = 0;
|
|
|
|
self->pos += ev->frame.len;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
self->stat.frame_count[SP_CAPTURE_FRAME_EXIT]++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
sp_capture_writer_add_timestamp (SpCaptureWriter *self,
|
|
gint64 time,
|
|
gint cpu,
|
|
GPid pid)
|
|
{
|
|
SpCaptureTimestamp *ev;
|
|
gsize len = sizeof *ev;
|
|
|
|
g_assert (self != NULL);
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
sp_capture_writer_realign (&len);
|
|
|
|
if (!sp_capture_writer_ensure_space_for (self, len))
|
|
return FALSE;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
ev = (SpCaptureTimestamp *)(gpointer)&self->buf[self->pos];
|
|
ev->frame.len = len;
|
|
ev->frame.cpu = cpu;
|
|
ev->frame.pid = pid;
|
|
ev->frame.time = time;
|
|
ev->frame.type = SP_CAPTURE_FRAME_TIMESTAMP;
|
|
ev->frame.padding = 0;
|
|
|
|
self->pos += ev->frame.len;
|
|
|
|
g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);
|
|
|
|
self->stat.frame_count[SP_CAPTURE_FRAME_TIMESTAMP]++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
sp_capture_writer_flush (SpCaptureWriter *self)
|
|
{
|
|
g_assert (self != NULL);
|
|
|
|
return (sp_capture_writer_flush_jitmap (self) &&
|
|
sp_capture_writer_flush_data (self));
|
|
}
|
|
|
|
/**
|
|
* sp_capture_writer_save_as:
|
|
* @self: A #SpCaptureWriter
|
|
* @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
|
|
sp_capture_writer_save_as (SpCaptureWriter *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 (!sp_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 = 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;
|
|
}
|
|
|
|
/**
|
|
* _sp_capture_writer_splice_from_fd:
|
|
* @self: An #SpCaptureWriter
|
|
* @fd: the fd to read from.
|
|
* @error: A location for a #GError, or %NULL.
|
|
*
|
|
* This is internal API for SpCaptureWriter and SpCaptureReader to
|
|
* communicate when splicing a reader into a writer.
|
|
*
|
|
* This should not be used outside of #SpCaptureReader or
|
|
* #SpCaptureWriter.
|
|
*
|
|
* This will not advance the position of @fd.
|
|
*
|
|
* Returns: %TRUE if successful; otherwise %FALSE and @error is set.
|
|
*/
|
|
gboolean
|
|
_sp_capture_writer_splice_from_fd (SpCaptureWriter *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 = 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;
|
|
}
|
|
|
|
/**
|
|
* sp_capture_writer_splice:
|
|
* @self: An #SpCaptureWriter
|
|
* @dest: An #SpCaptureWriter
|
|
* @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
|
|
sp_capture_writer_splice (SpCaptureWriter *self,
|
|
SpCaptureWriter *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 (!sp_capture_writer_flush (self) || !sp_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 = _sp_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;
|
|
}
|
|
|
|
/**
|
|
* sp_capture_writer_create_reader:
|
|
* @self: A #SpCaptureWriter
|
|
* @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 #SpCaptureReader. 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 #SpCaptureReader.
|
|
*/
|
|
SpCaptureReader *
|
|
sp_capture_writer_create_reader (SpCaptureWriter *self,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
g_return_val_if_fail (self->fd != -1, NULL);
|
|
|
|
return sp_capture_reader_new_from_fd (self->fd, error);
|
|
}
|
|
|
|
/**
|
|
* sp_capture_writer_stat:
|
|
* @self: A #SpCaptureWriter
|
|
* @stat: (out): A location for an #SpCaptureStat
|
|
*
|
|
* This function will fill @stat with statistics generated while capturing
|
|
* the profiler session.
|
|
*/
|
|
void
|
|
sp_capture_writer_stat (SpCaptureWriter *self,
|
|
SpCaptureStat *stat)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (stat != NULL);
|
|
|
|
*stat = self->stat;
|
|
}
|