#include "config.h" #include "gskprofilerprivate.h" #define MAX_SAMPLES 32 typedef struct { GQuark id; char *description; gint64 value; gint64 n_samples; gboolean can_reset : 1; } NamedCounter; typedef struct { GQuark id; char *description; gint64 value; gint64 start_time; gint64 min_value; gint64 max_value; gint64 avg_value; gint64 n_samples; gboolean in_flight : 1; gboolean can_reset : 1; gboolean invert : 1; } NamedTimer; typedef struct { GQuark id; gint64 value; } Sample; struct _GskProfiler { GObject parent_instance; GHashTable *counters; GHashTable *timers; Sample timer_samples[MAX_SAMPLES]; guint last_sample; }; G_DEFINE_TYPE (GskProfiler, gsk_profiler, G_TYPE_OBJECT) static void named_counter_free (gpointer data) { NamedCounter *counter = data; if (data == NULL) return; g_free (counter->description); g_slice_free (NamedCounter, counter); } static void named_timer_free (gpointer data) { NamedTimer *timer = data; if (data == NULL) return; g_free (timer->description); g_slice_free (NamedTimer, timer); } static void gsk_profiler_finalize (GObject *gobject) { GskProfiler *self = GSK_PROFILER (gobject); g_clear_pointer (&self->counters, g_hash_table_unref); g_clear_pointer (&self->timers, g_hash_table_unref); G_OBJECT_CLASS (gsk_profiler_parent_class)->finalize (gobject); } static void gsk_profiler_class_init (GskProfilerClass *klass) { G_OBJECT_CLASS (klass)->finalize = gsk_profiler_finalize; } static void gsk_profiler_init (GskProfiler *self) { self->counters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, named_counter_free); self->timers = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, named_timer_free); } GskProfiler * gsk_profiler_new (void) { return g_object_new (GSK_TYPE_PROFILER, NULL); } static NamedCounter * named_counter_new (GQuark id, const char *description, gboolean can_reset) { NamedCounter *res = g_slice_new0 (NamedCounter); res->id = id; res->description = g_strdup (description); res->can_reset = can_reset; return res; } static NamedCounter * gsk_profiler_get_counter (GskProfiler *profiler, GQuark id) { return g_hash_table_lookup (profiler->counters, GINT_TO_POINTER (id)); } GQuark gsk_profiler_add_counter (GskProfiler *profiler, const char *counter_name, const char *description, gboolean can_reset) { NamedCounter *counter; GQuark id; g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); id = g_quark_from_string (counter_name); counter = gsk_profiler_get_counter (profiler, id); if (counter != NULL) { g_critical ("Cannot add a counter '%s' as one already exists.", counter_name); return counter->id; } counter = named_counter_new (id, description, can_reset); g_hash_table_insert (profiler->counters, GINT_TO_POINTER (id), counter); return counter->id; } static NamedTimer * named_timer_new (GQuark id, const char *description, gboolean invert, gboolean can_reset) { NamedTimer *res = g_slice_new0 (NamedTimer); res->id = id; res->description = g_strdup (description); res->invert = invert; res->can_reset = can_reset; return res; } static NamedTimer * gsk_profiler_get_timer (GskProfiler *profiler, GQuark id) { return g_hash_table_lookup (profiler->timers, GINT_TO_POINTER (id)); } GQuark gsk_profiler_add_timer (GskProfiler *profiler, const char *timer_name, const char *description, gboolean invert, gboolean can_reset) { NamedTimer *timer; GQuark id; g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); id = g_quark_from_string (timer_name); timer = gsk_profiler_get_timer (profiler, id); if (timer != NULL) { g_critical ("Cannot add a timer '%s' as one already exists.", timer_name); return timer->id; } timer = named_timer_new (id, description, invert, can_reset); g_hash_table_insert (profiler->timers, GINT_TO_POINTER (id), timer); return timer->id; } void gsk_profiler_counter_inc (GskProfiler *profiler, GQuark counter_id) { gsk_profiler_counter_add (profiler, counter_id, 1); } void gsk_profiler_counter_set (GskProfiler *profiler, GQuark counter_id, gint64 value) { NamedCounter *counter; g_return_if_fail (GSK_IS_PROFILER (profiler)); counter = gsk_profiler_get_counter (profiler, counter_id); if (counter == NULL) { g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?", g_quark_to_string (counter_id), counter_id); return; } counter->value = value; } void gsk_profiler_counter_add (GskProfiler *profiler, GQuark counter_id, gint64 increment) { NamedCounter *counter; g_return_if_fail (GSK_IS_PROFILER (profiler)); counter = gsk_profiler_get_counter (profiler, counter_id); if (counter == NULL) { g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?", g_quark_to_string (counter_id), counter_id); return; } counter->value += increment; } void gsk_profiler_timer_begin (GskProfiler *profiler, GQuark timer_id) { NamedTimer *timer; g_return_if_fail (GSK_IS_PROFILER (profiler)); timer = gsk_profiler_get_timer (profiler, timer_id); if (timer == NULL) return; if (timer->in_flight) return; timer->in_flight = TRUE; timer->start_time = g_get_monotonic_time () * 1000; } gint64 gsk_profiler_timer_end (GskProfiler *profiler, GQuark timer_id) { NamedTimer *timer; gint64 diff; g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); timer = gsk_profiler_get_timer (profiler, timer_id); if (timer == NULL) { g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?", g_quark_to_string (timer_id), timer_id); return 0; } if (!timer->in_flight) { g_critical ("Timer '%s' (id:%d) is not running; did you forget to call gsk_profiler_timer_begin()?", g_quark_to_string (timer->id), timer->id); return 0; } diff = (g_get_monotonic_time () * 1000) - timer->start_time; timer->in_flight = FALSE; timer->value += diff; return diff; } void gsk_profiler_timer_set (GskProfiler *profiler, GQuark timer_id, gint64 value) { NamedTimer *timer; g_return_if_fail (GSK_IS_PROFILER (profiler)); timer = gsk_profiler_get_timer (profiler, timer_id); if (timer == NULL) { g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?", g_quark_to_string (timer_id), timer_id); return; } if (timer->in_flight) { g_critical ("Timer '%s' (id:%d) is running; are you sure you don't want to call " "gsk_profiler_timer_end() instead of gsk_profiler_timer_set()?", g_quark_to_string (timer_id), timer_id); } timer->value = value; } gint64 gsk_profiler_counter_get (GskProfiler *profiler, GQuark counter_id) { NamedCounter *counter; g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); counter = gsk_profiler_get_counter (profiler, counter_id); if (counter == NULL) { g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?", g_quark_to_string (counter_id), counter_id); return 0; } return counter->value; } gint64 gsk_profiler_timer_get (GskProfiler *profiler, GQuark timer_id) { NamedTimer *timer; g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); timer = gsk_profiler_get_timer (profiler, timer_id); if (timer == NULL) { g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?", g_quark_to_string (timer_id), timer_id); return 0; } if (timer->invert) return (gint64) (1000000000.0 / (double) timer->value); return timer->value; } void gsk_profiler_reset (GskProfiler *profiler) { GHashTableIter iter; gpointer value_p = NULL; g_return_if_fail (GSK_IS_PROFILER (profiler)); g_hash_table_iter_init (&iter, profiler->counters); while (g_hash_table_iter_next (&iter, NULL, &value_p)) { NamedCounter *counter = value_p; if (counter->can_reset) counter->value = 0; } g_hash_table_iter_init (&iter, profiler->timers); while (g_hash_table_iter_next (&iter, NULL, &value_p)) { NamedTimer *timer = value_p; if (timer->can_reset) { timer->value = 0; timer->min_value = 0; timer->max_value = 0; timer->avg_value = 0; timer->n_samples = 0; } } profiler->last_sample = 0; } void gsk_profiler_push_samples (GskProfiler *profiler) { GHashTableIter iter; gpointer value_p = NULL; guint last_sample; g_return_if_fail (GSK_IS_PROFILER (profiler)); g_hash_table_iter_init (&iter, profiler->timers); while (g_hash_table_iter_next (&iter, NULL, &value_p)) { NamedTimer *timer = value_p; Sample *s; last_sample = profiler->last_sample; profiler->last_sample += 1; if (profiler->last_sample == MAX_SAMPLES) profiler->last_sample = 0; s = &(profiler->timer_samples[last_sample]); s->id = timer->id; if (timer->invert) s->value = (gint64) (1000000000.0 / (double) timer->value); else s->value = timer->value; } } void gsk_profiler_append_counters (GskProfiler *profiler, GString *buffer) { GHashTableIter iter; gpointer value_p = NULL; g_return_if_fail (GSK_IS_PROFILER (profiler)); g_return_if_fail (buffer != NULL); g_hash_table_iter_init (&iter, profiler->counters); while (g_hash_table_iter_next (&iter, NULL, &value_p)) { NamedCounter *counter = value_p; g_string_append_printf (buffer, "%s: %" G_GINT64_FORMAT "\n", counter->description, counter->value); } } void gsk_profiler_append_timers (GskProfiler *profiler, GString *buffer) { GHashTableIter iter; gpointer value_p = NULL; int i; g_return_if_fail (GSK_IS_PROFILER (profiler)); g_return_if_fail (buffer != NULL); g_hash_table_iter_init (&iter, profiler->timers); while (g_hash_table_iter_next (&iter, NULL, &value_p)) { NamedTimer *timer = value_p; timer->min_value = G_MAXINT64; timer->max_value = G_MININT64; timer->avg_value = 0; timer->n_samples = 0; } for (i = 0; i < profiler->last_sample; i++) { Sample *s = &(profiler->timer_samples[i]); NamedTimer *timer; if (s->id == 0) continue; timer = gsk_profiler_get_timer (profiler, s->id); timer->min_value = MIN (timer->min_value, s->value); timer->max_value = MAX (timer->max_value, s->value); timer->avg_value += s->value; timer->n_samples += 1; } g_hash_table_iter_init (&iter, profiler->timers); while (g_hash_table_iter_next (&iter, NULL, &value_p)) { NamedTimer *timer = value_p; const char *unit = timer->invert ? "" : "usec"; double scale = timer->invert ? 1.0 : 1000.0; g_string_append_printf (buffer, "%s (%s): %.2f", timer->description, unit, (double) timer->value / scale); if (timer->n_samples > 1) { timer->avg_value = timer->avg_value / timer->n_samples; g_string_append_printf (buffer, " Min: %.2f Avg: %.2f Max: %.2f (%" G_GINT64_FORMAT " samples)", (double) timer->min_value / scale, (double) timer->avg_value / scale, (double) timer->max_value / scale, timer->n_samples); } g_string_append (buffer, "\n"); } }