/* GDK - The GIMP Drawing Kit
 *
 * gdkprofiler.c: A simple profiler
 *
 * Copyright © 2018 Matthias Clasen
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <sys/types.h>
#include <signal.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "gdkversionmacros.h"
#include "gdkprofilerprivate.h"
#include "gdkframeclockprivate.h"

#ifdef HAVE_SYSPROF_CAPTURE

#include <sysprof-capture.h>

static SysprofCaptureWriter *writer = NULL;
static gboolean running = FALSE;

static void
profiler_stop (int s)
{
  if (writer)
    sysprof_capture_writer_unref (writer);
}

void
gdk_profiler_start (int fd)
{
  if (writer)
    return;

  sysprof_clock_init ();

  if (fd == -1)
    {
      gchar *filename;

      filename = g_strdup_printf ("gtk.%d.syscap", getpid ());
      g_print ("Writing profiling data to %s\n", filename);
      writer = sysprof_capture_writer_new (filename, 16*1024);
      g_free (filename);
    }
  else if (fd > 2)
    writer = sysprof_capture_writer_new_from_fd (fd, 16*1024);

  if (writer)
    running = TRUE;

  atexit (G_CALLBACK (profiler_stop));
  signal (SIGTERM, profiler_stop);
}

void
gdk_profiler_stop (void)
{
  running = FALSE;
}

gboolean
gdk_profiler_is_running (void)
{
  return running;
}

void
gdk_profiler_add_mark (gint64      start,
                       guint64     duration,
                       const char *name,
                       const char *message)
{
  if (!running)
    return;

  sysprof_capture_writer_add_mark (writer,
                                   start * 1000L,
                                   -1, getpid (),
                                   duration * 1000L,
                                   "gtk", name, message);
}

static void add_markvf (gint64      start,
                        guint64     duration,
                        const char *name,
                        const char *format,
                        va_list      args) G_GNUC_PRINTF(4, 0);

static void
add_markvf (gint64      start,
            guint64     duration,
            const char *name,
            const char *format,
            va_list      args)
{
  char *message;
  message = g_strdup_vprintf (format, args);
  sysprof_capture_writer_add_mark (writer,
                                   start * 1000L,
                                   -1, getpid (),
                                   duration * 1000L,
                                   "gtk", name, message);
  g_free (message);
}

void
gdk_profiler_add_markf (gint64      start,
                        guint64     duration,
                        const char *name,
                        const char *format,
                        ...)
{
  va_list args;

  if (!running)
    return;

  va_start (args, format);
  add_markvf (start, duration, name, format, args);
  va_end (args);
}

void
gdk_profiler_end_mark (gint64      start,
                       const char *name,
                       const char *message)
{
  if (!running)
    return;

  sysprof_capture_writer_add_mark (writer,
                                   start * 1000L,
                                   -1, getpid (),
                                   (g_get_monotonic_time () - start) * 1000L,
                                   "gtk", name, message);
}

void
gdk_profiler_end_markf (gint64      start,
                        const char *name,
                        const char *format,
                        ...)
{
  va_list args;

  if (!running)
    return;

  va_start (args, format);
  add_markvf (start, g_get_monotonic_time () - start, name, format, args);
  va_end (args);
}


static guint
define_counter (const char *name,
                const char *description,
                int         type)
{
  SysprofCaptureCounter counter;

  if (!writer)
    return 0;

  counter.id = (guint) sysprof_capture_writer_request_counter (writer, 1);
  counter.type = type;
  counter.value.vdbl = 0;
  g_strlcpy (counter.category, "gtk", sizeof counter.category);
  g_strlcpy (counter.name, name, sizeof counter.name);
  g_strlcpy (counter.description, description, sizeof counter.name);

  sysprof_capture_writer_define_counters (writer,
                                          SYSPROF_CAPTURE_CURRENT_TIME,
                                          -1,
                                          getpid (),
                                          &counter,
                                          1);

  return counter.id;
}

guint
gdk_profiler_define_counter (const char *name,
                             const char *description)
{
  return define_counter (name, description, SYSPROF_CAPTURE_COUNTER_DOUBLE);
}

guint
gdk_profiler_define_int_counter (const char *name,
                                 const char *description)
{
  return define_counter (name, description, SYSPROF_CAPTURE_COUNTER_INT64);
}

void
gdk_profiler_set_counter (guint  id,
                          gint64 time,
                          double val)
{
  SysprofCaptureCounterValue value;

  if (!running)
    return;

  value.vdbl = val;
  sysprof_capture_writer_set_counters (writer,
                                       time * 1000L,
                                       -1, getpid (),
                                       &id, &value, 1);
}

void
gdk_profiler_set_int_counter (guint  id,
                              gint64 time,
                              gint64 val)
{
  SysprofCaptureCounterValue value;

  if (!running)
    return;

  value.v64 = val;
  sysprof_capture_writer_set_counters (writer,
                                       time * 1000L,
                                       -1, getpid (),
                                       &id, &value, 1);
}

#else

void
gdk_profiler_start (int fd)
{
}

void
gdk_profiler_stop (void)
{
}

gboolean
gdk_profiler_is_running (void)
{
  return FALSE;
}

void
gdk_profiler_add_mark (gint64      start,
                       guint64     duration,
                       const char *name,
                       const char *message)
{
}

void
gdk_profiler_add_markf (gint64      start,
                        guint64     duration,
                        const char *name,
                        const char *format,
                        ...)
{
}

void
gdk_profiler_end_mark (gint64      start,
                       const char *name,
                       const char *message)
{
}

void
gdk_profiler_end_markf (gint64      start,
                        const char *name,
                        const char *format,
                        ...)
{
}

guint
gdk_profiler_define_counter (const char *name,
                             const char *description)
{
 return 0;
}

void
gdk_profiler_set_counter (guint  id,
                          gint64 time,
                          double value)
{
}

guint
gdk_profiler_define_int_counter (const char *name,
                                 const char *description)
{
  return 0;
}

void
gdk_profiler_set_int_counter (guint  id,
                              gint64 time,
                              gint64 value)
{
}

#endif /* G_OS_WIN32 */