/* sysprof-capture-writer.c * * Copyright 2016-2019 Christian Hergert * * 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" #include #ifdef __APPLE__ # include #else # include #endif #include #include #include #include #include #include #include #include #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_pid_root (SysprofCaptureWriter *self, int64_t time, int cpu, int32_t pid, const char *path, uint32_t layer) { SysprofCapturePidRoot *ev; size_t strl = strlen (path); size_t len = sizeof *ev + strl + 1; assert (self != NULL); ev = (SysprofCapturePidRoot *)sysprof_capture_writer_allocate (self, &len); if (!ev) return false; sysprof_capture_writer_frame_init (&ev->frame, len, cpu, pid, time, SYSPROF_CAPTURE_FRAME_PID_ROOT); ev->layer = layer; memcpy (ev->path, path, strl); ev->path[strl] = 0; self->stat.frame_count[SYSPROF_CAPTURE_FRAME_PID_ROOT]++; 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; }