From 36477bb2877238f252d5cf8a56a38063201bc39e Mon Sep 17 00:00:00 2001 From: Ell Date: Sun, 2 Sep 2018 01:46:27 -0400 Subject: [PATCH] app, icons, menus: add performance-log recording to the dashboard Add an option to record a performance log through the dashboard. The log contains a series of samples of the dashboard variables, as well as the full program backtrace, when available. As such, it essentially acts as a built-in profiler, which allows us to correlate program execution with the information available through the dashboard. It is meant to be used for creating logs to accompany perofrmance-related bug reports, as well as for profiling GIMP during development. The sample frequency defaults to 10 samples per second, but can be overridden using the GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY environment variable. Backtraces are included by default when available, but can be suppressed using the GIMP_PERFORMANCE_LOG_NO_BACKTRACE environment variable. Logs are created through the new "record" button at the bottom of the dashboard dialog. When pressed, a file dialog is opened to select the log file, and, once confirmed, data is being recorded to the selected file. Recording is stopped by pressing the "record" button again (we use a highlight to indicate that recording is active.) While recording, the "reset" button is replaced with an "add marker" button, which can be used to add event markers to the log. These can be used to mark events of interest, such as "started painting" and "stopped painting", which then appear in the log as part of the sample stream. Markers are numbered sequentually, and the number of the next (to-be-added) marker appears on the button. Shift- clicking the button adds an empty (description-less) marker, which is only identified by its number; this can be used when markers need to be added quickly. The log is an XML file, containing some extra information (such as the output of "$ gimp -v", and symbol information) in addition to the samples. The data in the file is delta-encoded to reduce the file size, meaning that samples (as well as some other elements) only specify the changes since the previous sample. This adds a necessary decoding step before data can be processed; the next commit adds a tool that does that. There are currently no tools to actually analyze the data -- that's still TBD -- but at least we can start gathering it. --- app/actions/dashboard-actions.c | 28 + app/actions/dashboard-commands.c | 200 ++++ app/actions/dashboard-commands.h | 7 + app/widgets/gimpdashboard.c | 1272 ++++++++++++++++++++-- app/widgets/gimpdashboard.h | 37 +- app/widgets/gimphelp-ids.h | 3 + icons/Color/16/gimp-marker.png | Bin 0 -> 489 bytes icons/Color/16/media-record.png | Bin 0 -> 717 bytes icons/Color/color-scalable.svg | 199 +++- icons/Color/icon-list.mk | 4 + icons/Color/scalable/gimp-marker.svg | 172 +++ icons/Color/scalable/media-record.svg | 125 +++ icons/Symbolic/16/gimp-marker.png | Bin 0 -> 379 bytes icons/Symbolic/16/media-record.png | Bin 0 -> 496 bytes icons/Symbolic/scalable/gimp-marker.svg | 153 +++ icons/Symbolic/scalable/media-record.svg | 93 ++ icons/Symbolic/symbolic-scalable.svg | 170 ++- libgimpwidgets/gimpicons.h | 2 + menus/dashboard-menu.xml | 5 + 19 files changed, 2276 insertions(+), 194 deletions(-) create mode 100644 icons/Color/16/gimp-marker.png create mode 100644 icons/Color/16/media-record.png create mode 100644 icons/Color/scalable/gimp-marker.svg create mode 100644 icons/Color/scalable/media-record.svg create mode 100644 icons/Symbolic/16/gimp-marker.png create mode 100644 icons/Symbolic/16/media-record.png create mode 100644 icons/Symbolic/scalable/gimp-marker.svg create mode 100644 icons/Symbolic/scalable/media-record.svg diff --git a/app/actions/dashboard-actions.c b/app/actions/dashboard-actions.c index 3c359b5d82..b8eb5a7d6a 100644 --- a/app/actions/dashboard-actions.c +++ b/app/actions/dashboard-actions.c @@ -47,6 +47,24 @@ static const GimpActionEntry dashboard_actions[] = { "dashboard-history-duration", NULL, NC_("dashboard-action", "_History Duration") }, + { "dashboard-log-record", GIMP_ICON_RECORD, + NC_("dashboard-action", "_Start/Stop Recording..."), NULL, + NC_("dashboard-action", "Start/stop recording performance log"), + G_CALLBACK (dashboard_log_record_cmd_callback), + GIMP_HELP_DASHBOARD_LOG_RECORD }, + { "dashboard-log-add-marker", GIMP_ICON_MARKER, + NC_("dashboard-action", "_Add Marker..."), NULL, + NC_("dashboard-action", "Add an event marker " + "to the performance log"), + G_CALLBACK (dashboard_log_add_marker_cmd_callback), + GIMP_HELP_DASHBOARD_LOG_ADD_MARKER }, + { "dashboard-log-add-empty-marker", GIMP_ICON_MARKER, + NC_("dashboard-action", "Add _Empty Marker"), NULL, + NC_("dashboard-action", "Add an empty event marker " + "to the performance log"), + G_CALLBACK (dashboard_log_add_empty_marker_cmd_callback), + GIMP_HELP_DASHBOARD_LOG_ADD_EMPTY_MARKER }, + { "dashboard-reset", GIMP_ICON_RESET, NC_("dashboard-action", "_Reset"), NULL, NC_("dashboard-action", "Reset cumulative data"), @@ -153,7 +171,12 @@ dashboard_actions_update (GimpActionGroup *group, gpointer data) { GimpDashboard *dashboard = GIMP_DASHBOARD (data); + gboolean recording; + recording = gimp_dashboard_log_is_recording (dashboard); + +#define SET_SENSITIVE(action,condition) \ + gimp_action_group_set_action_sensitive (group, action, (condition) != 0) #define SET_ACTIVE(action,condition) \ gimp_action_group_set_action_active (group, action, (condition) != 0) @@ -195,8 +218,13 @@ dashboard_actions_update (GimpActionGroup *group, break; } + SET_SENSITIVE ("dashboard-log-add-marker", recording); + SET_SENSITIVE ("dashboard-log-add-empty-marker", recording); + SET_SENSITIVE ("dashboard-reset", !recording); + SET_ACTIVE ("dashboard-low-swap-space-warning", gimp_dashboard_get_low_swap_space_warning (dashboard)); +#undef SET_SENSITIVE #undef SET_ACTIVE } diff --git a/app/actions/dashboard-commands.c b/app/actions/dashboard-commands.c index 10ecbe6e64..c628e9858e 100644 --- a/app/actions/dashboard-commands.c +++ b/app/actions/dashboard-commands.c @@ -24,14 +24,30 @@ #include "actions-types.h" +#include "core/gimp.h" + #include "widgets/gimpdashboard.h" #include "widgets/gimphelp-ids.h" +#include "widgets/gimpuimanager.h" + +#include "dialogs/dialogs.h" #include "dashboard-commands.h" #include "gimp-intl.h" +/* local function prototypes */ + +static void dashboard_log_record_response (GtkWidget *dialog, + int response_id, + GimpDashboard *dashboard); + +static void dashboard_log_add_marker_response (GtkWidget *dialog, + const gchar *description, + GimpDashboard *dashboard); + + /* public functions */ @@ -61,6 +77,148 @@ dashboard_history_duration_cmd_callback (GtkAction *action, gimp_dashboard_set_history_duration (dashboard, history_duration); } +void +dashboard_log_record_cmd_callback (GtkAction *action, + gpointer data) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (data); + + if (! gimp_dashboard_log_is_recording (dashboard)) + { + GtkWidget *dialog; + + #define LOG_RECORD_KEY "gimp-dashboard-log-record-dialog" + + dialog = dialogs_get_dialog (G_OBJECT (dashboard), LOG_RECORD_KEY); + + if (! dialog) + { + GtkFileFilter *filter; + GFile *folder; + + dialog = gtk_file_chooser_dialog_new ( + "Record Performance Log", NULL, GTK_FILE_CHOOSER_ACTION_SAVE, + + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Record"), GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + GTK_RESPONSE_OK); + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_window_set_screen ( + GTK_WINDOW (dialog), + gtk_widget_get_screen (GTK_WIDGET (dashboard))); + gtk_window_set_role (GTK_WINDOW (dialog), + "gimp-dashboard-log-record"); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + + gtk_file_chooser_set_do_overwrite_confirmation ( + GTK_FILE_CHOOSER (dialog), TRUE); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("All Files")); + gtk_file_filter_add_pattern (filter, "*"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Log Files (*.log)")); + gtk_file_filter_add_pattern (filter, "*.log"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter); + + folder = g_object_get_data (G_OBJECT (dashboard), + "gimp-dashboard-log-record-folder"); + + if (folder) + { + gtk_file_chooser_set_current_folder_file ( + GTK_FILE_CHOOSER (dialog), folder, NULL); + } + + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), + "gimp-performance.log"); + + g_signal_connect (dialog, "response", + G_CALLBACK (dashboard_log_record_response), + dashboard); + g_signal_connect (dialog, "delete-event", + G_CALLBACK (gtk_true), + NULL); + + gimp_help_connect (dialog, gimp_standard_help_func, + GIMP_HELP_DASHBOARD_LOG_RECORD, NULL); + + dialogs_attach_dialog (G_OBJECT (dashboard), LOG_RECORD_KEY, dialog); + + g_signal_connect_object (dashboard, "destroy", + G_CALLBACK (gtk_widget_destroy), + dialog, + G_CONNECT_SWAPPED); + + #undef LOG_RECORD_KEY + } + + gtk_window_present (GTK_WINDOW (dialog)); + } + else + { + GError *error = NULL; + + if (! gimp_dashboard_log_stop_recording (dashboard, &error)) + { + gimp_message_literal ( + gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard))->gimp, + NULL, GIMP_MESSAGE_ERROR, error->message); + } + } +} + +void +dashboard_log_add_marker_cmd_callback (GtkAction *action, + gpointer data) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (data); + GtkWidget *dialog; + + #define LOG_ADD_MARKER_KEY "gimp-dashboard-log-add-marker-dialog" + + dialog = dialogs_get_dialog (G_OBJECT (dashboard), LOG_ADD_MARKER_KEY); + + if (! dialog) + { + dialog = gimp_query_string_box ( + _("Add Marker"), GTK_WIDGET (dashboard), + gimp_standard_help_func, GIMP_HELP_DASHBOARD_LOG_ADD_MARKER, + _("Enter a description for the marker"), + NULL, + G_OBJECT (dashboard), "destroy", + (GimpQueryStringCallback) dashboard_log_add_marker_response, + dashboard); + + dialogs_attach_dialog (G_OBJECT (dashboard), LOG_ADD_MARKER_KEY, dialog); + + #undef LOG_ADD_MARKER_KEY + } + + gtk_window_present (GTK_WINDOW (dialog)); +} + +void +dashboard_log_add_empty_marker_cmd_callback (GtkAction *action, + gpointer data) +{ + GimpDashboard *dashboard = GIMP_DASHBOARD (data); + + gimp_dashboard_log_add_marker (dashboard, NULL); +} + void dashboard_reset_cmd_callback (GtkAction *action, gpointer data) @@ -81,3 +239,45 @@ dashboard_low_swap_space_warning_cmd_callback (GtkAction *action, gimp_dashboard_set_low_swap_space_warning (dashboard, low_swap_space_warning); } + + +/* private functions */ + + +static void +dashboard_log_record_response (GtkWidget *dialog, + int response_id, + GimpDashboard *dashboard) +{ + if (response_id == GTK_RESPONSE_OK) + { + GFile *file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + GError *error = NULL; + + g_object_set_data_full (G_OBJECT (dashboard), + "gimp-dashboard-log-record-folder", + g_file_get_parent (file), + g_object_unref); + + if (! gimp_dashboard_log_start_recording (dashboard, file, &error)) + { + gimp_message_literal ( + gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard))->gimp, + NULL, GIMP_MESSAGE_ERROR, error->message); + + g_clear_error (&error); + } + + g_object_unref (file); + } + + gtk_widget_destroy (dialog); +} + +static void +dashboard_log_add_marker_response (GtkWidget *dialog, + const gchar *description, + GimpDashboard *dashboard) +{ + gimp_dashboard_log_add_marker (dashboard, description); +} diff --git a/app/actions/dashboard-commands.h b/app/actions/dashboard-commands.h index 803049936a..326721e9fd 100644 --- a/app/actions/dashboard-commands.h +++ b/app/actions/dashboard-commands.h @@ -26,6 +26,13 @@ void dashboard_history_duration_cmd_callback (GtkAction *action, GtkAction *current, gpointer data); +void dashboard_log_record_cmd_callback (GtkAction *action, + gpointer data); +void dashboard_log_add_marker_cmd_callback (GtkAction *action, + gpointer data); +void dashboard_log_add_empty_marker_cmd_callback (GtkAction *action, + gpointer data); + void dashboard_reset_cmd_callback (GtkAction *action, gpointer data); diff --git a/app/widgets/gimpdashboard.c b/app/widgets/gimpdashboard.c index 7bf73f1b39..c979f68db3 100644 --- a/app/widgets/gimpdashboard.c +++ b/app/widgets/gimpdashboard.c @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -53,8 +54,12 @@ #include "widgets-types.h" #include "core/gimp.h" +#include "core/gimp-gui.h" #include "core/gimp-utils.h" +#include "core/gimp-parallel.h" #include "core/gimpasync.h" +#include "core/gimpbacktrace.h" +#include "core/gimpwaitable.h" #include "gimpactiongroup.h" #include "gimpdocked.h" @@ -68,6 +73,7 @@ #include "gimpwindowstrategy.h" #include "gimp-intl.h" +#include "gimp-version.h" #define DEFAULT_UPDATE_INTERVAL GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC @@ -80,6 +86,9 @@ #define CPU_ACTIVE_ON /* individual cpu usage is above */ 0.75 #define CPU_ACTIVE_OFF /* individual cpu usage is below */ 0.25 +#define LOG_VERSION 1 +#define LOG_SAMPLE_FREQUENCY 10 /* samples per second */ + typedef enum { @@ -182,6 +191,7 @@ struct _VariableInfo const gchar *title; const gchar *description; VariableType type; + gboolean exclude_from_log; GimpRGB color; VariableFunc sample_func; VariableFunc reset_func; @@ -286,6 +296,20 @@ struct _GimpDashboardPrivate GimpDashboardUpdateInteval update_interval; GimpDashboardHistoryDuration history_duration; gboolean low_swap_space_warning; + + GOutputStream *log_output; + GError *log_error; + gint64 log_start_time; + gint log_sample_frequency; + gint log_n_samples; + gint log_n_markers; + VariableData log_variables[N_VARIABLES]; + gboolean log_include_backtrace; + GimpBacktrace *log_backtrace; + GHashTable *log_addresses; + + GtkWidget *log_record_button; + GtkLabel *log_add_marker_label; }; @@ -366,6 +390,8 @@ static void gimp_dashboard_field_set_active (GimpDashboard gint field, gboolean active); +static void gimp_dashboard_reset_unlocked (GimpDashboard *dashboard); + static void gimp_dashboard_reset_variables (GimpDashboard *dashboard); static gpointer gimp_dashboard_variable_get_data (GimpDashboard *dashboard, @@ -382,6 +408,20 @@ static gchar * gimp_dashboard_field_to_string (GimpDashboard gint field, gboolean full); +static gboolean gimp_dashboard_log_printf (GimpDashboard *dashboard, + const gchar *format, + ...) G_GNUC_PRINTF (2, 3); +static gboolean gimp_dashboard_log_print_escaped (GimpDashboard *dashboard, + const gchar *string); +static gint64 gimp_dashboard_log_time (GimpDashboard *dashboard); +static void gimp_dashboard_log_sample (GimpDashboard *dashboard, + gboolean variables_changed); +static void gimp_dashboard_log_update_highlight (GimpDashboard *dashboard); +static void gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard); + +static void gimp_dashboard_log_write_address_map (GimpAsync *async, + GimpDashboard *dashboard); + static gboolean gimp_dashboard_field_use_meter_underlay (Group group, gint field); @@ -1163,6 +1203,11 @@ gimp_dashboard_constructed (GObject *object) GimpDashboardPrivate *priv = dashboard->priv; GimpUIManager *ui_manager; GimpActionGroup *action_group; + GtkAction *action; + GtkWidget *button; + GtkWidget *box; + GtkWidget *image; + GtkWidget *label; Group group; G_OBJECT_CLASS (parent_class)->constructed (object); @@ -1176,7 +1221,6 @@ gimp_dashboard_constructed (GObject *object) const GroupInfo *group_info = &groups[group]; GroupData *group_data = &priv->groups[group]; GimpToggleActionEntry entry = {}; - GtkAction *action; entry.name = g_strdup_printf ("dashboard-group-%s", group_info->name); entry.label = g_dpgettext2 (NULL, "dashboard-group", group_info->title); @@ -1199,8 +1243,49 @@ gimp_dashboard_constructed (GObject *object) g_free ((gpointer) entry.name); } - gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard", - "dashboard-reset", NULL); + button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard", + "dashboard-log-record", NULL); + priv->log_record_button = button; + + button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard", + "dashboard-log-add-marker", + "dashboard-log-add-empty-marker", + gimp_get_extend_selection_mask (), + NULL); + + action = gtk_action_group_get_action (GTK_ACTION_GROUP (action_group), + "dashboard-log-add-marker"); + g_object_bind_property (action, "sensitive", + button, "visible", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + image = g_object_ref (gtk_bin_get_child (GTK_BIN (button))); + gtk_container_remove (GTK_CONTAINER (button), image); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_container_add (GTK_CONTAINER (button), box); + gtk_widget_set_halign (GTK_WIDGET (box), GTK_ALIGN_CENTER); + gtk_widget_set_valign (GTK_WIDGET (box), GTK_ALIGN_CENTER); + gtk_widget_show (box); + + gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0); + g_object_unref (image); + + label = gtk_label_new (NULL); + priv->log_add_marker_label = GTK_LABEL (label); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard", + "dashboard-reset", NULL); + + action = gtk_action_group_get_action (GTK_ACTION_GROUP (action_group), + "dashboard-reset"); + g_object_bind_property (action, "sensitive", + button, "visible", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + + gimp_action_group_update (action_group, dashboard); } static void @@ -1233,6 +1318,8 @@ gimp_dashboard_dispose (GObject *object) priv->low_swap_space_idle_id = 0; } + gimp_dashboard_log_stop_recording (dashboard, NULL); + gimp_dashboard_reset_variables (dashboard); G_OBJECT_CLASS (parent_class)->dispose (object); @@ -1577,26 +1664,39 @@ gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard, static gpointer gimp_dashboard_sample (GimpDashboard *dashboard) { - GimpDashboardPrivate *priv = dashboard->priv; - GimpDashboardUpdateInteval update_interval; - gint64 end_time; - gboolean seen_low_swap_space = FALSE; + GimpDashboardPrivate *priv = dashboard->priv; + gint64 last_sample_time = 0; + gint64 last_update_time = 0; + gboolean seen_low_swap_space = FALSE; g_mutex_lock (&priv->mutex); - update_interval = priv->update_interval; - end_time = g_get_monotonic_time (); - while (! priv->quit) { + gint64 update_interval; + gint64 sample_interval; + gint64 end_time; + + update_interval = priv->update_interval * G_TIME_SPAN_SECOND / 1000; + + if (priv->log_output) + sample_interval = G_TIME_SPAN_SECOND / priv->log_sample_frequency; + else + sample_interval = update_interval; + + end_time = last_sample_time + sample_interval; + if (! g_cond_wait_until (&priv->cond, &priv->mutex, end_time) || priv->update_now) { + gint64 time; gboolean variables_changed = FALSE; Variable variable; Group group; gint field; + time = g_get_monotonic_time (); + /* sample all variables */ for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++) { @@ -1611,119 +1711,123 @@ gimp_dashboard_sample (GimpDashboard *dashboard) sizeof (VariableData)); } - /* add samples to meters */ - for (group = FIRST_GROUP; group < N_GROUPS; group++) + /* log sample */ + if (priv->log_output) + gimp_dashboard_log_sample (dashboard, variables_changed); + + /* update gui */ + if (priv->update_now || + ! priv->log_output || + time - last_update_time >= update_interval) { - const GroupInfo *group_info = &groups[group]; - GroupData *group_data = &priv->groups[group]; - gdouble *sample; - gdouble total = 0.0; - - if (! group_info->has_meter) - continue; - - sample = g_new (gdouble, group_data->n_meter_values); - - for (field = 0; field < group_data->n_fields; field++) + /* add samples to meters */ + for (group = FIRST_GROUP; group < N_GROUPS; group++) { - const FieldInfo *field_info = &group_info->fields[field]; + const GroupInfo *group_info = &groups[group]; + GroupData *group_data = &priv->groups[group]; + gdouble *sample; + gdouble total = 0.0; - if (field_info->meter_value) + if (! group_info->has_meter) + continue; + + sample = g_new (gdouble, group_data->n_meter_values); + + for (field = 0; field < group_data->n_fields; field++) { - gdouble value; + const FieldInfo *field_info = &group_info->fields[field]; - if (field_info->meter_variable) - variable = field_info->meter_variable; - else - variable = field_info->variable; - - value = gimp_dashboard_variable_to_double (dashboard, - variable); - - if (value && - gimp_dashboard_field_use_meter_underlay (group, - field)) + if (field_info->meter_value) { - value = G_MAXDOUBLE; - } + gdouble value; - if (field_info->meter_cumulative) + if (field_info->meter_variable) + variable = field_info->meter_variable; + else + variable = field_info->variable; + + value = gimp_dashboard_variable_to_double (dashboard, + variable); + + if (value && + gimp_dashboard_field_use_meter_underlay (group, + field)) + { + value = G_MAXDOUBLE; + } + + if (field_info->meter_cumulative) + { + total += value; + value = total; + } + + sample[field_info->meter_value - 1] = value; + } + } + + gimp_meter_add_sample (group_data->meter, sample); + + g_free (sample); + } + + if (variables_changed) + { + /* enqueue update source */ + if (! priv->update_idle_id && + gtk_widget_get_mapped (GTK_WIDGET (dashboard))) + { + priv->update_idle_id = g_idle_add_full ( + G_PRIORITY_DEFAULT, + (GSourceFunc) gimp_dashboard_update, + dashboard, NULL); + } + + /* check for low swap space */ + if (priv->low_swap_space_warning && + priv->variables[VARIABLE_SWAP_OCCUPIED].available && + priv->variables[VARIABLE_SWAP_LIMIT].available) + { + guint64 swap_occupied; + guint64 swap_limit; + + swap_occupied = priv->variables[VARIABLE_SWAP_OCCUPIED].value.size; + swap_limit = priv->variables[VARIABLE_SWAP_LIMIT].value.size; + + if (! seen_low_swap_space && + swap_occupied >= LOW_SWAP_SPACE_WARNING_ON * swap_limit) { - total += value; - value = total; - } + if (! priv->low_swap_space_idle_id) + { + priv->low_swap_space_idle_id = + g_idle_add_full (G_PRIORITY_HIGH, + (GSourceFunc) gimp_dashboard_low_swap_space, + dashboard, NULL); + } - sample[field_info->meter_value - 1] = value; + seen_low_swap_space = TRUE; + } + else if (seen_low_swap_space && + swap_occupied <= LOW_SWAP_SPACE_WARNING_OFF * swap_limit) + { + if (priv->low_swap_space_idle_id) + { + g_source_remove (priv->low_swap_space_idle_id); + + priv->low_swap_space_idle_id = 0; + } + + seen_low_swap_space = FALSE; + } } } - gimp_meter_add_sample (group_data->meter, sample); + priv->update_now = FALSE; - g_free (sample); + last_update_time = time; } - if (variables_changed) - { - /* enqueue update source */ - if (! priv->update_idle_id && - gtk_widget_get_mapped (GTK_WIDGET (dashboard))) - { - priv->update_idle_id = g_idle_add_full ( - G_PRIORITY_DEFAULT, - (GSourceFunc) gimp_dashboard_update, - dashboard, NULL); - } - - /* check for low swap space */ - if (priv->low_swap_space_warning && - priv->variables[VARIABLE_SWAP_OCCUPIED].available && - priv->variables[VARIABLE_SWAP_LIMIT].available) - { - guint64 swap_occupied; - guint64 swap_limit; - - swap_occupied = priv->variables[VARIABLE_SWAP_OCCUPIED].value.size; - swap_limit = priv->variables[VARIABLE_SWAP_LIMIT].value.size; - - if (! seen_low_swap_space && - swap_occupied >= LOW_SWAP_SPACE_WARNING_ON * swap_limit) - { - if (! priv->low_swap_space_idle_id) - { - priv->low_swap_space_idle_id = - g_idle_add_full (G_PRIORITY_HIGH, - (GSourceFunc) gimp_dashboard_low_swap_space, - dashboard, NULL); - } - - seen_low_swap_space = TRUE; - } - else if (seen_low_swap_space && - swap_occupied <= LOW_SWAP_SPACE_WARNING_OFF * swap_limit) - { - if (priv->low_swap_space_idle_id) - { - g_source_remove (priv->low_swap_space_idle_id); - - priv->low_swap_space_idle_id = 0; - } - - seen_low_swap_space = FALSE; - } - } - } - - priv->update_now = FALSE; - - end_time = g_get_monotonic_time () + - update_interval * G_TIME_SPAN_SECOND / 1000; - } - - if (priv->update_interval != update_interval) - { - update_interval = priv->update_interval; - end_time = g_get_monotonic_time () + - update_interval * G_TIME_SPAN_SECOND / 1000; + last_sample_time = time; } } @@ -2843,6 +2947,27 @@ gimp_dashboard_field_set_active (GimpDashboard *dashboard, } } +static void +gimp_dashboard_reset_unlocked (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv; + Group group; + + priv = dashboard->priv; + + gegl_reset_stats (); + + gimp_dashboard_reset_variables (dashboard); + + for (group = FIRST_GROUP; group < N_GROUPS; group++) + { + GroupData *group_data = &priv->groups[group]; + + if (group_data->meter) + gimp_meter_clear_history (group_data->meter); + } +} + static void gimp_dashboard_reset_variables (GimpDashboard *dashboard) { @@ -3163,6 +3288,643 @@ gimp_dashboard_field_to_string (GimpDashboard *dashboard, return (gpointer) str; } +static gboolean +gimp_dashboard_log_printf (GimpDashboard *dashboard, + const gchar *format, + ...) +{ + GimpDashboardPrivate *priv = dashboard->priv; + va_list args; + gboolean result; + + if (priv->log_error) + return FALSE; + + va_start (args, format); + + result = g_output_stream_vprintf (priv->log_output, + NULL, NULL, + &priv->log_error, + format, args); + + va_end (args); + + return result; +} + +static gboolean +gimp_dashboard_log_print_escaped (GimpDashboard *dashboard, + const gchar *string) +{ + GimpDashboardPrivate *priv = dashboard->priv; + gchar buffer[1024]; + const gchar *s; + gint i; + + if (priv->log_error) + return FALSE; + + i = 0; + + #define FLUSH() \ + G_STMT_START \ + { \ + if (! g_output_stream_write_all (priv->log_output, \ + buffer, i, NULL, \ + NULL, &priv->log_error)) \ + { \ + return FALSE; \ + } \ + \ + i = 0; \ + } \ + G_STMT_END + + #define RESERVE(n) \ + G_STMT_START \ + { \ + if (i + (n) > sizeof (buffer)) \ + FLUSH (); \ + } \ + G_STMT_END + + for (s = string; *s; s++) + { + #define ESCAPE(from, to) \ + case from: \ + RESERVE (sizeof (to) - 1); \ + memcpy (&buffer[i], to, sizeof (to) - 1); \ + i += sizeof (to) - 1; \ + break; + + switch (*s) + { + ESCAPE ('"', """) + ESCAPE ('\'', "'") + ESCAPE ('<', "<") + ESCAPE ('>', ">") + ESCAPE ('&', "&") + + default: + RESERVE (1); + buffer[i++] = *s; + break; + } + + #undef ESCAPE + } + + FLUSH (); + + #undef FLUSH + #undef RESERVE + + return TRUE; +} + +static gint64 +gimp_dashboard_log_time (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + + return g_get_monotonic_time () - priv->log_start_time; +} + +static void +gimp_dashboard_log_sample (GimpDashboard *dashboard, + gboolean variables_changed) +{ + GimpDashboardPrivate *priv = dashboard->priv; + GimpBacktrace *backtrace = NULL; + gboolean empty = TRUE; + Variable variable; + + #define NONEMPTY() \ + G_STMT_START \ + { \ + if (empty) \ + { \ + gimp_dashboard_log_printf (dashboard, \ + ">\n"); \ + \ + empty = FALSE; \ + } \ + } \ + G_STMT_END + + gimp_dashboard_log_printf (dashboard, + "\n" + "log_n_samples, + (long long) gimp_dashboard_log_time (dashboard)); + + if (priv->log_n_samples == 0 || variables_changed) + { + NONEMPTY (); + + gimp_dashboard_log_printf (dashboard, + "\n"); + + for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++) + { + const VariableInfo *variable_info = &variables[variable]; + const VariableData *variable_data = &priv->variables[variable]; + VariableData *log_variable_data = &priv->log_variables[variable]; + + if (variable_info->exclude_from_log) + continue; + + if (priv->log_n_samples > 0 && + ! memcmp (variable_data, log_variable_data, + sizeof (VariableData))) + { + continue; + } + + *log_variable_data = *variable_data; + + if (variable_data->available) + { + #define LOG_VAR(format, ...) \ + gimp_dashboard_log_printf (dashboard, \ + "<%s>" format "\n", \ + variable_info->name, \ + __VA_ARGS__, \ + variable_info->name) + + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: + LOG_VAR ( + "%d", + variable_data->value.boolean); + break; + + case VARIABLE_TYPE_INTEGER: + LOG_VAR ( + "%d", + variable_data->value.integer); + break; + + case VARIABLE_TYPE_SIZE: + LOG_VAR ( + "%llu", + (unsigned long long) variable_data->value.size); + break; + + case VARIABLE_TYPE_SIZE_RATIO: + LOG_VAR ( + "%llu/%llu", + (unsigned long long) variable_data->value.size_ratio.antecedent, + (unsigned long long) variable_data->value.size_ratio.consequent); + break; + + case VARIABLE_TYPE_INT_RATIO: + LOG_VAR ( + "%d:%d", + variable_data->value.int_ratio.antecedent, + variable_data->value.int_ratio.consequent); + break; + + case VARIABLE_TYPE_PERCENTAGE: + LOG_VAR ( + "%.16g", + variable_data->value.percentage); + break; + + case VARIABLE_TYPE_DURATION: + LOG_VAR ( + "%.16g", + variable_data->value.duration); + break; + + case VARIABLE_TYPE_RATE_OF_CHANGE: + LOG_VAR ( + "%.16g", + variable_data->value.rate_of_change); + break; + } + + #undef LOG_VAR + } + else + { + gimp_dashboard_log_printf (dashboard, + "<%s />\n", + variable_info->name); + } + } + + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + if (priv->log_include_backtrace) + backtrace = gimp_backtrace_new (FALSE); + + if (backtrace) + { + gboolean backtrace_empty = TRUE; + gint n_threads; + gint thread; + + #define BACKTRACE_NONEMPTY() \ + G_STMT_START \ + { \ + if (backtrace_empty) \ + { \ + NONEMPTY (); \ + \ + gimp_dashboard_log_printf (dashboard, \ + "\n"); \ + \ + backtrace_empty = FALSE; \ + } \ + } \ + G_STMT_END + + if (priv->log_backtrace) + { + n_threads = gimp_backtrace_get_n_threads (priv->log_backtrace); + + for (thread = 0; thread < n_threads; thread++) + { + guintptr thread_id; + + thread_id = gimp_backtrace_get_thread_id (priv->log_backtrace, + thread); + + if (gimp_backtrace_find_thread_by_id (backtrace, + thread_id, thread) < 0) + { + const gchar *thread_name; + + BACKTRACE_NONEMPTY (); + + thread_name = + gimp_backtrace_get_thread_name (priv->log_backtrace, + thread); + + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + } + + n_threads = gimp_backtrace_get_n_threads (backtrace); + + for (thread = 0; thread < n_threads; thread++) + { + guintptr thread_id; + const gchar *thread_name; + gint n_frames; + gint n_head = 0; + gint n_tail = 0; + gint frame; + + thread_id = gimp_backtrace_get_thread_id (backtrace, thread); + thread_name = gimp_backtrace_get_thread_name (backtrace, thread); + + n_frames = gimp_backtrace_get_n_frames (backtrace, thread); + + if (priv->log_backtrace) + { + gint other_thread = gimp_backtrace_find_thread_by_id ( + priv->log_backtrace, thread_id, thread); + + if (other_thread >= 0) + { + gint n; + gint i; + + n = gimp_backtrace_get_n_frames (priv->log_backtrace, + other_thread); + n = MIN (n, n_frames); + + for (i = 0; i < n; i++) + { + if (gimp_backtrace_get_frame_address (backtrace, + thread, i) != + gimp_backtrace_get_frame_address (priv->log_backtrace, + other_thread, i)) + { + break; + } + } + + n_head = i; + n -= i; + + for (i = 0; i < n; i++) + { + if (gimp_backtrace_get_frame_address (backtrace, + thread, -i - 1) != + gimp_backtrace_get_frame_address (priv->log_backtrace, + other_thread, -i - 1)) + { + break; + } + } + + n_tail = i; + } + } + + if (n_head + n_tail == n_frames) + continue; + + BACKTRACE_NONEMPTY (); + + gimp_dashboard_log_printf (dashboard, + " 0) + { + gimp_dashboard_log_printf (dashboard, + " head=\"%d\"", + n_head); + } + + if (n_tail > 0) + { + gimp_dashboard_log_printf (dashboard, + " tail=\"%d\"", + n_tail); + } + + gimp_dashboard_log_printf (dashboard, + ">\n"); + + for (frame = n_head; frame < n_frames - n_tail; frame++) + { + unsigned long long address; + + address = gimp_backtrace_get_frame_address (backtrace, + thread, frame); + + gimp_dashboard_log_printf (dashboard, + "\n", + address); + + g_hash_table_add (priv->log_addresses, (gpointer) address); + } + + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + if (! backtrace_empty) + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + #undef BACKTRACE_NONEMPTY + } + else if (priv->log_backtrace) + { + NONEMPTY (); + + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + gimp_backtrace_free (priv->log_backtrace); + priv->log_backtrace = backtrace; + + if (empty) + { + gimp_dashboard_log_printf (dashboard, + " />\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + #undef NONEMPTY + + priv->log_n_samples++; +} + +static void +gimp_dashboard_log_update_highlight (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + GtkReliefStyle default_relief; + + gtk_widget_style_get (GTK_WIDGET (dashboard), + "button-relief", &default_relief, + NULL); + + gimp_button_set_suggested (priv->log_record_button, + gimp_dashboard_log_is_recording (dashboard), + default_relief); +} + +static void +gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + gchar buffer[32]; + + g_snprintf (buffer, sizeof (buffer), "%d", priv->log_n_markers + 1); + + gtk_label_set_text (priv->log_add_marker_label, buffer); +} + +static gint +gimp_dashboard_log_compare_addresses (gconstpointer a1, + gconstpointer a2) +{ + guintptr address1 = *(const guintptr *) a1; + guintptr address2 = *(const guintptr *) a2; + + if (address1 < address2) + return -1; + else if (address1 > address2) + return +1; + else + return 0; +} + +static void +gimp_dashboard_log_write_address_map (GimpAsync *async, + GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv = dashboard->priv; + GimpBacktraceSymbolInfo infos[2]; + guintptr *addresses; + gint n_addresses; + GList *iter; + gint i; + gint n; + + n_addresses = g_hash_table_size (priv->log_addresses); + + if (n_addresses == 0) + { + gimp_async_finish (async, NULL); + + return; + } + + addresses = g_new (guintptr, n_addresses); + + for (iter = g_hash_table_get_keys (priv->log_addresses), i = 0; + iter; + iter = g_list_next (iter), i++) + { + addresses[i] = (guintptr) iter->data; + } + + qsort (addresses, n_addresses, sizeof (guintptr), + gimp_dashboard_log_compare_addresses); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + n = 0; + + for (i = 0; i < n_addresses; i++) + { + GimpBacktraceSymbolInfo *info = &infos[n % 2]; + const GimpBacktraceSymbolInfo *prev_info = &infos[(n + 1) % 2]; + + if (gimp_async_is_canceled (async)) + break; + + if (gimp_backtrace_get_symbol_info (addresses[i], info)) + { + gboolean empty = TRUE; + + #define NONEMPTY() \ + G_STMT_START \ + { \ + if (empty) \ + { \ + gimp_dashboard_log_printf (dashboard, \ + ">\n"); \ + \ + empty = FALSE; \ + } \ + } \ + G_STMT_END + + gimp_dashboard_log_printf (dashboard, + "\n" + "
object_name, prev_info->object_name)) + { + NONEMPTY (); + + if (info->object_name[0]) + { + gimp_dashboard_log_printf (dashboard, + ""); + gimp_dashboard_log_print_escaped (dashboard, + info->object_name); + gimp_dashboard_log_printf (dashboard, + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + + if (n == 0 || strcmp (info->symbol_name, prev_info->symbol_name)) + { + NONEMPTY (); + + if (info->symbol_name[0]) + { + gimp_dashboard_log_printf (dashboard, + ""); + gimp_dashboard_log_print_escaped (dashboard, + info->symbol_name); + gimp_dashboard_log_printf (dashboard, + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + + if (n == 0 || info->symbol_address != prev_info->symbol_address) + { + NONEMPTY (); + + if (info->symbol_address) + { + gimp_dashboard_log_printf (dashboard, + "0x%llx\n", + (unsigned long long) + info->symbol_address); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + } + + if (empty) + { + gimp_dashboard_log_printf (dashboard, + " />\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + "\n"); + } + + #undef NONEMPTY + + n++; + } + } + + g_free (addresses); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + gimp_async_finish (async, NULL); +} + static gboolean gimp_dashboard_field_use_meter_underlay (Group group, gint field) @@ -3303,11 +4065,291 @@ gimp_dashboard_new (Gimp *gimp, return GTK_WIDGET (dashboard); } +gboolean +gimp_dashboard_log_start_recording (GimpDashboard *dashboard, + GFile *file, + GError **error) +{ + GimpDashboardPrivate *priv; + GimpUIManager *ui_manager; + GimpActionGroup *action_group; + gchar *version; + gboolean has_backtrace; + Variable variable; + + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE); + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + priv = dashboard->priv; + + g_return_val_if_fail (! gimp_dashboard_log_is_recording (dashboard), FALSE); + + g_mutex_lock (&priv->mutex); + + priv->log_output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, NULL, + error)); + + if (! priv->log_output) + { + g_mutex_unlock (&priv->mutex); + + return FALSE; + } + + priv->log_error = NULL; + priv->log_start_time = g_get_monotonic_time (); + priv->log_sample_frequency = LOG_SAMPLE_FREQUENCY; + priv->log_n_samples = 0; + priv->log_n_markers = 0; + priv->log_include_backtrace = TRUE; + priv->log_backtrace = NULL; + priv->log_addresses = g_hash_table_new (NULL, NULL); + + if (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY")) + { + priv->log_sample_frequency = + atoi (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY")); + + priv->log_sample_frequency = CLAMP (priv->log_sample_frequency, + 1, 1000); + } + + if (g_getenv ("GIMP_PERFORMANCE_LOG_NO_BACKTRACE")) + priv->log_include_backtrace = FALSE; + + if (priv->log_include_backtrace) + has_backtrace = gimp_backtrace_init (); + else + has_backtrace = FALSE; + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n", + LOG_VERSION); + + version = gimp_version (TRUE, FALSE); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + gimp_dashboard_log_print_escaped (dashboard, version); + gimp_dashboard_log_printf (dashboard, + "\n"); + + g_free (version); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n" + "%d\n" + "%d\n" + "\n", + priv->log_sample_frequency, + has_backtrace); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++) + { + const VariableInfo *variable_info = &variables[variable]; + const gchar *type = ""; + + if (variable_info->exclude_from_log) + continue; + + switch (variable_info->type) + { + case VARIABLE_TYPE_BOOLEAN: type = "boolean"; break; + case VARIABLE_TYPE_INTEGER: type = "integer"; break; + case VARIABLE_TYPE_SIZE: type = "size"; break; + case VARIABLE_TYPE_SIZE_RATIO: type = "size-ratio"; break; + case VARIABLE_TYPE_INT_RATIO: type = "int-ratio"; break; + case VARIABLE_TYPE_PERCENTAGE: type = "percentage"; break; + case VARIABLE_TYPE_DURATION: type = "duration"; break; + case VARIABLE_TYPE_RATE_OF_CHANGE: type = "rate-of-change"; break; + } + + gimp_dashboard_log_printf (dashboard, + "\n", + variable_info->name, + type); + } + + gimp_dashboard_log_printf (dashboard, + "\n"); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + if (priv->log_error) + { + gimp_backtrace_shutdown (); + + g_clear_object (&priv->log_output); + + g_propagate_error (error, priv->log_error); + priv->log_error = NULL; + + g_mutex_unlock (&priv->mutex); + + return FALSE; + } + + gimp_dashboard_reset_unlocked (dashboard); + + priv->update_now = TRUE; + g_cond_signal (&priv->cond); + + g_mutex_unlock (&priv->mutex); + + gimp_dashboard_log_update_n_markers (dashboard); + + ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard)); + action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard"); + + gimp_action_group_update (action_group, dashboard); + + gimp_dashboard_log_update_highlight (dashboard); + + return TRUE; +} + +gboolean +gimp_dashboard_log_stop_recording (GimpDashboard *dashboard, + GError **error) +{ + GimpDashboardPrivate *priv; + GimpUIManager *ui_manager; + GimpActionGroup *action_group; + gboolean result = TRUE; + + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + priv = dashboard->priv; + + if (! gimp_dashboard_log_is_recording (dashboard)) + return TRUE; + + g_mutex_lock (&priv->mutex); + + if (priv->log_include_backtrace) + gimp_backtrace_shutdown (); + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + + if (g_hash_table_size (priv->log_addresses) > 0) + { + GimpAsync *async; + + async = gimp_parallel_run_async_full ( + -1, + (GimpParallelRunAsyncFunc) gimp_dashboard_log_write_address_map, + dashboard, NULL); + + gimp_wait (priv->gimp, GIMP_WAITABLE (async), + _("Resolving symbol information...")); + + g_object_unref (async); + } + + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + + if (! priv->log_error) + g_output_stream_close (priv->log_output, NULL, &priv->log_error); + + g_clear_object (&priv->log_output); + + if (priv->log_error) + { + g_propagate_error (error, priv->log_error); + priv->log_error = NULL; + + result = FALSE; + } + + g_clear_pointer (&priv->log_backtrace, gimp_backtrace_free); + g_clear_pointer (&priv->log_addresses, g_hash_table_unref); + + g_mutex_unlock (&priv->mutex); + + ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard)); + action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard"); + + gimp_action_group_update (action_group, dashboard); + + gimp_dashboard_log_update_highlight (dashboard); + + return result; +} + +gboolean +gimp_dashboard_log_is_recording (GimpDashboard *dashboard) +{ + GimpDashboardPrivate *priv; + + g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE); + + priv = dashboard->priv; + + return priv->log_output != NULL; +} + +void +gimp_dashboard_log_add_marker (GimpDashboard *dashboard, + const gchar *description) +{ + GimpDashboardPrivate *priv; + + g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); + g_return_if_fail (gimp_dashboard_log_is_recording (dashboard)); + + priv = dashboard->priv; + + g_mutex_lock (&priv->mutex); + + priv->log_n_markers++; + + gimp_dashboard_log_printf (dashboard, + "\n" + "log_n_markers, + (long long) gimp_dashboard_log_time (dashboard)); + + if (description && description[0]) + { + gimp_dashboard_log_printf (dashboard, + ">\n"); + gimp_dashboard_log_print_escaped (dashboard, description); + gimp_dashboard_log_printf (dashboard, + "\n" + "\n"); + } + else + { + gimp_dashboard_log_printf (dashboard, + " />\n"); + } + + g_mutex_unlock (&priv->mutex); + + gimp_dashboard_log_update_n_markers (dashboard); +} + void gimp_dashboard_reset (GimpDashboard *dashboard) { GimpDashboardPrivate *priv; - Group group; g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); @@ -3315,17 +4357,7 @@ gimp_dashboard_reset (GimpDashboard *dashboard) g_mutex_lock (&priv->mutex); - gegl_reset_stats (); - - gimp_dashboard_reset_variables (dashboard); - - for (group = FIRST_GROUP; group < N_GROUPS; group++) - { - GroupData *group_data = &priv->groups[group]; - - if (group_data->meter) - gimp_meter_clear_history (group_data->meter); - } + gimp_dashboard_reset_unlocked (dashboard); priv->update_now = TRUE; g_cond_signal (&priv->cond); diff --git a/app/widgets/gimpdashboard.h b/app/widgets/gimpdashboard.h index d39e3634bf..3255775cd0 100644 --- a/app/widgets/gimpdashboard.h +++ b/app/widgets/gimpdashboard.h @@ -51,25 +51,34 @@ struct _GimpDashboardClass GType gimp_dashboard_get_type (void) G_GNUC_CONST; -GtkWidget * gimp_dashboard_new (Gimp *gimp, - GimpMenuFactory *menu_factory); +GtkWidget * gimp_dashboard_new (Gimp *gimp, + GimpMenuFactory *menu_factory); -void gimp_dashboard_reset (GimpDashboard *dashboard); +gboolean gimp_dashboard_log_start_recording (GimpDashboard *dashboard, + GFile *file, + GError **error); +gboolean gimp_dashboard_log_stop_recording (GimpDashboard *dashboard, + GError **error); +gboolean gimp_dashboard_log_is_recording (GimpDashboard *dashboard); +void gimp_dashboard_log_add_marker (GimpDashboard *dashboard, + const gchar *description); -void gimp_dashboard_set_update_interval (GimpDashboard *dashboard, - GimpDashboardUpdateInteval update_interval); -GimpDashboardUpdateInteval gimp_dashboard_get_update_interval (GimpDashboard *dashboard); +void gimp_dashboard_reset (GimpDashboard *dashboard); -void gimp_dashboard_set_history_duration (GimpDashboard *dashboard, - GimpDashboardHistoryDuration history_duration); -GimpDashboardHistoryDuration gimp_dashboard_get_history_duration (GimpDashboard *dashboard); +void gimp_dashboard_set_update_interval (GimpDashboard *dashboard, + GimpDashboardUpdateInteval update_interval); +GimpDashboardUpdateInteval gimp_dashboard_get_update_interval (GimpDashboard *dashboard); -void gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard, - gboolean low_swap_space_warning); -gboolean gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard); +void gimp_dashboard_set_history_duration (GimpDashboard *dashboard, + GimpDashboardHistoryDuration history_duration); +GimpDashboardHistoryDuration gimp_dashboard_get_history_duration (GimpDashboard *dashboard); -void gimp_dashboard_menu_setup (GimpUIManager *manager, - const gchar *ui_path); +void gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard, + gboolean low_swap_space_warning); +gboolean gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard); + +void gimp_dashboard_menu_setup (GimpUIManager *manager, + const gchar *ui_path); #endif /* __GIMP_DASHBOARD_H__ */ diff --git a/app/widgets/gimphelp-ids.h b/app/widgets/gimphelp-ids.h index 9b3a0aef92..0a81534abc 100644 --- a/app/widgets/gimphelp-ids.h +++ b/app/widgets/gimphelp-ids.h @@ -674,6 +674,9 @@ #define GIMP_HELP_DASHBOARD_GROUPS "gimp-dashboard-groups" #define GIMP_HELP_DASHBOARD_UPDATE_INTERVAL "gimp-dashboard-update-interval" #define GIMP_HELP_DASHBOARD_HISTORY_DURATION "gimp-dashboard-history-duration" +#define GIMP_HELP_DASHBOARD_LOG_RECORD "gimp-dashboard-log-record" +#define GIMP_HELP_DASHBOARD_LOG_ADD_MARKER "gimp-dashboard-log-add-marker" +#define GIMP_HELP_DASHBOARD_LOG_ADD_EMPTY_MARKER "gimp-dashboard-log-add-empty-marker" #define GIMP_HELP_DASHBOARD_RESET "gimp-dashboard-reset" #define GIMP_HELP_DASHBOARD_LOW_SWAP_SPACE_WARNING "gimp-dashboard-low-swap-space-warning" diff --git a/icons/Color/16/gimp-marker.png b/icons/Color/16/gimp-marker.png new file mode 100644 index 0000000000000000000000000000000000000000..f75eecb6bf6798a81982a3667af124a7806b11de GIT binary patch literal 489 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1S9L5e~$ zOL9^f+&z5*6nqnlvQvvV7Dv1WN^*fD9ZO3x@{1T;b8>XPgw6$u27yGK^9w4AGSf3k z7@VCI97{@yGLuS6GV}9vgEN4(Ip$<$rz&KWloVL$>n9hbCYEHDr6%X+=H}-WgDlt2 z$xKeoD^4xePpZ@{PSiKiGtfUAlEex${i>&nV~9oX)?jPCCI^u@={uUfl$qUQP~5n5 z`O>d!^BVcAqi^LZsNJ}@rhwhrTYuxp%@NlWBJDaqb=^HP`^}j>zH$qLgFelbluHv4 z%(}mP@8w*h!`+X4yXXD7B5sy@J3({n#jL*D5thzJCTuqhp!SADct}S!T7}waa`R$vUeoc4@%V%3q2t9t%WPX$nr7bp6bVPd0Z& zkN)iDIjp?s#TSJ`2fx2e^*zut`>i3vl&7ptD$!{%$Ax^>ZHSDO{?5j#6%p4r#j+LX OK?YA(KbLh*2~7a%|GFmt literal 0 HcmV?d00001 diff --git a/icons/Color/16/media-record.png b/icons/Color/16/media-record.png new file mode 100644 index 0000000000000000000000000000000000000000..aaaf2ee5b426731d876e0ac9b2b7931dbf9d3b34 GIT binary patch literal 717 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1TqK#D># zOL9^fd{a|06BU9|lk(-qRgbylFa-(-QWzMb&ffi*{KQ{B_#z``ufR5sfi_-Wk7>-bMy0xLALAX zWG1KP6{i;KCspbeC+eH%8R#DlNn&MSVBF#9;uvDlyL95lY~e(SWA*RL#cwTJINfWO zh|#N30j^hi7M5FPi?;w2Nqi2^&G=#}69 zzEj1`r-xC#Vw(6p`SYLmeV9MVp51Y2&^0gF?h|qk`j7KIn0slL*p{fb`XJ#&M!?^;Fy_Yx6k6OF0QC(eJe0>^&?2VnW$M`{$RChZ-~Fz0!RDOAKb>7l3Ps6W0vpwQ z4UIlu-FfL6(+$l7w?5~|+`n0Lmo+=M^s>kr+y8qHWh!^&#h-pYy-R?PO{{|Zv9i|M z?0EOZb>^P`ezxXK`k?Ar#g)atcOX*4^}CJ!A2TW2H4RaQY}Uu^w>+zrd41;A;RQGU z{NQFNxc0t&-|Nb+ZP&iHN3DO-GjWb#M)C_O=h*Duj<4g=-aho){4;Hn`Q@K4zkQpw w`pU!uavO~0%o~DPw?6o^Y4eq()35rkGUVbZJbwKYFoiI9y85}Sb4q9e0H0Ve_5c6? literal 0 HcmV?d00001 diff --git a/icons/Color/color-scalable.svg b/icons/Color/color-scalable.svg index d25998923b..27efde655d 100644 --- a/icons/Color/color-scalable.svg +++ b/icons/Color/color-scalable.svg @@ -13,7 +13,7 @@ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" viewBox="0 0 1411.4984 384.2294" sodipodi:docname="color-scalable.svg" - inkscape:version="0.92.2 2405546, 2018-03-11" + inkscape:version="0.92.3 (2405546, 2018-03-11)" id="svg" height="384.2294" width="1411.4984" @@ -59,15 +59,15 @@ showborder="false" inkscape:current-layer="stock" inkscape:window-maximized="1" - inkscape:window-y="27" - inkscape:window-x="0" - inkscape:cy="166.99597" - inkscape:cx="794.81478" - inkscape:zoom="10.24" + inkscape:window-y="24" + inkscape:window-x="65" + inkscape:cy="118.27566" + inkscape:cx="616.06499" + inkscape:zoom="1" showgrid="false" id="namedview88" - inkscape:window-height="741" - inkscape:window-width="1366" + inkscape:window-height="876" + inkscape:window-width="1535" inkscape:pageshadow="2" inkscape:pageopacity="0" guidetolerance="10" @@ -18338,6 +18338,27 @@ y1="234.1875" x2="76.0625" y2="252.02119" /> + + + + + @@ -38645,7 +38666,7 @@ transform="matrix(0.10215483,0,0,0.10215483,-0.52798284,140.10035)" /> @@ -44184,7 +44205,7 @@ inkscape:r_cy="true" inkscape:r_cx="true" id="path4926" - d="M 7.8738374,1044.3622 C 6.8302869,1044.4246 6,1045.2489 6,1046.2491 c 0,0.039 0.00388,0.075 0.00629,0.1131 h 3.9874254 c 0.00241,-0.038 0.00629,-0.074 0.00629,-0.1131 0,-1.0409 -0.8967349,-1.8869 -1.9999944,-1.8869 -0.043099,0 -0.083767,0 -0.1261851,0 z" + d="M 7.8738374,1044.3622 C 6.8302869,1044.4246 6,1045.2489 6,1046.2491 c 0,0.039 0.00388,0.075 0.00629,0.1131 h 3.9874254 c 0.00241,-0.038 0.00629,-0.074 0.00629,-0.1131 0,-1.0409 -0.8967289,-1.8869 -1.9999884,-1.8869 -0.043099,0 -0.083767,0 -0.1261851,0 z" style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#fffbd7;fill-opacity:0.55681817;fill-rule:evenodd;stroke:none;stroke-width:1.01903164;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none" inkscape:connector-curvature="0" /> @@ -46478,7 +46499,7 @@ inkscape:connector-curvature="0" /> @@ -46945,7 +46966,7 @@ style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:Sans;-inkscape-font-specification:Sans;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;display:inline;overflow:visible;visibility:visible;fill:#fcaf3e;fill-opacity:1;stroke:none;stroke-width:0.27636784;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker:none;enable-background:accumulate" /> @@ -48206,7 +48227,7 @@ style="opacity:1;fill:#f57900;fill-opacity:1;stroke:none;stroke-width:0.30461028;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> @@ -49319,7 +49340,7 @@ inkscape:connector-curvature="0" /> + + + + + + + + + + + + + + + + + + + + + + + + id="path2210-25" + inkscape:connector-curvature="0" /> @@ -58288,7 +58406,7 @@ sodipodi:nodetypes="cccccccc" /> @@ -58316,6 +58434,33 @@ sodipodi:nodetypes="ccccccccc" /> + + + + + + + + + + + GIMP Marker + + + + + + + + image/svg+xml + + GIMP Marker + + + Ell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/Color/scalable/media-record.svg b/icons/Color/scalable/media-record.svg new file mode 100644 index 0000000000..3d0acfd7c9 --- /dev/null +++ b/icons/Color/scalable/media-record.svg @@ -0,0 +1,125 @@ + + + + + Media Record + + + + + + + + + + + + + + image/svg+xml + + Media Record + + + Ell + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/Symbolic/16/gimp-marker.png b/icons/Symbolic/16/gimp-marker.png new file mode 100644 index 0000000000000000000000000000000000000000..f6de788c6bcd495f3a3d89aae7e6159a7bc3f572 GIT binary patch literal 379 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1S9L5e~$ zOL9^f+&z5*6nqnlvQvvV7Dv1WN^*fD9ZO3x@{1T;b8>XPgw6$u27yGK^9w4AGSf3k z7@VCI97{@yGLuS6GV}9vgEN4(Ip$<$rz&KWloVL$>n9hbCYEHDr6%X+=H}-WgDlt2 z$xKeoD^4xePpZ@{PSiKiGtfUAlEex$J>ApAF~p+xZlEJyg8>Kg`qP>2eG;B1!gpEGLE%@2@oJ!z44$rjF6*2Ung9ur BfB^si literal 0 HcmV?d00001 diff --git a/icons/Symbolic/16/media-record.png b/icons/Symbolic/16/media-record.png new file mode 100644 index 0000000000000000000000000000000000000000..e1e651668afa17a2fa35d1ec500e44b9c52dbcab GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ z@ErkR#;MwT(m+AU64!{5;QX|b^2DN4hVt@qz0ADq;^f4FRK5J7^x5xhq=1TqK#D># zOL9^fd{a|06BU9|lk(-qRgbylFa-(-QWzMb&ffi*{KQ{B_#z``ufR5sfi_-Wk7>-bMy0xLALAX zWG1KP6{i;KCspbeC+eH%8R#DlNn!<>e%sT?B3LLk%#)hN!+2AnWv=0nx5b~#9J_1>&z@k9+5-KUM_mD`|r%}YBza4yuOhBaE|e@8M?;n z8P3RkjdWQh;1nt1Dj{OxYn=G3dDS^~v2Ck%ZMhaVpLKt}Tq9$Gqs!Lfb1qEDXUbUK zsYXuARbn(=FZ>{5oA`=XCZP@2pYLP2-_oHXBydqRbit~>Ir;(awR@}H7Yp8Gv|!x9xI^$_pi9}k-+yHP_1|G^JQ%a` UvgnMjKyNa5y85}Sb4q9e0AAX)9RL6T literal 0 HcmV?d00001 diff --git a/icons/Symbolic/scalable/gimp-marker.svg b/icons/Symbolic/scalable/gimp-marker.svg new file mode 100644 index 0000000000..23bd675488 --- /dev/null +++ b/icons/Symbolic/scalable/gimp-marker.svg @@ -0,0 +1,153 @@ + + + + + GIMP Marker + + + + + + + + image/svg+xml + + GIMP Marker + + + Ell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/Symbolic/scalable/media-record.svg b/icons/Symbolic/scalable/media-record.svg new file mode 100644 index 0000000000..1965434d5f --- /dev/null +++ b/icons/Symbolic/scalable/media-record.svg @@ -0,0 +1,93 @@ + + + + + Media Record + + + + + + + + image/svg+xml + + Media Record + + + Ell + + + + + + + + + + + + + + + + + + diff --git a/icons/Symbolic/symbolic-scalable.svg b/icons/Symbolic/symbolic-scalable.svg index a0986cea04..a859cabf83 100644 --- a/icons/Symbolic/symbolic-scalable.svg +++ b/icons/Symbolic/symbolic-scalable.svg @@ -26,7 +26,7 @@ image/svg+xml - + Barbara Muraus, Jakub Steiner, Klaus Staedtler @@ -45,15 +45,15 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="1366" - inkscape:window-height="741" + inkscape:window-width="1535" + inkscape:window-height="876" id="namedview88" showgrid="true" - inkscape:zoom="16" - inkscape:cx="797.92087" - inkscape:cy="177.56439" - inkscape:window-x="0" - inkscape:window-y="27" + inkscape:zoom="1" + inkscape:cx="788.00815" + inkscape:cy="59.704507" + inkscape:window-x="65" + inkscape:window-y="24" inkscape:window-maximized="1" inkscape:current-layer="stock" showborder="false" @@ -26824,7 +26824,7 @@ inkscape:connector-curvature="0" /> + + + + @@ -31873,7 +31892,7 @@ inkscape:connector-curvature="0" id="rect6791-5" transform="matrix(0,0.6666668,-0.6666668,0,77.000215,619.33333)" - d="M 7,3 C 5.3380003,3 4,4.3380003 4,6 l -0.00351,2.9999947 2.9973747,0 L 7,7.5 C 7.0033989,6.6690072 7.6690002,6 8.5,6 h 6 C 15.331,6 16,6.6690002 16,7.5 l -0.014,1.4999947 2.997377,0 L 19,6 C 19.009212,4.3380259 17.662,3 16,3 Z M 3.996488,14.999995 4,18 c 0.00195,1.661999 1.3380003,3 3,3 h 9 c 1.662,0 3,-1.338 3,-3 l -0.01663,-3.000005 -2.997377,0 L 16,16.5 c 0.0078,0.830964 -0.669,1.5 -1.5,1.5 h -6 C 7.6690002,18 7,17.331 7,16.5 L 6.99386,14.999995 Z" + d="M 7,3 C 5.3380003,3 4,4.3380003 4,6 L 3.99649,8.9999947 H 6.9938647 L 7,7.5 C 7.0033989,6.6690072 7.6690002,6 8.5,6 h 6 C 15.331,6 16,6.6690002 16,7.5 l -0.014,1.4999947 h 2.997377 L 19,6 C 19.009212,4.3380259 17.662,3 16,3 Z M 3.996488,14.999995 4,18 c 0.00195,1.661999 1.3380003,3 3,3 h 9 c 1.662,0 3,-1.338 3,-3 L 18.98337,14.999995 H 15.985993 L 16,16.5 c 0.0078,0.830964 -0.669,1.5 -1.5,1.5 h -6 C 7.6690002,18 7,17.331 7,16.5 L 6.99386,14.999995 Z" style="fill:url(#linearGradient9944);fill-opacity:1;stroke:none;stroke-width:1.49999964" sodipodi:nodetypes="ssccssssccssscssssccsssscc" /> @@ -32178,7 +32197,7 @@ height="5" width="5" id="rect9128-9" - style="opacity:1;fill:#ffffff !important;fill-opacity:1;stroke:none;stroke-width:0.30977488;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" + style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.30977488;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers" inkscape:label="color-important" /> @@ -34805,7 +34824,7 @@ style="display:inline" transform="translate(-132.8914,-347.20086)"> + id="path11855-6" + inkscape:connector-curvature="0" /> + style="opacity:0;fill:none;stroke:none" /> @@ -35720,7 +35740,8 @@ + id="path5911" + inkscape:connector-curvature="0" /> + id="path11855" + inkscape:connector-curvature="0" /> + id="path30125-2-6" + inkscape:connector-curvature="0" /> + id="path3938-37" + inkscape:connector-curvature="0" /> + id="path3938-7" + inkscape:connector-curvature="0" /> @@ -39220,11 +39245,13 @@ + id="path3938-9" + inkscape:connector-curvature="0" /> + id="path3938-7-5" + inkscape:connector-curvature="0" /> @@ -41361,7 +41388,7 @@ transform="matrix(0.99912557,0,0,1,-633.85747,-167.42187)" id="toilet-paper-stroke" d="m 797.76172,203.9082 c -2.38956,2.65602 -2.19,6.61799 -0.11524,8.53516 -1.61599,4.79484 -2.94674,6.06941 -1.625,6.78906 l 8.0254,0.4043 c 2.1892,-1.32536 -1.22351,-4.67312 1.97265,-7.06836 h 0.0352 c 0.007,0 0.0137,-0.004 0.0137,-0.0137 v -0.0176 c 0.0992,0.0202 0.19894,0.0306 0.29883,0.0312 1.42141,8e-5 2.57383,-1.94102 2.57422,-4.33594 2.5e-4,-2.39568 -1.15236,-4.33797 -2.57422,-4.33789 -0.0999,0.002 -0.19971,0.0137 -0.29883,0.0352 v -0.0215 c 0,-0.004 -0.006,-0.0137 -0.0137,-0.0137 -2.47956,0.004 -6.46975,-0.0148 -8.29297,0.0137 z" - style="fill:none;fill-opacity:1;stroke:url(#linearGradient6478);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;opacity:0" + style="opacity:0;fill:none;fill-opacity:1;stroke:url(#linearGradient6478);stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" inkscape:connector-curvature="0" sodipodi:nodetypes="cccccsscccccscc" /> + + + + + + + + + + + + + + + + + + + + + +