/* sp-capture-writer.c * * Copyright (C) 2016 Christian Hergert * * 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 . */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #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; /* counter id sequence */ gint next_counter_id; /* 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) { /* Check for max frame size */ if (len > G_MAXUSHORT) return FALSE; 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 = SP_CAPTURE_CURRENT_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; self->next_counter_id = 1; 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); header->time = SP_CAPTURE_CURRENT_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) { int copy; g_return_val_if_fail (self != NULL, NULL); g_return_val_if_fail (self->fd != -1, NULL); /* * We don't care about the write position, since the reader * uses positioned reads. */ if (-1 == (copy = dup (self->fd))) return NULL; return sp_capture_reader_new_from_fd (copy, 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; } gboolean sp_capture_writer_define_counters (SpCaptureWriter *self, gint64 time, gint cpu, GPid pid, const SpCaptureCounter *counters, guint n_counters) { SpCaptureFrameCounterDefine *def; gsize len; guint i; g_assert (self != NULL); g_assert (counters != NULL); g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0); if (n_counters == 0) return TRUE; len = sizeof *def + (sizeof *counters * n_counters); sp_capture_writer_realign (&len); if (!sp_capture_writer_ensure_space_for (self, len)) return FALSE; g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0); def = (SpCaptureFrameCounterDefine *)&self->buf[self->pos]; def->frame.len = len; def->frame.cpu = cpu; def->frame.pid = pid; def->frame.time = time; def->frame.type = SP_CAPTURE_FRAME_CTRDEF; def->frame.padding = 0; def->padding = 0; def->n_counters = n_counters; for (i = 0; i < n_counters; i++) def->counters[i] = counters[i]; self->pos += def->frame.len; g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0); self->stat.frame_count[SP_CAPTURE_FRAME_CTRDEF]++; return TRUE; } gboolean sp_capture_writer_set_counters (SpCaptureWriter *self, gint64 time, gint cpu, GPid pid, const guint *counters_ids, const SpCaptureCounterValue *values, guint n_counters) { SpCaptureFrameCounterSet *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); g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0); 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 (SpCaptureCounterValues)); sp_capture_writer_realign (&len); if (!sp_capture_writer_ensure_space_for (self, len)) return FALSE; g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0); set = (SpCaptureFrameCounterSet *)&self->buf[self->pos]; set->frame.len = len; set->frame.cpu = cpu; set->frame.pid = pid; set->frame.time = time; set->frame.type = SP_CAPTURE_FRAME_CTRSET; set->frame.padding = 0; set->padding = 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->pos += set->frame.len; g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0); self->stat.frame_count[SP_CAPTURE_FRAME_CTRSET]++; return TRUE; } gint sp_capture_writer_request_counter (SpCaptureWriter *self, guint n_counters) { gint ret; g_assert (self != NULL); ret = self->next_counter_id; self->next_counter_id += n_counters; return ret; }