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 0000000000..f75eecb6bf Binary files /dev/null and b/icons/Color/16/gimp-marker.png differ diff --git a/icons/Color/16/media-record.png b/icons/Color/16/media-record.png new file mode 100644 index 0000000000..aaaf2ee5b4 Binary files /dev/null and b/icons/Color/16/media-record.png differ 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 0000000000..f6de788c6b Binary files /dev/null and b/icons/Symbolic/16/gimp-marker.png differ diff --git a/icons/Symbolic/16/media-record.png b/icons/Symbolic/16/media-record.png new file mode 100644 index 0000000000..e1e651668a Binary files /dev/null and b/icons/Symbolic/16/media-record.png differ 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" /> + + + + + + + + + + + + + + + + + + + + + +