Files
sysprof/src/libsysprof-capture/sysprof-capture-writer.c
Philip Withnall 608582d3c4 libsysprof-capture: Drop GLib dependency
None of the code uses it any more. This means that `libsysprof-capture.a`
can now be used within `libglib-2.0.so` for collecting main loop
statistics.

Brought to you by Opeth’s Deliverance on repeat.

Signed-off-by: Philip Withnall <withnall@endlessm.com>

Fixes: #40
2020-07-03 22:00:34 +01:00

1624 lines
44 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
*/
#include "config.h"
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <assert.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.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"
#include "sysprof-macros-internal.h"
#include "sysprof-macros.h"
#define DEFAULT_BUFFER_SIZE (_sysprof_getpagesize() * 64L)
#define INVALID_ADDRESS (SYSPROF_UINT64_CONSTANT(0))
#define MAX_COUNTERS ((1 << 24) - 1)
#define MAX_UNWIND_DEPTH 64
typedef struct
{
/* A pinter into the string buffer */
const char *str;
/* The unique address for the string */
uint64_t 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.
*/
char 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 int 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.
*/
size_t addr_seq;
/* Our position in addr_buf. */
size_t 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.
*/
unsigned int addr_hash_size;
/* Capture file handle */
int fd;
/* Our write buffer for fd */
uint8_t *buf;
size_t pos;
size_t len;
/* counter id sequence */
int next_counter_id;
/* Statistics while recording */
SysprofCaptureStat stat;
};
static inline void
sysprof_capture_writer_frame_init (SysprofCaptureFrame *frame_,
int len,
int cpu,
int32_t pid,
int64_t time_,
SysprofCaptureFrameType type)
{
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);
if (self->fd != -1)
{
close (self->fd);
self->fd = -1;
}
free (self->buf);
free (self);
}
}
SysprofCaptureWriter *
sysprof_capture_writer_ref (SysprofCaptureWriter *self)
{
assert (self != NULL);
assert (self->ref_count > 0);
__atomic_fetch_add (&self->ref_count, 1, __ATOMIC_SEQ_CST);
return self;
}
void
sysprof_capture_writer_unref (SysprofCaptureWriter *self)
{
assert (self != NULL);
assert (self->ref_count > 0);
if (__atomic_fetch_sub (&self->ref_count, 1, __ATOMIC_SEQ_CST) == 1)
sysprof_capture_writer_finalize (self);
}
static bool
sysprof_capture_writer_flush_data (SysprofCaptureWriter *self)
{
const uint8_t *buf;
ssize_t written;
size_t to_write;
assert (self != NULL);
assert (self->pos <= self->len);
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;
assert (written <= (ssize_t)to_write);
buf += written;
to_write -= written;
}
self->pos = 0;
return true;
}
static inline void
sysprof_capture_writer_realign (size_t *pos)
{
*pos = (*pos + SYSPROF_CAPTURE_ALIGN - 1) & ~(SYSPROF_CAPTURE_ALIGN - 1);
}
static inline bool
sysprof_capture_writer_ensure_space_for (SysprofCaptureWriter *self,
size_t len)
{
/* Check for max frame size */
if (len > USHRT_MAX)
return false;
if ((self->len - self->pos) < len)
{
if (!sysprof_capture_writer_flush_data (self))
return false;
}
return true;
}
static inline void *
sysprof_capture_writer_allocate (SysprofCaptureWriter *self,
size_t *len)
{
void *p;
assert (self != NULL);
assert (len != NULL);
assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0);
sysprof_capture_writer_realign (len);
if (!sysprof_capture_writer_ensure_space_for (self, *len))
return NULL;
p = (void *)&self->buf[self->pos];
self->pos += *len;
assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0);
return p;
}
static bool
sysprof_capture_writer_flush_jitmap (SysprofCaptureWriter *self)
{
SysprofCaptureJitmap jitmap;
ssize_t r;
size_t len;
assert (self != NULL);
if (self->addr_hash_size == 0)
return true;
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 || (size_t)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;
}
/* djb hash */
static unsigned int
str_hash (const char *str)
{
const uint8_t *p;
uint32_t h = 5381;
for (p = (const uint8_t *) str; *p != '\0'; p++)
h = (h << 5) + h + *p;
return h;
}
static bool
sysprof_capture_writer_lookup_jitmap (SysprofCaptureWriter *self,
const char *name,
SysprofCaptureAddress *addr)
{
unsigned int hash;
unsigned int i;
assert (self != NULL);
assert (name != NULL);
assert (addr != NULL);
hash = str_hash (name) % SYSPROF_N_ELEMENTS (self->addr_hash);
for (i = hash; i < SYSPROF_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 char *str)
{
SysprofCaptureAddress addr;
char *dst;
size_t len;
unsigned int hash;
unsigned int i;
assert (self != NULL);
assert (str != NULL);
assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0);
len = sizeof addr + strlen (str) + 1;
if ((self->addr_hash_size == SYSPROF_N_ELEMENTS (self->addr_hash)) ||
((sizeof self->addr_buf - self->addr_buf_pos) < len))
{
if (!sysprof_capture_writer_flush_jitmap (self))
return INVALID_ADDRESS;
assert (self->addr_hash_size == 0);
assert (self->addr_buf_pos == 0);
}
assert (self->addr_hash_size < SYSPROF_N_ELEMENTS (self->addr_hash));
assert (len > sizeof addr);
/* Allocate the next unique address */
addr = SYSPROF_CAPTURE_JITMAP_MARK | ++self->addr_seq;
/* Copy the address into the buffer */
dst = (char *)&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;
assert (self->addr_buf_pos <= sizeof self->addr_buf);
/* Now place the address into the hashtable */
hash = str_hash (str) % SYSPROF_N_ELEMENTS (self->addr_hash);
/* Start from the current hash bucket and go forward */
for (i = hash; i < SYSPROF_N_ELEMENTS (self->addr_hash); i++)
{
SysprofCaptureJitmapBucket *bucket = &self->addr_hash[i];
if (SYSPROF_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 (SYSPROF_LIKELY (bucket->str == NULL))
{
bucket->str = dst;
bucket->addr = addr;
self->addr_hash_size++;
return addr;
}
}
sysprof_assert_not_reached ();
return INVALID_ADDRESS;
}
SysprofCaptureWriter *
sysprof_capture_writer_new_from_fd (int fd,
size_t buffer_size)
{
time_t now;
char now_str[sizeof ("2020-06-30T14:34:00Z")];
SysprofCaptureWriter *self;
SysprofCaptureFileHeader *header;
size_t header_len = sizeof(*header);
if (fd < 0)
return NULL;
if (buffer_size == 0)
buffer_size = DEFAULT_BUFFER_SIZE;
assert (fd != -1);
assert (buffer_size % _sysprof_getpagesize() == 0);
/* This is only useful on files, memfd, etc */
if (ftruncate (fd, 0) != 0) { /* Do Nothing */ }
self = sysprof_malloc0 (sizeof (SysprofCaptureWriter));
if (self == NULL)
return NULL;
self->ref_count = 1;
self->fd = fd;
self->buf = (uint8_t *) sysprof_malloc0 (buffer_size);
if (self->buf == NULL)
{
free (self);
return NULL;
}
self->len = buffer_size;
self->next_counter_id = 1;
/* Format the time as ISO 8601, in UTC */
time (&now);
if (strftime (now_str, sizeof (now_str), "%FT%TZ", gmtime (&now)) == 0)
{
free (self->buf);
free (self);
return NULL;
}
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;
#if __BYTE_ORDER == __LITTLE_ENDIAN
header->little_endian = true;
#else
header->little_endian = false;
#endif
header->padding = 0;
_sysprof_strlcpy (header->capture_time, now_str, 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;
}
assert (self->pos == 0);
assert (self->len > 0);
assert (self->len % _sysprof_getpagesize() == 0);
assert (self->buf != NULL);
assert (self->addr_hash_size == 0);
assert (self->fd != -1);
return self;
}
SysprofCaptureWriter *
sysprof_capture_writer_new (const char *filename,
size_t buffer_size)
{
SysprofCaptureWriter *self;
int fd;
assert (filename != NULL);
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;
}
bool
sysprof_capture_writer_add_map (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
uint64_t start,
uint64_t end,
uint64_t offset,
uint64_t inode,
const char *filename)
{
SysprofCaptureMap *ev;
size_t len;
if (filename == NULL)
filename = "";
assert (self != NULL);
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;
_sysprof_strlcpy (ev->filename, filename, len - sizeof *ev);
ev->filename[len - sizeof *ev - 1] = '\0';
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_MAP]++;
return true;
}
bool
sysprof_capture_writer_add_mark (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
uint64_t duration,
const char *group,
const char *name,
const char *message)
{
SysprofCaptureMark *ev;
size_t message_len;
size_t len;
assert (self != NULL);
assert (name != NULL);
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;
_sysprof_strlcpy (ev->group, group, sizeof ev->group);
_sysprof_strlcpy (ev->name, name, sizeof ev->name);
memcpy (ev->message, message, message_len);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_MARK]++;
return true;
}
bool
sysprof_capture_writer_add_metadata (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
const char *id,
const char *metadata,
ssize_t metadata_len)
{
SysprofCaptureMetadata *ev;
size_t len;
assert (self != NULL);
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);
_sysprof_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 char *name)
{
SysprofCaptureAddress addr = INVALID_ADDRESS;
if (name == NULL)
name = "";
assert (self != NULL);
assert (name != NULL);
if (!sysprof_capture_writer_lookup_jitmap (self, name, &addr))
addr = sysprof_capture_writer_insert_jitmap (self, name);
return addr;
}
bool
sysprof_capture_writer_add_process (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
const char *cmdline)
{
SysprofCaptureProcess *ev;
size_t len;
if (cmdline == NULL)
cmdline = "";
assert (self != NULL);
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);
_sysprof_strlcpy (ev->cmdline, cmdline, len - sizeof *ev);
ev->cmdline[len - sizeof *ev - 1] = '\0';
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_PROCESS]++;
return true;
}
bool
sysprof_capture_writer_add_sample (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
int32_t tid,
const SysprofCaptureAddress *addrs,
unsigned int n_addrs)
{
SysprofCaptureSample *ev;
size_t len;
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;
}
bool
sysprof_capture_writer_add_fork (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
int32_t child_pid)
{
SysprofCaptureFork *ev;
size_t len = sizeof *ev;
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;
}
bool
sysprof_capture_writer_add_exit (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid)
{
SysprofCaptureExit *ev;
size_t len = sizeof *ev;
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;
}
bool
sysprof_capture_writer_add_timestamp (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid)
{
SysprofCaptureTimestamp *ev;
size_t len = sizeof *ev;
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 bool
sysprof_capture_writer_flush_end_time (SysprofCaptureWriter *self)
{
int64_t end_time = SYSPROF_CAPTURE_CURRENT_TIME;
ssize_t ret;
assert (self != NULL);
/* This field is opportunistic, so a failure is okay. */
again:
ret = _sysprof_pwrite (self->fd,
&end_time,
sizeof (end_time),
offsetof (SysprofCaptureFileHeader, end_time));
if (ret < 0 && errno == EAGAIN)
goto again;
return true;
}
bool
sysprof_capture_writer_flush (SysprofCaptureWriter *self)
{
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
*
* 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.
*
* `errno` is set on error, to any of the errors returned by `open()`,
* sysprof_capture_writer_flush(), `lseek()` or `sendfile()`.
*
* Returns: %TRUE if successful, otherwise %FALSE and `errno` is set.
*/
bool
sysprof_capture_writer_save_as (SysprofCaptureWriter *self,
const char *filename)
{
size_t to_write;
off_t in_off;
off_t pos;
int fd = -1;
int errsv;
assert (self != NULL);
assert (self->fd != -1);
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)
{
ssize_t written;
written = _sysprof_sendfile (fd, self->fd, &in_off, pos);
if (written < 0)
goto handle_errno;
if (written == 0 && errno != EAGAIN)
goto handle_errno;
assert (written <= (ssize_t)to_write);
to_write -= written;
}
close (fd);
return true;
handle_errno:
errsv = errno;
if (fd != -1)
{
close (fd);
unlink (filename);
}
errno = errsv;
return false;
}
/**
* _sysprof_capture_writer_splice_from_fd:
* @self: An #SysprofCaptureWriter
* @fd: the fd to read from.
*
* 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.
*
* `errno` is set on error, to any of the errors returned by `fstat()` or
* `sendfile()`, or `EBADMSG` if the file is corrupt.
*
* Returns: %TRUE if successful; otherwise %FALSE and `errno` is set.
*/
bool
_sysprof_capture_writer_splice_from_fd (SysprofCaptureWriter *self,
int fd)
{
struct stat stbuf;
off_t in_off;
size_t to_write;
assert (self != NULL);
assert (self->fd != -1);
if (-1 == fstat (fd, &stbuf))
goto handle_errno;
if (stbuf.st_size < 256)
{
errno = EBADMSG;
return false;
}
in_off = 256;
to_write = stbuf.st_size - in_off;
while (to_write > 0)
{
ssize_t 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;
assert (written <= (ssize_t)to_write);
to_write -= written;
}
return true;
handle_errno:
/* errno is propagated */
return false;
}
/**
* sysprof_capture_writer_splice:
* @self: An #SysprofCaptureWriter
* @dest: An #SysprofCaptureWriter
*
* 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.
*
* `errno` is set on error, to any of the errors returned by
* sysprof_capture_writer_flush(), `lseek()` or
* _sysprof_capture_writer_splice_from_fd().
*
* Returns: %TRUE if successful, otherwise %FALSE and and `errno` is set.
*/
bool
sysprof_capture_writer_splice (SysprofCaptureWriter *self,
SysprofCaptureWriter *dest)
{
bool ret;
off_t pos;
int errsv;
assert (self != NULL);
assert (self->fd != -1);
assert (dest != NULL);
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);
errsv = errno;
/* Now reset or file-descriptor position (it should be the same */
if (pos != lseek (self->fd, pos, SEEK_SET))
goto handle_errno;
if (!ret)
errno = errsv;
return ret;
handle_errno:
/* errno is propagated */
return false;
}
/**
* sysprof_capture_writer_create_reader:
* @self: A #SysprofCaptureWriter
*
* 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.
*
* `errno` is set on error, to any of the errors returned by
* sysprof_capture_writer_flush(), `dup()` or
* sysprof_capture_reader_new_from_fd().
*
* Returns: (transfer full): A #SysprofCaptureReader.
*/
SysprofCaptureReader *
sysprof_capture_writer_create_reader (SysprofCaptureWriter *self)
{
SysprofCaptureReader *ret;
int copy;
assert (self != NULL);
assert (self->fd != -1);
if (!sysprof_capture_writer_flush (self))
{
/* errno is propagated */
return NULL;
}
/*
* We don't care about the write position, since the reader
* uses positioned reads.
*/
if (-1 == (copy = dup (self->fd)))
{
/* errno is propagated */
return NULL;
}
if (!(ret = sysprof_capture_reader_new_from_fd (copy)))
{
/* errno is propagated */
return NULL;
}
sysprof_capture_reader_set_stat (ret, &self->stat);
return sysprof_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)
{
assert (self != NULL);
assert (stat != NULL);
*stat = self->stat;
}
bool
sysprof_capture_writer_define_counters (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
const SysprofCaptureCounter *counters,
unsigned int n_counters)
{
SysprofCaptureCounterDefine *def;
size_t len;
unsigned int i;
assert (self != NULL);
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++)
{
/* Has the counter been registered? */
assert (counters[i].id < self->next_counter_id);
def->counters[i] = counters[i];
}
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_CTRDEF]++;
return true;
}
bool
sysprof_capture_writer_set_counters (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
const unsigned int *counters_ids,
const SysprofCaptureCounterValue *values,
unsigned int n_counters)
{
SysprofCaptureCounterSet *set;
size_t len;
unsigned int n_groups;
unsigned int group;
unsigned int field;
unsigned int i;
assert (self != NULL);
assert (counters_ids != NULL || n_counters == 0);
assert (values != NULL || !n_counters);
if (n_counters == 0)
return true;
/* Determine how many value groups we need */
n_groups = n_counters / SYSPROF_N_ELEMENTS (set->values[0].values);
if ((n_groups * SYSPROF_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 == SYSPROF_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.
*/
unsigned int
sysprof_capture_writer_request_counter (SysprofCaptureWriter *self,
unsigned int n_counters)
{
int ret;
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;
}
bool
_sysprof_capture_writer_set_time_range (SysprofCaptureWriter *self,
int64_t start_time,
int64_t end_time)
{
ssize_t ret;
assert (self != NULL);
do_start:
ret = _sysprof_pwrite (self->fd,
&start_time,
sizeof (start_time),
offsetof (SysprofCaptureFileHeader, time));
if (ret < 0 && errno == EAGAIN)
goto do_start;
do_end:
ret = _sysprof_pwrite (self->fd,
&end_time,
sizeof (end_time),
offsetof (SysprofCaptureFileHeader, end_time));
if (ret < 0 && errno == EAGAIN)
goto do_end;
return true;
}
SysprofCaptureWriter *
sysprof_capture_writer_new_from_env (size_t buffer_size)
{
const char *fdstr;
int fd;
if (!(fdstr = 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);
}
size_t
sysprof_capture_writer_get_buffer_size (SysprofCaptureWriter *self)
{
assert (self != NULL);
return self->len;
}
bool
sysprof_capture_writer_add_log (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
int severity,
const char *domain,
const char *message)
{
SysprofCaptureLog *ev;
size_t message_len;
size_t len;
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;
_sysprof_strlcpy (ev->domain, domain, sizeof ev->domain);
memcpy (ev->message, message, message_len);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_LOG]++;
return true;
}
bool
sysprof_capture_writer_add_file (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
const char *path,
bool is_last,
const uint8_t *data,
size_t data_len)
{
SysprofCaptureFileChunk *ev;
size_t len;
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;
_sysprof_strlcpy (ev->path, path, sizeof ev->path);
memcpy (ev->data, data, data_len);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_FILE_CHUNK]++;
return true;
}
bool
sysprof_capture_writer_add_file_fd (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
const char *path,
int fd)
{
uint8_t data[(4096*4L) - sizeof(SysprofCaptureFileChunk)];
assert (self != NULL);
for (;;)
{
bool is_last;
ssize_t 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;
}
bool
sysprof_capture_writer_add_allocation (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
int32_t tid,
SysprofCaptureAddress alloc_addr,
int64_t alloc_size,
SysprofBacktraceFunc backtrace_func,
void *backtrace_data)
{
SysprofCaptureAllocation *ev;
size_t len;
unsigned int n_addrs;
assert (self != NULL);
assert (backtrace_func != NULL);
len = sizeof *ev + (MAX_UNWIND_DEPTH * sizeof (SysprofCaptureAddress));
ev = (SysprofCaptureAllocation *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return false;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_ALLOCATION);
ev->alloc_size = alloc_size;
ev->alloc_addr = alloc_addr;
ev->padding1 = 0;
ev->tid = tid;
ev->n_addrs = 0;
n_addrs = backtrace_func (ev->addrs, MAX_UNWIND_DEPTH, backtrace_data);
if (n_addrs <= MAX_UNWIND_DEPTH)
ev->n_addrs = n_addrs;
if (ev->n_addrs < MAX_UNWIND_DEPTH)
{
size_t diff = (sizeof (SysprofCaptureAddress) * (MAX_UNWIND_DEPTH - ev->n_addrs));
ev->frame.len -= diff;
self->pos -= diff;
}
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_ALLOCATION]++;
return true;
}
bool
sysprof_capture_writer_add_allocation_copy (SysprofCaptureWriter *self,
int64_t time,
int cpu,
int32_t pid,
int32_t tid,
SysprofCaptureAddress alloc_addr,
int64_t alloc_size,
const SysprofCaptureAddress *addrs,
unsigned int n_addrs)
{
SysprofCaptureAllocation *ev;
size_t len;
assert (self != NULL);
if (n_addrs > 0xFFF)
n_addrs = 0xFFF;
len = sizeof *ev + (n_addrs * sizeof (SysprofCaptureAddress));
ev = (SysprofCaptureAllocation *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return false;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_ALLOCATION);
ev->alloc_size = alloc_size;
ev->alloc_addr = alloc_addr;
ev->padding1 = 0;
ev->tid = tid;
ev->n_addrs = n_addrs;
memcpy (ev->addrs, addrs, sizeof (SysprofCaptureAddress) * n_addrs);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_ALLOCATION]++;
return true;
}
bool
_sysprof_capture_writer_add_raw (SysprofCaptureWriter *self,
const SysprofCaptureFrame *fr)
{
void *begin;
size_t len;
assert (self != NULL);
assert ((fr->len & 0x7) == 0);
assert (fr->type < SYSPROF_CAPTURE_FRAME_LAST);
len = fr->len;
if (!(begin = sysprof_capture_writer_allocate (self, &len)))
return false;
assert (fr->len == len);
assert (fr->type < 16);
memcpy (begin, fr, fr->len);
if (fr->type < SYSPROF_N_ELEMENTS (self->stat.frame_count))
self->stat.frame_count[fr->type]++;
return true;
}