/* sysprof-normalized-series.c * * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 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 . * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "config.h" #include "eggbitset.h" #include "sysprof-axis-private.h" #include "sysprof-normalized-series.h" #include "sysprof-normalized-series-item.h" #include "sysprof-series-private.h" #define SYSPROF_NORMALIZED_SERIES_STEP_TIME_USEC (1000) /* 1 msec */ struct _SysprofNormalizedSeries { SysprofSeries parent_instance; SysprofSeries *series; SysprofAxis *axis; GtkExpression *expression; GArray *values; EggBitset *missing; gulong range_changed_handler; guint update_source; guint disposed : 1; guint inverted : 1; }; struct _SysprofNormalizedSeriesClass { SysprofSeriesClass parent_class; }; enum { PROP_0, PROP_AXIS, PROP_EXPRESSION, PROP_INVERTED, PROP_SERIES, N_PROPS }; G_DEFINE_FINAL_TYPE (SysprofNormalizedSeries, sysprof_normalized_series, SYSPROF_TYPE_SERIES) static GParamSpec *properties [N_PROPS]; static gboolean sysprof_normalized_series_update_missing (gpointer user_data) { SysprofNormalizedSeries *self = user_data; g_autoptr(GtkExpression) expression = NULL; g_autoptr(GListModel) model = NULL; g_autoptr(EggBitset) bitset = NULL; EggBitsetIter iter; guint position; g_assert (SYSPROF_IS_NORMALIZED_SERIES (self)); if (self->missing == NULL || self->disposed) { self->update_source = 0; return G_SOURCE_REMOVE; } bitset = egg_bitset_ref (self->missing); model = g_object_ref (sysprof_series_get_model (self->series)); expression = gtk_expression_ref (self->expression); if (egg_bitset_iter_init_first (&iter, bitset, &position)) { gint64 deadline = g_get_monotonic_time () + SYSPROF_NORMALIZED_SERIES_STEP_TIME_USEC; guint first = position; for (;;) { g_autoptr(GObject) item = g_list_model_get_item (model, position); g_auto(GValue) value = G_VALUE_INIT; guint next = GTK_INVALID_LIST_POSITION; float *fval = &g_array_index (self->values, float, position); gtk_expression_evaluate (expression, item, &value); g_assert (self->values->len > position); if (!self->inverted) *fval = _sysprof_axis_normalize (self->axis, &value); else *fval = 1. - _sysprof_axis_normalize (self->axis, &value); egg_bitset_remove (bitset, position); if (self->disposed) break; if (!egg_bitset_iter_init_first (&iter, bitset, &next) || next != position + 1) { g_list_model_items_changed (G_LIST_MODEL (self), first, position-first+1, position-first+1); first = next; } if (next == GTK_INVALID_LIST_POSITION || g_get_monotonic_time () >= deadline) break; position = next; } } if (egg_bitset_is_empty (bitset)) { self->update_source = 0; return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } static void sysprof_normalized_series_maybe_update (SysprofNormalizedSeries *self) { GSource *source; g_assert (SYSPROF_IS_NORMALIZED_SERIES (self)); if (self->update_source || self->disposed) return; if (!self->missing || egg_bitset_is_empty (self->missing)) return; source = g_idle_source_new (); g_source_set_callback (source, sysprof_normalized_series_update_missing, self, NULL); g_source_set_static_name (source, "[SysprofNormalizedSeries]"); g_source_set_priority (source, G_PRIORITY_LOW); self->update_source = g_source_attach (source, NULL); g_source_unref (source); } static void sysprof_normalized_series_items_changed (SysprofSeries *series, GListModel *model, guint position, guint removed, guint added) { SysprofNormalizedSeries *self = (SysprofNormalizedSeries *)series; g_assert (SYSPROF_IS_NORMALIZED_SERIES (self)); g_assert (G_IS_LIST_MODEL (model)); egg_bitset_splice (self->missing, position, removed, added); egg_bitset_add_range (self->missing, position, added); if (removed > 0) g_array_remove_range (self->values, position, removed); if (added > 0) { if (position == self->values->len) { g_array_set_size (self->values, self->values->len + added); } else { static const float empty[32] = {0}; const float *vals = empty; float *alloc = NULL; if (added > G_N_ELEMENTS (empty)) vals = alloc = g_new0 (float, added); g_array_insert_vals (self->values, position, vals, added); g_free (alloc); } } SYSPROF_SERIES_CLASS (sysprof_normalized_series_parent_class)->items_changed (series, model, position, removed, added); sysprof_normalized_series_maybe_update (self); } static void sysprof_normalized_series_invalidate (SysprofNormalizedSeries *self) { guint n_items; g_assert (SYSPROF_IS_NORMALIZED_SERIES (self)); n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); if (n_items > 0) { egg_bitset_remove_all (self->missing); egg_bitset_add_range (self->missing, 0, n_items); g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items); } } static gpointer sysprof_normalized_series_get_series_item (SysprofSeries *series, guint position, gpointer item) { SysprofNormalizedSeries *self = SYSPROF_NORMALIZED_SERIES (series); SysprofNormalizedSeriesItem *ret; ret = g_object_new (SYSPROF_TYPE_NORMALIZED_SERIES_ITEM, "item", item, "value", g_array_index (self->values, float, position), NULL); g_object_unref (item); return ret; } static void sysprof_normalized_series_dispose (GObject *object) { SysprofNormalizedSeries *self = (SysprofNormalizedSeries *)object; self->disposed = TRUE; g_clear_handle_id (&self->update_source, g_source_remove); g_clear_signal_handler (&self->range_changed_handler, self->axis); g_clear_object (&self->axis); g_clear_object (&self->series); g_clear_pointer (&self->expression, gtk_expression_unref); G_OBJECT_CLASS (sysprof_normalized_series_parent_class)->dispose (object); } static void sysprof_normalized_series_finalize (GObject *object) { SysprofNormalizedSeries *self = (SysprofNormalizedSeries *)object; g_clear_pointer (&self->missing, egg_bitset_unref); g_clear_pointer (&self->values, g_array_unref); G_OBJECT_CLASS (sysprof_normalized_series_parent_class)->finalize (object); } static void sysprof_normalized_series_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SysprofNormalizedSeries *self = SYSPROF_NORMALIZED_SERIES (object); switch (prop_id) { case PROP_AXIS: g_value_set_object (value, sysprof_normalized_series_get_axis (self)); break; case PROP_EXPRESSION: gtk_value_set_expression (value, sysprof_normalized_series_get_expression (self)); break; case PROP_SERIES: g_value_set_object (value, sysprof_normalized_series_get_series (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void sysprof_normalized_series_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SysprofNormalizedSeries *self = SYSPROF_NORMALIZED_SERIES (object); switch (prop_id) { case PROP_AXIS: sysprof_normalized_series_set_axis (self, g_value_get_object (value)); break; case PROP_EXPRESSION: sysprof_normalized_series_set_expression (self, gtk_value_get_expression (value)); break; case PROP_SERIES: sysprof_normalized_series_set_series (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void sysprof_normalized_series_class_init (SysprofNormalizedSeriesClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); SysprofSeriesClass *series_class = SYSPROF_SERIES_CLASS (klass); object_class->dispose = sysprof_normalized_series_dispose; object_class->finalize = sysprof_normalized_series_finalize; object_class->get_property = sysprof_normalized_series_get_property; object_class->set_property = sysprof_normalized_series_set_property; series_class->series_item_type = SYSPROF_TYPE_NORMALIZED_SERIES_ITEM; series_class->get_series_item = sysprof_normalized_series_get_series_item; series_class->items_changed = sysprof_normalized_series_items_changed; properties [PROP_AXIS] = g_param_spec_object ("axis", NULL, NULL, SYSPROF_TYPE_AXIS, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); properties [PROP_EXPRESSION] = gtk_param_spec_expression ("expression", NULL, NULL, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); properties [PROP_INVERTED] = g_param_spec_boolean ("inverted", NULL, NULL, FALSE, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); properties [PROP_SERIES] = g_param_spec_object ("series", NULL, NULL, SYSPROF_TYPE_SERIES, (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (object_class, N_PROPS, properties); } static void sysprof_normalized_series_init (SysprofNormalizedSeries *self) { self->values = g_array_new (FALSE, TRUE, sizeof (float)); self->missing = egg_bitset_new_empty (); } /** * sysprof_normalized_series_new: * @series: (transfer-full) (nullable): a #SysprofSeries * @axis: (transfer-full) (nullable): a #SysprofAxis * @expression: (transfer-full) (nullable): a #GtkExpression * * Creates a new series that will normalize values from @series * by extracting values using @expression and determining where * they fall between the axis's range. * * Returns: (transfer full) (type SysprofNormalizedSeries): a #SysprofNormalizedSeries */ SysprofSeries * sysprof_normalized_series_new (SysprofSeries *series, SysprofAxis *axis, GtkExpression *expression) { SysprofNormalizedSeries *normalized; g_return_val_if_fail (!series || SYSPROF_IS_SERIES (series), NULL); g_return_val_if_fail (!axis || SYSPROF_IS_AXIS (axis), NULL); g_return_val_if_fail (!expression || GTK_IS_EXPRESSION (expression), NULL); normalized = g_object_new (SYSPROF_TYPE_NORMALIZED_SERIES, "axis", axis, "series", series, NULL); g_clear_object (&series); g_clear_object (&axis); g_clear_pointer (&expression, gtk_expression_unref); return SYSPROF_SERIES (normalized); } float sysprof_normalized_series_get_value_at (SysprofNormalizedSeries *self, guint position) { g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), .0f); if (egg_bitset_contains (self->missing, position)) return .0; if (position >= self->values->len) return .0; return g_array_index (self->values, float, position); } /** * sysprof_normalized_series_get_axis: * @self: a #SysprofNormalizedSeries * * Gets the axis used to normalize values. * * Returns: (transfer none) (nullable): a #SysprofAxis or %NULL */ SysprofAxis * sysprof_normalized_series_get_axis (SysprofNormalizedSeries *self) { g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), NULL); return self->axis; } void sysprof_normalized_series_set_axis (SysprofNormalizedSeries *self, SysprofAxis *axis) { g_return_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self)); g_return_if_fail (!axis || SYSPROF_IS_AXIS (axis)); if (g_set_object (&self->axis, axis)) { g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_AXIS]); sysprof_normalized_series_invalidate (self); } } /** * sysprof_normalized_series_get_expression: * @self: a #SysprofNormalizedSeries * * Gets the expression to used to extract values from items within * #SysprofNormalizedSeries:series. * * Returns: (transfer none) (nullable): a #GtkExpression or %NULL */ GtkExpression * sysprof_normalized_series_get_expression (SysprofNormalizedSeries *self) { g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), NULL); return self->expression; } void sysprof_normalized_series_set_expression (SysprofNormalizedSeries *self, GtkExpression *expression) { g_return_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self)); g_return_if_fail (!expression || GTK_IS_EXPRESSION (expression)); if (expression == self->expression) return; if (expression) gtk_expression_ref (expression); g_clear_pointer (&self->expression, gtk_expression_unref); self->expression = expression; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]); } /** * sysprof_normalized_series_get_series: * @self: a #SysprofNormalizedSeries * * Gets the series containing values to normalize. * * Returns: (transfer none) (nullable): a #SysprofSeries or %NULL */ SysprofSeries * sysprof_normalized_series_get_series (SysprofNormalizedSeries *self) { g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), NULL); return self->series; } void sysprof_normalized_series_set_series (SysprofNormalizedSeries *self, SysprofSeries *series) { g_return_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self)); g_return_if_fail (!series || SYSPROF_IS_SERIES (series)); if (g_set_object (&self->series, series)) { g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SERIES]); sysprof_series_set_model (SYSPROF_SERIES (self), G_LIST_MODEL (series)); } } const float * sysprof_normalized_series_get_values (SysprofNormalizedSeries *self, guint *n_values) { g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), NULL); if (self->values == NULL || self->values->len == 0) return NULL; *n_values = self->values->len; return &g_array_index (self->values, float, 0); } void sysprof_normalized_series_set_inverted (SysprofNormalizedSeries *self, gboolean inverted) { g_return_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self)); inverted = !!inverted; if (inverted != self->inverted) { self->inverted = inverted; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INVERTED]); sysprof_normalized_series_invalidate (self); } }