/* sp-capture-writer.c
 *
 * Copyright © 2016 Christian Hergert <chergert@redhat.com>
 *
 * 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 "capture/sp-capture-reader.h"
#include "capture/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 inline void
sp_capture_writer_frame_init (SpCaptureFrame     *frame_,
                              gint                len,
                              gint                cpu,
                              GPid                pid,
                              gint64              time_,
                              SpCaptureFrameType  type)
{
  g_assert (frame_ != NULL);

  frame_->len = len;
  frame_->cpu = cpu;
  frame_->pid = pid;
  frame_->time = time_;
  frame_->type = type;
  frame_->padding = 0;
}

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 inline gpointer
sp_capture_writer_allocate (SpCaptureWriter *self,
                            gsize           *len)
{
  gpointer p;

  g_assert (self != NULL);
  g_assert (len != NULL);
  g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);

  sp_capture_writer_realign (len);

  if (!sp_capture_writer_ensure_space_for (self, *len))
    return NULL;

  p = (gpointer)&self->buf[self->pos];

  self->pos += *len;

  g_assert ((self->pos % SP_CAPTURE_ALIGN) == 0);

  return p;
}

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);

  sp_capture_writer_frame_init (&jitmap.frame,
                                len,
                                -1,
                                getpid (),
                                SP_CAPTURE_CURRENT_TIME,
                                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;
  gsize header_len = sizeof(*header);

  if (buffer_size == 0)
    buffer_size = DEFAULT_BUFFER_SIZE;

  g_assert (fd != -1);
  g_assert (buffer_size % getpagesize() == 0);

  if (ftruncate (fd, 0) != 0)
    return NULL;

  self = g_new0 (SpCaptureWriter, 1);
  self->ref_count = 1;
  self->fd = fd;
  self->buf = (guint8 *)g_malloc0 (buffer_size);
  self->len = buffer_size;
  self->next_counter_id = 1;

  g_get_current_time (&tv);
  nowstr = g_time_val_to_iso8601 (&tv);

  header = sp_capture_writer_allocate (self, &header_len);

  if (header == NULL)
    {
      sp_capture_writer_finalize (self);
      return NULL;
    }

  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;
  header->end_time = 0;
  memset (header->suffix, 0, sizeof header->suffix);

  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 (filename != NULL);

  len = sizeof *ev + strlen (filename) + 1;

  ev = (SpCaptureMap *)sp_capture_writer_allocate (self, &len);
  if (!ev)
    return FALSE;

  sp_capture_writer_frame_init (&ev->frame,
                                len,
                                cpu,
                                pid,
                                time,
                                SP_CAPTURE_FRAME_MAP);
  ev->start = start;
  ev->end = end;
  ev->offset = offset;
  ev->inode = inode;

  g_strlcpy (ev->filename, filename, len - sizeof *ev);
  ev->filename[len - sizeof *ev - 1] = '\0';

  self->stat.frame_count[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 (cmdline != NULL);

  len = sizeof *ev + strlen (cmdline) + 1;

  ev = (SpCaptureProcess *)sp_capture_writer_allocate (self, &len);
  if (!ev)
    return FALSE;

  sp_capture_writer_frame_init (&ev->frame,
                                len,
                                cpu,
                                pid,
                                time,
                                SP_CAPTURE_FRAME_PROCESS);

  g_strlcpy (ev->cmdline, cmdline, len - sizeof *ev);
  ev->cmdline[len - sizeof *ev - 1] = '\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);

  len = sizeof *ev + (n_addrs * sizeof (SpCaptureAddress));

  ev = (SpCaptureSample *)sp_capture_writer_allocate (self, &len);
  if (!ev)
    return FALSE;

  sp_capture_writer_frame_init (&ev->frame,
                                len,
                                cpu,
                                pid,
                                time,
                                SP_CAPTURE_FRAME_SAMPLE);
  ev->n_addrs = n_addrs;

  memcpy (ev->addrs, addrs, (n_addrs * sizeof (SpCaptureAddress)));

  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);

  ev = (SpCaptureFork *)sp_capture_writer_allocate (self, &len);
  if (!ev)
    return FALSE;

  sp_capture_writer_frame_init (&ev->frame,
                                len,
                                cpu,
                                pid,
                                time,
                                SP_CAPTURE_FRAME_FORK);
  ev->child_pid = child_pid;

  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);

  ev = (SpCaptureExit *)sp_capture_writer_allocate (self, &len);
  if (!ev)
    return FALSE;

  sp_capture_writer_frame_init (&ev->frame,
                                len,
                                cpu,
                                pid,
                                time,
                                SP_CAPTURE_FRAME_EXIT);

  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);

  ev = (SpCaptureTimestamp *)sp_capture_writer_allocate (self, &len);
  if (!ev)
    return FALSE;

  sp_capture_writer_frame_init (&ev->frame,
                                len,
                                cpu,
                                pid,
                                time,
                                SP_CAPTURE_FRAME_TIMESTAMP);

    self->stat.frame_count[SP_CAPTURE_FRAME_TIMESTAMP]++;

  return TRUE;
}

static gboolean
sp_capture_writer_flush_end_time (SpCaptureWriter *self)
{
  gint64 end_time = SP_CAPTURE_CURRENT_TIME;
  ssize_t ret;

  g_assert (self != NULL);

  /* This field is opportunistic, so a failure is okay. */

again:
  ret = pwrite (self->fd,
                &end_time,
                sizeof (end_time),
                G_STRUCT_OFFSET (SpCaptureFileHeader, end_time));

  if (ret < 0 && errno == EAGAIN)
    goto again;

  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_flush_end_time (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);

  if (!sp_capture_writer_flush (self))
    {
      g_set_error (error,
                   G_FILE_ERROR,
                   g_file_error_from_errno (errno),
                   "%s", g_strerror (errno));
      return NULL;
    }

  /*
   * We don't care about the write position, since the reader
   * uses positioned reads.
   */
  if (-1 == (copy = dup (self->fd)))
    return NULL;

  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);

  if (n_counters == 0)
    return TRUE;

  len = sizeof *def + (sizeof *counters * n_counters);

  def = (SpCaptureFrameCounterDefine *)sp_capture_writer_allocate (self, &len);
  if (!def)
    return FALSE;

  sp_capture_writer_frame_init (&def->frame,
                                len,
                                cpu,
                                pid,
                                time,
                                SP_CAPTURE_FRAME_CTRDEF);
  def->padding = 0;
  def->n_counters = n_counters;

  for (i = 0; i < n_counters; i++)
    def->counters[i] = counters[i];

  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);

  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));

  set = (SpCaptureFrameCounterSet *)sp_capture_writer_allocate (self, &len);
  if (!set)
    return FALSE;

  sp_capture_writer_frame_init (&set->frame,
                                len,
                                cpu,
                                pid,
                                time,
                                SP_CAPTURE_FRAME_CTRSET);
  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->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;
}
