/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis * * gimpdashboard.c * Copyright (C) 2017 Ell * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #ifdef G_OS_WIN32 #include #include #define HAVE_CPU_GROUP #define HAVE_MEMORY_GROUP #elif defined(PLATFORM_OSX) #include #include #define HAVE_CPU_GROUP #define HAVE_MEMORY_GROUP #else /* ! G_OS_WIN32 && ! PLATFORM_OSX */ #ifdef HAVE_SYS_TIMES_H #include #define HAVE_CPU_GROUP #endif /* HAVE_SYS_TIMES_H */ #if defined (HAVE_UNISTD_H) && defined (HAVE_FCNTL_H) #include #include #ifdef _SC_PAGE_SIZE #define HAVE_MEMORY_GROUP #endif /* _SC_PAGE_SIZE */ #endif /* HAVE_UNISTD_H && HAVE_FCNTL_H */ #endif /* ! G_OS_WIN32 */ #include "libgimpbase/gimpbase.h" #include "libgimpmath/gimpmath.h" #include "libgimpwidgets/gimpwidgets.h" #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/gimptempbuf.h" #include "core/gimpwaitable.h" #include "gimpactiongroup.h" #include "gimpdocked.h" #include "gimpdashboard.h" #include "gimpdialogfactory.h" #include "gimphelp-ids.h" #include "gimphighlightablebutton.h" #include "gimpmeter.h" #include "gimpsessioninfo-aux.h" #include "gimptoggleaction.h" #include "gimpuimanager.h" #include "gimpwidgets-utils.h" #include "gimpwindowstrategy.h" #include "gimp-intl.h" #include "gimp-log.h" #include "gimp-version.h" #define DEFAULT_UPDATE_INTERVAL GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC #define DEFAULT_HISTORY_DURATION GIMP_DASHBOARD_HISTORY_DURATION_60_SEC #define DEFAULT_LOW_SWAP_SPACE_WARNING TRUE #define LOW_SWAP_SPACE_WARNING_ON /* swap occupied is above */ 0.90 /* of swap limit */ #define LOW_SWAP_SPACE_WARNING_OFF /* swap occupied is below */ 0.85 /* of swap limit */ #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_MIN 1 /* samples per second */ #define LOG_SAMPLE_FREQUENCY_MAX 1000 /* samples per second */ #define LOG_DEFAULT_SAMPLE_FREQUENCY 10 /* samples per second */ #define LOG_DEFAULT_BACKTRACE TRUE #define LOG_DEFAULT_MESSAGES TRUE #define LOG_DEFAULT_PROGRESSIVE FALSE typedef enum { VARIABLE_NONE, FIRST_VARIABLE, /* cache */ VARIABLE_CACHE_OCCUPIED = FIRST_VARIABLE, VARIABLE_CACHE_MAXIMUM, VARIABLE_CACHE_LIMIT, VARIABLE_CACHE_COMPRESSION, VARIABLE_CACHE_HIT_MISS, /* swap */ VARIABLE_SWAP_OCCUPIED, VARIABLE_SWAP_SIZE, VARIABLE_SWAP_LIMIT, VARIABLE_SWAP_QUEUED, VARIABLE_SWAP_QUEUE_STALLS, VARIABLE_SWAP_QUEUE_FULL, VARIABLE_SWAP_READ, VARIABLE_SWAP_READ_THROUGHPUT, VARIABLE_SWAP_WRITTEN, VARIABLE_SWAP_WRITE_THROUGHPUT, VARIABLE_SWAP_COMPRESSION, #ifdef HAVE_CPU_GROUP /* cpu */ VARIABLE_CPU_USAGE, VARIABLE_CPU_ACTIVE, VARIABLE_CPU_ACTIVE_TIME, #endif #ifdef HAVE_MEMORY_GROUP /* memory */ VARIABLE_MEMORY_USED, VARIABLE_MEMORY_AVAILABLE, VARIABLE_MEMORY_SIZE, #endif /* misc */ VARIABLE_MIPMAPED, VARIABLE_ASSIGNED_THREADS, VARIABLE_ACTIVE_THREADS, VARIABLE_ASYNC_RUNNING, VARIABLE_TILE_ALLOC_TOTAL, VARIABLE_SCRATCH_TOTAL, VARIABLE_TEMP_BUF_TOTAL, N_VARIABLES, VARIABLE_SEPARATOR } Variable; typedef enum { VARIABLE_TYPE_BOOLEAN, VARIABLE_TYPE_INTEGER, VARIABLE_TYPE_SIZE, VARIABLE_TYPE_SIZE_RATIO, VARIABLE_TYPE_INT_RATIO, VARIABLE_TYPE_PERCENTAGE, VARIABLE_TYPE_DURATION, VARIABLE_TYPE_RATE_OF_CHANGE } VariableType; typedef enum { FIRST_GROUP, GROUP_CACHE = FIRST_GROUP, GROUP_SWAP, #ifdef HAVE_CPU_GROUP GROUP_CPU, #endif #ifdef HAVE_MEMORY_GROUP GROUP_MEMORY, #endif GROUP_MISC, N_GROUPS } Group; typedef struct _VariableInfo VariableInfo; typedef struct _FieldInfo FieldInfo; typedef struct _GroupInfo GroupInfo; typedef struct _VariableData VariableData; typedef struct _FieldData FieldData; typedef struct _GroupData GroupData; typedef void (* VariableFunc) (GimpDashboard *dashboard, Variable variable); struct _VariableInfo { const gchar *name; const gchar *title; const gchar *description; VariableType type; gboolean exclude_from_log; GimpRGB color; VariableFunc sample_func; VariableFunc reset_func; gconstpointer data; }; struct _FieldInfo { Variable variable; const gchar *title; gboolean default_active; gboolean show_in_header; Variable meter_variable; gint meter_value; gboolean meter_cumulative; }; struct _GroupInfo { const gchar *name; const gchar *title; const gchar *description; gboolean default_active; gboolean default_expanded; gboolean has_meter; Variable meter_limit; const Variable *meter_led; const FieldInfo *fields; }; struct _VariableData { gboolean available; union { gboolean boolean; gint integer; guint64 size; /* in bytes */ struct { guint64 antecedent; guint64 consequent; } size_ratio; struct { gint antecedent; gint consequent; } int_ratio; gdouble percentage; /* from 0 to 1 */ gdouble duration; /* in seconds */ gdouble rate_of_change; /* in source units per second */ } value; gpointer data; gsize data_size; }; struct _FieldData { gboolean active; GtkCheckMenuItem *menu_item; GtkLabel *value_label; }; struct _GroupData { gint n_fields; gint n_meter_values; gboolean active; gdouble limit; GimpToggleAction *action; GtkExpander *expander; GtkLabel *header_values_label; GtkButton *menu_button; GtkMenu *menu; GimpMeter *meter; GtkTable *table; FieldData *fields; }; struct _GimpDashboardPrivate { Gimp *gimp; VariableData variables[N_VARIABLES]; GroupData groups[N_GROUPS]; GThread *thread; GMutex mutex; GCond cond; gboolean quit; gboolean update_now; gint update_idle_id; gint low_swap_space_idle_id; GimpDashboardUpdateInteval update_interval; GimpDashboardHistoryDuration history_duration; gboolean low_swap_space_warning; GOutputStream *log_output; GError *log_error; GimpDashboardLogParams log_params; gint64 log_start_time; gint log_n_samples; gint log_n_markers; VariableData log_variables[N_VARIABLES]; GimpBacktrace *log_backtrace; GHashTable *log_addresses; GimpLogHandler log_log_handler; GimpHighlightableButton *log_record_button; GtkLabel *log_add_marker_label; }; /* local function prototypes */ static void gimp_dashboard_docked_iface_init (GimpDockedInterface *iface); static void gimp_dashboard_constructed (GObject *object); static void gimp_dashboard_dispose (GObject *object); static void gimp_dashboard_finalize (GObject *object); static void gimp_dashboard_map (GtkWidget *widget); static void gimp_dashboard_unmap (GtkWidget *widget); static void gimp_dashboard_set_aux_info (GimpDocked *docked, GList *aux_info); static GList * gimp_dashboard_get_aux_info (GimpDocked *docked); static gboolean gimp_dashboard_group_expander_button_press (GimpDashboard *dashboard, GdkEventButton *bevent, GtkWidget *widget); static void gimp_dashboard_group_action_toggled (GimpDashboard *dashboard, GimpToggleAction *action); static void gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard, GtkCheckMenuItem *item); static gpointer gimp_dashboard_sample (GimpDashboard *dashboard); static gboolean gimp_dashboard_update (GimpDashboard *dashboard); static gboolean gimp_dashboard_low_swap_space (GimpDashboard *dashboard); static void gimp_dashboard_sample_function (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard, Variable variable); #ifdef HAVE_CPU_GROUP static void gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard, Variable variable); #endif /* HAVE_CPU_GROUP */ #ifdef HAVE_MEMORY_GROUP static void gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, Variable variable); static void gimp_dashboard_sample_memory_size (GimpDashboard *dashboard, Variable variable); #endif /* HAVE_MEMORY_GROUP */ static void gimp_dashboard_sample_object (GimpDashboard *dashboard, GObject *object, Variable variable); static void gimp_dashboard_group_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data); static void gimp_dashboard_update_groups (GimpDashboard *dashboard); static void gimp_dashboard_update_group (GimpDashboard *dashboard, Group group); static void gimp_dashboard_update_group_values (GimpDashboard *dashboard, Group group); static void gimp_dashboard_group_set_active (GimpDashboard *dashboard, Group group, gboolean active); static void gimp_dashboard_field_set_active (GimpDashboard *dashboard, Group group, 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, Variable variable, gsize size); static gboolean gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard, Variable variable); static gdouble gimp_dashboard_variable_to_double (GimpDashboard *dashboard, Variable variable); static gchar * gimp_dashboard_field_to_string (GimpDashboard *dashboard, Group group, 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, gboolean include_current_thread); static void gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard, const gchar *description); 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 (GimpDashboard *dashboard, guintptr *addresses, gint n_addresses, GimpAsync *async); static void gimp_dashboard_log_write_global_address_map (GimpAsync *async, GimpDashboard *dashboard); static void gimp_dashboard_log_log_func (const gchar *log_domain, GLogLevelFlags log_levels, const gchar *message, GimpDashboard *dashboard); static gboolean gimp_dashboard_field_use_meter_underlay (Group group, gint field); static gchar * gimp_dashboard_format_rate_of_change (const gchar *value); static gchar * gimp_dashboard_format_value (VariableType type, gdouble value); static void gimp_dashboard_label_set_text (GtkLabel *label, const gchar *text); /* static variables */ static const VariableInfo variables[] = { /* cache variables */ [VARIABLE_CACHE_OCCUPIED] = { .name = "cache-occupied", .title = NC_("dashboard-variable", "Occupied"), .description = N_("Tile cache occupied size"), .type = VARIABLE_TYPE_SIZE, .color = {0.3, 0.6, 0.3, 1.0}, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "tile-cache-total" }, [VARIABLE_CACHE_MAXIMUM] = { .name = "cache-maximum", .title = NC_("dashboard-variable", "Maximum"), .description = N_("Maximal tile cache occupied size"), .type = VARIABLE_TYPE_SIZE, .color = {0.3, 0.7, 0.8, 1.0}, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "tile-cache-total-max" }, [VARIABLE_CACHE_LIMIT] = { .name = "cache-limit", .title = NC_("dashboard-variable", "Limit"), .description = N_("Tile cache size limit"), .type = VARIABLE_TYPE_SIZE, .sample_func = gimp_dashboard_sample_gegl_config, .data = "tile-cache-size" }, [VARIABLE_CACHE_COMPRESSION] = { .name = "cache-compression", .title = NC_("dashboard-variable", "Compression"), .description = N_("Tile cache compression ratio"), .type = VARIABLE_TYPE_SIZE_RATIO, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "tile-cache-total\0" "tile-cache-total-uncompressed" }, [VARIABLE_CACHE_HIT_MISS] = { .name = "cache-hit-miss", .title = NC_("dashboard-variable", "Hit/Miss"), .description = N_("Tile cache hit/miss ratio"), .type = VARIABLE_TYPE_INT_RATIO, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "tile-cache-hits\0" "tile-cache-misses" }, /* swap variables */ [VARIABLE_SWAP_OCCUPIED] = { .name = "swap-occupied", .title = NC_("dashboard-variable", "Occupied"), .description = N_("Swap file occupied size"), .type = VARIABLE_TYPE_SIZE, .color = {0.8, 0.2, 0.2, 1.0}, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "swap-total" }, [VARIABLE_SWAP_SIZE] = { .name = "swap-size", .title = NC_("dashboard-variable", "Size"), .description = N_("Swap file size"), .type = VARIABLE_TYPE_SIZE, .color = {0.8, 0.6, 0.4, 1.0}, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "swap-file-size" }, [VARIABLE_SWAP_LIMIT] = { .name = "swap-limit", .title = NC_("dashboard-variable", "Limit"), .description = N_("Swap file size limit"), .type = VARIABLE_TYPE_SIZE, .sample_func = gimp_dashboard_sample_swap_limit, }, [VARIABLE_SWAP_QUEUED] = { .name = "swap-queued", .title = NC_("dashboard-variable", "Queued"), .description = N_("Size of data queued for writing to the swap"), .type = VARIABLE_TYPE_SIZE, .color = {0.8, 0.8, 0.2, 0.5}, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "swap-queued-total" }, [VARIABLE_SWAP_QUEUE_STALLS] = { .name = "swap-queue-stalls", .title = NC_("dashboard-variable", "Queue stalls"), .description = N_("Number of times the writing to the swap has been " "stalled, due to a full queue"), .type = VARIABLE_TYPE_INTEGER, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "swap-queue-stalls" }, [VARIABLE_SWAP_QUEUE_FULL] = { .name = "swap-queue-full", .title = NC_("dashboard-variable", "Queue full"), .description = N_("Whether the swap queue is full"), .type = VARIABLE_TYPE_BOOLEAN, .sample_func = gimp_dashboard_sample_variable_changed, .data = GINT_TO_POINTER (VARIABLE_SWAP_QUEUE_STALLS) }, [VARIABLE_SWAP_READ] = { .name = "swap-read", /* Translators: this is the past participle form of "read", * as in "total amount of data read from the swap". */ .title = NC_("dashboard-variable", "Read"), .description = N_("Total amount of data read from the swap"), .type = VARIABLE_TYPE_SIZE, .color = {0.2, 0.4, 1.0, 0.4}, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "swap-read-total" }, [VARIABLE_SWAP_READ_THROUGHPUT] = { .name = "swap-read-throughput", .title = NC_("dashboard-variable", "Read throughput"), .description = N_("The rate at which data is read from the swap"), .type = VARIABLE_TYPE_RATE_OF_CHANGE, .color = {0.2, 0.4, 1.0, 1.0}, .sample_func = gimp_dashboard_sample_variable_rate_of_change, .data = GINT_TO_POINTER (VARIABLE_SWAP_READ) }, [VARIABLE_SWAP_WRITTEN] = { .name = "swap-written", /* Translators: this is the past participle form of "write", * as in "total amount of data written to the swap". */ .title = NC_("dashboard-variable", "Written"), .description = N_("Total amount of data written to the swap"), .type = VARIABLE_TYPE_SIZE, .color = {0.8, 0.3, 0.2, 0.4}, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "swap-write-total" }, [VARIABLE_SWAP_WRITE_THROUGHPUT] = { .name = "swap-write-throughput", .title = NC_("dashboard-variable", "Write throughput"), .description = N_("The rate at which data is written to the swap"), .type = VARIABLE_TYPE_RATE_OF_CHANGE, .color = {0.8, 0.3, 0.2, 1.0}, .sample_func = gimp_dashboard_sample_variable_rate_of_change, .data = GINT_TO_POINTER (VARIABLE_SWAP_WRITTEN) }, [VARIABLE_SWAP_COMPRESSION] = { .name = "swap-compression", .title = NC_("dashboard-variable", "Compression"), .description = N_("Swap compression ratio"), .type = VARIABLE_TYPE_SIZE_RATIO, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "swap-total\0" "swap-total-uncompressed" }, #ifdef HAVE_CPU_GROUP /* cpu variables */ [VARIABLE_CPU_USAGE] = { .name = "cpu-usage", .title = NC_("dashboard-variable", "Usage"), .description = N_("Total CPU usage"), .type = VARIABLE_TYPE_PERCENTAGE, .color = {0.8, 0.7, 0.2, 1.0}, .sample_func = gimp_dashboard_sample_cpu_usage }, [VARIABLE_CPU_ACTIVE] = { .name = "cpu-active", .title = NC_("dashboard-variable", "Active"), .description = N_("Whether the CPU is active"), .type = VARIABLE_TYPE_BOOLEAN, .color = {0.9, 0.8, 0.3, 1.0}, .sample_func = gimp_dashboard_sample_cpu_active }, [VARIABLE_CPU_ACTIVE_TIME] = { .name = "cpu-active-time", .title = NC_("dashboard-variable", "Active"), .description = N_("Total amount of time the CPU has been active"), .type = VARIABLE_TYPE_DURATION, .color = {0.8, 0.7, 0.2, 0.4}, .sample_func = gimp_dashboard_sample_cpu_active_time }, #endif /* HAVE_CPU_GROUP */ #ifdef HAVE_MEMORY_GROUP /* memory variables */ [VARIABLE_MEMORY_USED] = { .name = "memory-used", .title = NC_("dashboard-variable", "Used"), .description = N_("Amount of memory used by the process"), .type = VARIABLE_TYPE_SIZE, .color = {0.8, 0.5, 0.2, 1.0}, .sample_func = gimp_dashboard_sample_memory_used }, [VARIABLE_MEMORY_AVAILABLE] = { .name = "memory-available", .title = NC_("dashboard-variable", "Available"), .description = N_("Amount of available physical memory"), .type = VARIABLE_TYPE_SIZE, .color = {0.8, 0.5, 0.2, 0.4}, .sample_func = gimp_dashboard_sample_memory_available }, [VARIABLE_MEMORY_SIZE] = { .name = "memory-size", .title = NC_("dashboard-variable", "Size"), .description = N_("Physical memory size"), .type = VARIABLE_TYPE_SIZE, .sample_func = gimp_dashboard_sample_memory_size }, #endif /* HAVE_MEMORY_GROUP */ /* misc variables */ [VARIABLE_MIPMAPED] = { .name = "mipmapped", .title = NC_("dashboard-variable", "Mipmapped"), .description = N_("Total size of processed mipmapped data"), .type = VARIABLE_TYPE_SIZE, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "zoom-total" }, [VARIABLE_ASSIGNED_THREADS] = { .name = "assigned-threads", .title = NC_("dashboard-variable", "Assigned"), .description = N_("Number of assigned worker threads"), .type = VARIABLE_TYPE_INTEGER, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "assigned-threads" }, [VARIABLE_ACTIVE_THREADS] = { .name = "active-threads", .title = NC_("dashboard-variable", "Active"), .description = N_("Number of active worker threads"), .type = VARIABLE_TYPE_INTEGER, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "active-threads" }, [VARIABLE_ASYNC_RUNNING] = { .name = "async-running", .title = NC_("dashboard-variable", "Async"), .description = N_("Number of ongoing asynchronous operations"), .type = VARIABLE_TYPE_INTEGER, .sample_func = gimp_dashboard_sample_function, .data = gimp_async_get_n_running }, [VARIABLE_TILE_ALLOC_TOTAL] = { .name = "tile-alloc-total", .title = NC_("dashboard-variable", "Tile"), .description = N_("Total size of tile memory"), .type = VARIABLE_TYPE_SIZE, .color = {0.3, 0.3, 1.0, 1.0}, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "tile-alloc-total" }, [VARIABLE_SCRATCH_TOTAL] = { .name = "scratch-total", .title = NC_("dashboard-variable", "Scratch"), .description = N_("Total size of scratch memory"), .type = VARIABLE_TYPE_SIZE, .sample_func = gimp_dashboard_sample_gegl_stats, .data = "scratch-total" }, [VARIABLE_TEMP_BUF_TOTAL] = { .name = "temp-buf-total", /* Translators: "TempBuf" is a technical term referring to an internal * GIMP data structure. It's probably OK to leave it untranslated. */ .title = NC_("dashboard-variable", "TempBuf"), .description = N_("Total size of temporary buffers"), .type = VARIABLE_TYPE_SIZE, .sample_func = gimp_dashboard_sample_function, .data = gimp_temp_buf_get_total_memsize } }; static const GroupInfo groups[] = { /* cache group */ [GROUP_CACHE] = { .name = "cache", .title = NC_("dashboard-group", "Cache"), .description = N_("In-memory tile cache"), .default_active = TRUE, .default_expanded = TRUE, .has_meter = TRUE, .meter_limit = VARIABLE_CACHE_LIMIT, .fields = (const FieldInfo[]) { { .variable = VARIABLE_CACHE_OCCUPIED, .default_active = TRUE, .show_in_header = TRUE, .meter_value = 2 }, { .variable = VARIABLE_CACHE_MAXIMUM, .default_active = FALSE, .meter_value = 1 }, { .variable = VARIABLE_CACHE_LIMIT, .default_active = TRUE }, { VARIABLE_SEPARATOR }, { .variable = VARIABLE_CACHE_COMPRESSION, .default_active = FALSE }, { .variable = VARIABLE_CACHE_HIT_MISS, .default_active = FALSE }, {} } }, /* swap group */ [GROUP_SWAP] = { .name = "swap", .title = NC_("dashboard-group", "Swap"), .description = N_("On-disk tile swap"), .default_active = TRUE, .default_expanded = TRUE, .has_meter = TRUE, .meter_limit = VARIABLE_SWAP_LIMIT, .meter_led = (const Variable[]) { VARIABLE_SWAP_QUEUE_FULL, VARIABLE_SWAP_READ_THROUGHPUT, VARIABLE_SWAP_WRITE_THROUGHPUT, VARIABLE_NONE }, .fields = (const FieldInfo[]) { { .variable = VARIABLE_SWAP_OCCUPIED, .default_active = TRUE, .show_in_header = TRUE, .meter_value = 5 }, { .variable = VARIABLE_SWAP_SIZE, .default_active = TRUE, .meter_value = 4 }, { .variable = VARIABLE_SWAP_LIMIT, .default_active = TRUE }, { VARIABLE_SEPARATOR }, { .variable = VARIABLE_SWAP_QUEUED, .default_active = FALSE, .meter_variable = VARIABLE_SWAP_QUEUE_FULL, .meter_value = 3 }, { VARIABLE_SEPARATOR }, { .variable = VARIABLE_SWAP_READ, .default_active = FALSE, .meter_variable = VARIABLE_SWAP_READ_THROUGHPUT, .meter_value = 2 }, { .variable = VARIABLE_SWAP_WRITTEN, .default_active = FALSE, .meter_variable = VARIABLE_SWAP_WRITE_THROUGHPUT, .meter_value = 1 }, { VARIABLE_SEPARATOR }, { .variable = VARIABLE_SWAP_COMPRESSION, .default_active = FALSE }, {} } }, #ifdef HAVE_CPU_GROUP /* cpu group */ [GROUP_CPU] = { .name = "cpu", .title = NC_("dashboard-group", "CPU"), .description = N_("CPU usage"), .default_active = TRUE, .default_expanded = FALSE, .has_meter = TRUE, .meter_led = (const Variable[]) { VARIABLE_CPU_ACTIVE, VARIABLE_NONE }, .fields = (const FieldInfo[]) { { .variable = VARIABLE_CPU_USAGE, .default_active = TRUE, .show_in_header = TRUE, .meter_value = 2 }, { VARIABLE_SEPARATOR }, { .variable = VARIABLE_CPU_ACTIVE_TIME, .default_active = FALSE, .meter_variable = VARIABLE_CPU_ACTIVE, .meter_value = 1 }, {} } }, #endif /* HAVE_CPU_GROUP */ #ifdef HAVE_MEMORY_GROUP /* memory group */ [GROUP_MEMORY] = { .name = "memory", .title = NC_("dashboard-group", "Memory"), .description = N_("Memory usage"), .default_active = TRUE, .default_expanded = FALSE, .has_meter = TRUE, .meter_limit = VARIABLE_MEMORY_SIZE, .fields = (const FieldInfo[]) { { .variable = VARIABLE_CACHE_OCCUPIED, .title = NC_("dashboard-variable", "Cache"), .default_active = FALSE, .meter_value = 4 }, { .variable = VARIABLE_TILE_ALLOC_TOTAL, .default_active = FALSE, .meter_value = 3 }, { VARIABLE_SEPARATOR }, { .variable = VARIABLE_MEMORY_USED, .default_active = TRUE, .show_in_header = TRUE, .meter_value = 2, .meter_cumulative = TRUE }, { .variable = VARIABLE_MEMORY_AVAILABLE, .default_active = TRUE, .meter_value = 1, .meter_cumulative = TRUE }, { .variable = VARIABLE_MEMORY_SIZE, .default_active = TRUE }, {} } }, #endif /* HAVE_MEMORY_GROUP */ /* misc group */ [GROUP_MISC] = { .name = "misc", .title = NC_("dashboard-group", "Misc"), .description = N_("Miscellaneous information"), .default_active = FALSE, .default_expanded = FALSE, .has_meter = FALSE, .fields = (const FieldInfo[]) { { .variable = VARIABLE_MIPMAPED, .default_active = TRUE }, { .variable = VARIABLE_ASSIGNED_THREADS, .default_active = TRUE }, { .variable = VARIABLE_ACTIVE_THREADS, .default_active = TRUE }, { .variable = VARIABLE_ASYNC_RUNNING, .default_active = TRUE }, { .variable = VARIABLE_TILE_ALLOC_TOTAL, .default_active = TRUE }, { .variable = VARIABLE_SCRATCH_TOTAL, .default_active = TRUE }, { .variable = VARIABLE_TEMP_BUF_TOTAL, .default_active = TRUE }, {} } }, }; G_DEFINE_TYPE_WITH_CODE (GimpDashboard, gimp_dashboard, GIMP_TYPE_EDITOR, G_ADD_PRIVATE (GimpDashboard) G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED, gimp_dashboard_docked_iface_init)) #define parent_class gimp_dashboard_parent_class static GimpDockedInterface *parent_docked_iface = NULL; /* private functions */ static void gimp_dashboard_class_init (GimpDashboardClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = gimp_dashboard_constructed; object_class->dispose = gimp_dashboard_dispose; object_class->finalize = gimp_dashboard_finalize; widget_class->map = gimp_dashboard_map; widget_class->unmap = gimp_dashboard_unmap; } static void gimp_dashboard_init (GimpDashboard *dashboard) { GimpDashboardPrivate *priv; GtkWidget *box; GtkWidget *scrolled_window; GtkWidget *viewport; GtkWidget *vbox; GtkWidget *expander; GtkWidget *hbox; GtkWidget *button; GtkWidget *image; GtkWidget *menu; GtkWidget *item; GtkWidget *frame; GtkWidget *vbox2; GtkWidget *meter; GtkWidget *table; GtkWidget *label; gint content_spacing; Group group; gint field; priv = dashboard->priv = gimp_dashboard_get_instance_private (dashboard); g_mutex_init (&priv->mutex); g_cond_init (&priv->cond); priv->update_interval = DEFAULT_UPDATE_INTERVAL; priv->history_duration = DEFAULT_HISTORY_DURATION; priv->low_swap_space_warning = DEFAULT_LOW_SWAP_SPACE_WARNING; gtk_widget_style_get (GTK_WIDGET (dashboard), "content-spacing", &content_spacing, NULL); /* we put the dashboard inside an event box, so that it gets its own window, * which reduces the overhead of updating the ui, since it gets updated * frequently. unfortunately, this means that the dashboard's background * color may be a bit off for some themes. */ box = gtk_event_box_new (); gtk_box_pack_start (GTK_BOX (dashboard), box, TRUE, TRUE, 0); gtk_widget_show (box); /* scrolled window */ scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (box), scrolled_window); gtk_widget_show (scrolled_window); /* viewport */ viewport = gtk_viewport_new ( gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled_window)), gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window))); gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); gtk_container_add (GTK_CONTAINER (scrolled_window), viewport); gtk_widget_show (viewport); /* main vbox */ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing); gtk_container_add (GTK_CONTAINER (viewport), vbox); gtk_widget_show (vbox); /* construct the groups */ for (group = FIRST_GROUP; group < N_GROUPS; group++) { const GroupInfo *group_info = &groups[group]; GroupData *group_data = &priv->groups[group]; group_data->n_fields = 0; group_data->n_meter_values = 0; for (field = 0; group_info->fields[field].variable; field++) { const FieldInfo *field_info = &group_info->fields[field]; group_data->n_fields++; group_data->n_meter_values = MAX (group_data->n_meter_values, field_info->meter_value); } group_data->fields = g_new0 (FieldData, group_data->n_fields); /* group expander */ expander = gtk_expander_new (NULL); group_data->expander = GTK_EXPANDER (expander); gtk_expander_set_expanded (GTK_EXPANDER (expander), group_info->default_expanded); gtk_expander_set_label_fill (GTK_EXPANDER (expander), TRUE); gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0); g_object_set_data (G_OBJECT (expander), "gimp-dashboard-group", GINT_TO_POINTER (group)); g_signal_connect_swapped (expander, "button-press-event", G_CALLBACK (gimp_dashboard_group_expander_button_press), dashboard); /* group expander label box */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gimp_help_set_help_data (hbox, g_dgettext (NULL, group_info->description), NULL); gtk_expander_set_label_widget (GTK_EXPANDER (expander), hbox); gtk_widget_show (hbox); /* group expander label */ label = gtk_label_new (g_dpgettext2 (NULL, "dashboard-group", group_info->title)); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gimp_label_set_attributes (GTK_LABEL (label), PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, -1); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); /* group expander values label */ label = gtk_label_new (NULL); group_data->header_values_label = GTK_LABEL (label); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 4); g_object_bind_property (expander, "expanded", label, "visible", G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); /* group expander menu button */ button = gtk_button_new (); group_data->menu_button = GTK_BUTTON (button); gimp_help_set_help_data (button, _("Select fields"), NULL); gtk_widget_set_can_focus (button, FALSE); gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0); gtk_widget_show (button); image = gtk_image_new_from_icon_name (GIMP_ICON_MENU_LEFT, GTK_ICON_SIZE_MENU); gtk_image_set_pixel_size (GTK_IMAGE (image), 12); gtk_image_set_from_icon_name (GTK_IMAGE (image), GIMP_ICON_MENU_LEFT, GTK_ICON_SIZE_MENU); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_show (image); /* group menu */ menu = gtk_menu_new (); group_data->menu = GTK_MENU (menu); gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL); for (field = 0; field < group_data->n_fields; field++) { const FieldInfo *field_info = &group_info->fields[field]; FieldData *field_data = &group_data->fields[field]; if (field_info->variable != VARIABLE_SEPARATOR) { const VariableInfo *variable_info = &variables[field_info->variable]; item = gtk_check_menu_item_new_with_label ( g_dpgettext2 (NULL, "dashboard-variable", field_info->title ? field_info->title : variable_info->title)); field_data->menu_item = GTK_CHECK_MENU_ITEM (item); gimp_help_set_help_data (item, g_dgettext (NULL, variable_info->description), NULL); g_object_set_data (G_OBJECT (item), "gimp-dashboard-group", GINT_TO_POINTER (group)); g_object_set_data (G_OBJECT (item), "gimp-dashboard-field", GINT_TO_POINTER (field)); g_signal_connect_swapped (item, "toggled", G_CALLBACK (gimp_dashboard_field_menu_item_toggled), dashboard); gimp_dashboard_field_set_active (dashboard, group, field, field_info->default_active); } else { item = gtk_separator_menu_item_new (); } gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_widget_show (item); } /* group frame */ frame = gimp_frame_new (NULL); gtk_container_add (GTK_CONTAINER (expander), frame); gtk_widget_show (frame); /* group vbox */ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing); gtk_container_add (GTK_CONTAINER (frame), vbox2); gtk_widget_show (vbox2); /* group meter */ if (group_info->has_meter) { meter = gimp_meter_new (group_data->n_meter_values); group_data->meter = GIMP_METER (meter); gimp_help_set_help_data (meter, g_dgettext (NULL, group_info->description), NULL); gimp_meter_set_history_resolution (GIMP_METER (meter), priv->update_interval / 1000.0); gimp_meter_set_history_duration (GIMP_METER (meter), priv->history_duration / 1000.0); gtk_box_pack_start (GTK_BOX (vbox2), meter, FALSE, FALSE, 0); gtk_widget_show (meter); for (field = 0; field < group_data->n_fields; field++) { const FieldInfo *field_info = &group_info->fields[field]; if (field_info->meter_value) { const VariableInfo *variable_info = &variables[field_info->variable]; gimp_meter_set_value_color (GIMP_METER (meter), field_info->meter_value - 1, &variable_info->color); if (gimp_dashboard_field_use_meter_underlay (group, field)) { gimp_meter_set_value_show_in_gauge (GIMP_METER (meter), field_info->meter_value - 1, FALSE); gimp_meter_set_value_interpolation (GIMP_METER (meter), field_info->meter_value - 1, GIMP_INTERPOLATION_NONE); } } } } /* group table */ table = gtk_table_new (1, 1, FALSE); group_data->table = GTK_TABLE (table); gtk_table_set_row_spacings (GTK_TABLE (table), content_spacing); gtk_table_set_col_spacings (GTK_TABLE (table), 4); gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0); gtk_widget_show (table); gimp_dashboard_group_set_active (dashboard, group, group_info->default_active); gimp_dashboard_update_group (dashboard, group); } /* sampler thread * * we use a separate thread for sampling, so that data is sampled even when * the main thread is busy */ priv->thread = g_thread_new ("dashboard", (GThreadFunc) gimp_dashboard_sample, dashboard); } static void gimp_dashboard_docked_iface_init (GimpDockedInterface *iface) { parent_docked_iface = g_type_interface_peek_parent (iface); if (! parent_docked_iface) parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED); iface->set_aux_info = gimp_dashboard_set_aux_info; iface->get_aux_info = gimp_dashboard_get_aux_info; } static void gimp_dashboard_constructed (GObject *object) { GimpDashboard *dashboard = GIMP_DASHBOARD (object); GimpDashboardPrivate *priv = dashboard->priv; GimpUIManager *ui_manager; GimpActionGroup *action_group; GimpAction *action; GtkWidget *button; GtkWidget *alignment; GtkWidget *box; GtkWidget *image; GtkWidget *label; Group group; G_OBJECT_CLASS (parent_class)->constructed (object); ui_manager = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard)); action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard"); /* group actions */ for (group = FIRST_GROUP; group < N_GROUPS; group++) { const GroupInfo *group_info = &groups[group]; GroupData *group_data = &priv->groups[group]; GimpToggleActionEntry entry = {}; entry.name = g_strdup_printf ("dashboard-group-%s", group_info->name); entry.label = g_dpgettext2 (NULL, "dashboard-group", group_info->title); entry.tooltip = g_dgettext (NULL, group_info->description); entry.help_id = GIMP_HELP_DASHBOARD_GROUPS; entry.is_active = group_data->active; gimp_action_group_add_toggle_actions (action_group, "dashboard-groups", &entry, 1); action = gimp_ui_manager_find_action (ui_manager, "dashboard", entry.name); group_data->action = GIMP_TOGGLE_ACTION (action); g_object_set_data (G_OBJECT (action), "gimp-dashboard-group", GINT_TO_POINTER (group)); g_signal_connect_swapped (action, "toggled", G_CALLBACK (gimp_dashboard_group_action_toggled), dashboard); g_free ((gpointer) entry.name); } button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard", "dashboard-log-record", NULL); priv->log_record_button = GIMP_HIGHLIGHTABLE_BUTTON (button); gimp_highlightable_button_set_highlight_color ( GIMP_HIGHLIGHTABLE_BUTTON (button), GIMP_HIGHLIGHTABLE_BUTTON_COLOR_AFFIRMATIVE); 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 = gimp_action_group_get_action (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); alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0); gtk_container_add (GTK_CONTAINER (button), alignment); gtk_widget_show (alignment); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_container_add (GTK_CONTAINER (alignment), box); 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 = gimp_action_group_get_action (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 gimp_dashboard_dispose (GObject *object) { GimpDashboard *dashboard = GIMP_DASHBOARD (object); GimpDashboardPrivate *priv = dashboard->priv; if (priv->thread) { g_mutex_lock (&priv->mutex); priv->quit = TRUE; g_cond_signal (&priv->cond); g_mutex_unlock (&priv->mutex); g_clear_pointer (&priv->thread, g_thread_join); } if (priv->update_idle_id) { g_source_remove (priv->update_idle_id); priv->update_idle_id = 0; } if (priv->low_swap_space_idle_id) { g_source_remove (priv->low_swap_space_idle_id); 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); } static void gimp_dashboard_finalize (GObject *object) { GimpDashboard *dashboard = GIMP_DASHBOARD (object); GimpDashboardPrivate *priv = dashboard->priv; gint i; for (i = FIRST_GROUP; i < N_GROUPS; i++) g_free (priv->groups[i].fields); g_mutex_clear (&priv->mutex); g_cond_clear (&priv->cond); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gimp_dashboard_map (GtkWidget *widget) { GimpDashboard *dashboard = GIMP_DASHBOARD (widget); GTK_WIDGET_CLASS (parent_class)->map (widget); gimp_dashboard_update (dashboard); } static void gimp_dashboard_unmap (GtkWidget *widget) { GimpDashboard *dashboard = GIMP_DASHBOARD (widget); GimpDashboardPrivate *priv = dashboard->priv; g_mutex_lock (&priv->mutex); if (priv->update_idle_id) { g_source_remove (priv->update_idle_id); priv->update_idle_id = 0; } g_mutex_unlock (&priv->mutex); GTK_WIDGET_CLASS (parent_class)->unmap (widget); } #define AUX_INFO_UPDATE_INTERVAL "update-interval" #define AUX_INFO_HISTORY_DURATION "history-duration" #define AUX_INFO_LOW_SWAP_SPACE_WARNING "low-swap-space-warning" static void gimp_dashboard_set_aux_info (GimpDocked *docked, GList *aux_info) { GimpDashboard *dashboard = GIMP_DASHBOARD (docked); GimpDashboardPrivate *priv = dashboard->priv; gchar *name; GList *list; parent_docked_iface->set_aux_info (docked, aux_info); for (list = aux_info; list; list = g_list_next (list)) { GimpSessionInfoAux *aux = list->data; if (! strcmp (aux->name, AUX_INFO_UPDATE_INTERVAL)) { gint value = atoi (aux->value); GimpDashboardUpdateInteval update_interval; for (update_interval = GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC; update_interval < value && update_interval < GIMP_DASHBOARD_UPDATE_INTERVAL_4_SEC; update_interval *= 2); gimp_dashboard_set_update_interval (dashboard, update_interval); } else if (! strcmp (aux->name, AUX_INFO_HISTORY_DURATION)) { gint value = atoi (aux->value); GimpDashboardHistoryDuration history_duration; for (history_duration = GIMP_DASHBOARD_HISTORY_DURATION_15_SEC; history_duration < value && history_duration < GIMP_DASHBOARD_HISTORY_DURATION_240_SEC; history_duration *= 2); gimp_dashboard_set_history_duration (dashboard, history_duration); } else if (! strcmp (aux->name, AUX_INFO_LOW_SWAP_SPACE_WARNING)) { gimp_dashboard_set_low_swap_space_warning (dashboard, ! strcmp (aux->value, "yes")); } else { Group group; gint field; for (group = FIRST_GROUP; group < N_GROUPS; group++) { const GroupInfo *group_info = &groups[group]; GroupData *group_data = &priv->groups[group]; name = g_strdup_printf ("%s-active", group_info->name); if (! strcmp (aux->name, name)) { gboolean active = ! strcmp (aux->value, "yes"); gimp_dashboard_group_set_active (dashboard, group, active); g_free (name); goto next_aux_info; } g_free (name); name = g_strdup_printf ("%s-expanded", group_info->name); if (! strcmp (aux->name, name)) { gboolean expanded = ! strcmp (aux->value, "yes"); gtk_expander_set_expanded (group_data->expander, expanded); g_free (name); goto next_aux_info; } g_free (name); for (field = 0; field < group_data->n_fields; field++) { const FieldInfo *field_info = &group_info->fields[field]; if (field_info->variable != VARIABLE_SEPARATOR) { const VariableInfo *variable_info = &variables[field_info->variable]; name = g_strdup_printf ("%s-%s-active", group_info->name, variable_info->name); if (! strcmp (aux->name, name)) { gboolean active = ! strcmp (aux->value, "yes"); gimp_dashboard_field_set_active (dashboard, group, field, active); g_free (name); goto next_aux_info; } g_free (name); } } } } next_aux_info: ; } gimp_dashboard_update_groups (dashboard); } static GList * gimp_dashboard_get_aux_info (GimpDocked *docked) { GimpDashboard *dashboard = GIMP_DASHBOARD (docked); GimpDashboardPrivate *priv = dashboard->priv; GList *aux_info; GimpSessionInfoAux *aux; gchar *name; gchar *value; Group group; gint field; aux_info = parent_docked_iface->get_aux_info (docked); if (priv->update_interval != DEFAULT_UPDATE_INTERVAL) { value = g_strdup_printf ("%d", priv->update_interval); aux = gimp_session_info_aux_new (AUX_INFO_UPDATE_INTERVAL, value); aux_info = g_list_append (aux_info, aux); g_free (value); } if (priv->history_duration != DEFAULT_HISTORY_DURATION) { value = g_strdup_printf ("%d", priv->history_duration); aux = gimp_session_info_aux_new (AUX_INFO_HISTORY_DURATION, value); aux_info = g_list_append (aux_info, aux); g_free (value); } if (priv->low_swap_space_warning != DEFAULT_LOW_SWAP_SPACE_WARNING) { value = priv->low_swap_space_warning ? "yes" : "no"; aux = gimp_session_info_aux_new (AUX_INFO_LOW_SWAP_SPACE_WARNING, value); aux_info = g_list_append (aux_info, aux); } for (group = FIRST_GROUP; group < N_GROUPS; group++) { const GroupInfo *group_info = &groups[group]; GroupData *group_data = &priv->groups[group]; gboolean active = group_data->active; gboolean expanded = gtk_expander_get_expanded (group_data->expander); if (active != group_info->default_active) { name = g_strdup_printf ("%s-active", group_info->name); value = active ? "yes" : "no"; aux = gimp_session_info_aux_new (name, value); aux_info = g_list_append (aux_info, aux); g_free (name); } if (expanded != group_info->default_expanded) { name = g_strdup_printf ("%s-expanded", group_info->name); value = expanded ? "yes" : "no"; aux = gimp_session_info_aux_new (name, value); aux_info = g_list_append (aux_info, aux); g_free (name); } for (field = 0; field < group_data->n_fields; field++) { const FieldInfo *field_info = &group_info->fields[field]; FieldData *field_data = &group_data->fields[field]; gboolean active = field_data->active; if (field_info->variable != VARIABLE_SEPARATOR) { const VariableInfo *variable_info = &variables[field_info->variable]; if (active != field_info->default_active) { name = g_strdup_printf ("%s-%s-active", group_info->name, variable_info->name); value = active ? "yes" : "no"; aux = gimp_session_info_aux_new (name, value); aux_info = g_list_append (aux_info, aux); g_free (name); } } } } return aux_info; } static gboolean gimp_dashboard_group_expander_button_press (GimpDashboard *dashboard, GdkEventButton *bevent, GtkWidget *widget) { GimpDashboardPrivate *priv = dashboard->priv; Group group; GroupData *group_data; GtkAllocation expander_allocation; GtkAllocation allocation; group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "gimp-dashboard-group")); group_data = &priv->groups[group]; gtk_widget_get_allocation (GTK_WIDGET (group_data->expander), &expander_allocation); gtk_widget_get_allocation (GTK_WIDGET (group_data->menu_button), &allocation); allocation.x -= expander_allocation.x; allocation.y -= expander_allocation.y; if (bevent->button == 1 && bevent->x >= allocation.x && bevent->x < allocation.x + allocation.width && bevent->y >= allocation.y && bevent->y < allocation.y + allocation.height) { gtk_menu_popup (group_data->menu, NULL, NULL, gimp_dashboard_group_menu_position, group_data->menu_button, bevent->button, bevent->time); return TRUE; } return FALSE; } static void gimp_dashboard_group_action_toggled (GimpDashboard *dashboard, GimpToggleAction *action) { GimpDashboardPrivate *priv = dashboard->priv; Group group; GroupData *group_data; group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action), "gimp-dashboard-group")); group_data = &priv->groups[group]; group_data->active = gimp_toggle_action_get_active (action); gimp_dashboard_update_group (dashboard, group); } static void gimp_dashboard_field_menu_item_toggled (GimpDashboard *dashboard, GtkCheckMenuItem *item) { GimpDashboardPrivate *priv = dashboard->priv; Group group; GroupData *group_data; gint field; FieldData *field_data; group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "gimp-dashboard-group")); group_data = &priv->groups[group]; field = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "gimp-dashboard-field")); field_data = &group_data->fields[field]; field_data->active = gtk_check_menu_item_get_active (item); gimp_dashboard_update_group (dashboard, group); } static gpointer gimp_dashboard_sample (GimpDashboard *dashboard) { 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); 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_params.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++) { const VariableInfo *variable_info = &variables[variable]; const VariableData *variable_data = &priv->variables[variable]; VariableData prev_variable_data = *variable_data; variable_info->sample_func (dashboard, variable); variables_changed = variables_changed || memcmp (variable_data, &prev_variable_data, sizeof (VariableData)); } /* log sample */ if (priv->log_output) gimp_dashboard_log_sample (dashboard, variables_changed, FALSE); /* update gui */ if (priv->update_now || ! priv->log_output || time - last_update_time >= update_interval) { /* add samples to meters */ for (group = FIRST_GROUP; group < N_GROUPS; group++) { 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++) { const FieldInfo *field_info = &group_info->fields[field]; if (field_info->meter_value) { gdouble value; 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) { 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; last_update_time = time; } last_sample_time = time; } } g_mutex_unlock (&priv->mutex); return NULL; } static gboolean gimp_dashboard_update (GimpDashboard *dashboard) { GimpDashboardPrivate *priv = dashboard->priv; Group group; g_mutex_lock (&priv->mutex); for (group = FIRST_GROUP; group < N_GROUPS; group++) gimp_dashboard_update_group_values (dashboard, group); priv->update_idle_id = 0; g_mutex_unlock (&priv->mutex); return G_SOURCE_REMOVE; } static gboolean gimp_dashboard_low_swap_space (GimpDashboard *dashboard) { GimpDashboardPrivate *priv = dashboard->priv; if (priv->gimp) { GdkScreen *screen; gint monitor; gint field; gtk_expander_set_expanded (priv->groups[GROUP_SWAP].expander, TRUE); for (field = 0; field < priv->groups[GROUP_SWAP].n_fields; field++) { const FieldInfo *field_info = &groups[GROUP_SWAP].fields[field]; if (field_info->variable == VARIABLE_SWAP_OCCUPIED || field_info->variable == VARIABLE_SWAP_LIMIT) { gimp_dashboard_field_set_active (dashboard, GROUP_SWAP, field, TRUE); } } gimp_dashboard_update_groups (dashboard); monitor = gimp_get_monitor_at_pointer (&screen); gimp_window_strategy_show_dockable_dialog ( GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (priv->gimp)), priv->gimp, gimp_dialog_factory_get_singleton (), screen, monitor, "gimp-dashboard"); g_mutex_lock (&priv->mutex); priv->low_swap_space_idle_id = 0; g_mutex_unlock (&priv->mutex); } return G_SOURCE_REMOVE; } static void gimp_dashboard_sample_function (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; const VariableInfo *variable_info = &variables[variable]; VariableData *variable_data = &priv->variables[variable]; #define CALL_FUNC(result_type) \ (((result_type (*) (void)) variable_info->data) ()) switch (variable_info->type) { case VARIABLE_TYPE_BOOLEAN: variable_data->value.boolean = CALL_FUNC (gboolean); break; case VARIABLE_TYPE_INTEGER: variable_data->value.integer = CALL_FUNC (gint); case VARIABLE_TYPE_SIZE: variable_data->value.size = CALL_FUNC (guint64); break; case VARIABLE_TYPE_PERCENTAGE: variable_data->value.percentage = CALL_FUNC (gdouble); break; case VARIABLE_TYPE_DURATION: variable_data->value.duration = CALL_FUNC (gdouble); break; case VARIABLE_TYPE_RATE_OF_CHANGE: variable_data->value.rate_of_change = CALL_FUNC (gdouble); break; case VARIABLE_TYPE_SIZE_RATIO: case VARIABLE_TYPE_INT_RATIO: g_return_if_reached (); break; } #undef CALL_FUNC variable_data->available = TRUE; } static void gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard, Variable variable) { gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_config ()), variable); } static void gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard, Variable variable) { gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_stats ()), variable); } static void gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; const VariableInfo *variable_info = &variables[variable]; VariableData *variable_data = &priv->variables[variable]; Variable var = GPOINTER_TO_INT (variable_info->data); const VariableData *var_data = &priv->variables[var]; gpointer prev_value = gimp_dashboard_variable_get_data ( dashboard, variable, sizeof (var_data->value)); if (var_data->available) { variable_data->available = TRUE; variable_data->value.boolean = memcmp (&var_data->value, prev_value, sizeof (var_data->value)) != 0; if (variable_data->value.boolean) memcpy (prev_value, &var_data->value, sizeof (var_data->value)); } else { variable_data->available = FALSE; } } static void gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard, Variable variable) { typedef struct { gint64 last_time; gboolean last_available; gdouble last_value; } Data; GimpDashboardPrivate *priv = dashboard->priv; const VariableInfo *variable_info = &variables[variable]; VariableData *variable_data = &priv->variables[variable]; Variable var = GPOINTER_TO_INT (variable_info->data); const VariableData *var_data = &priv->variables[var]; Data *data = gimp_dashboard_variable_get_data ( dashboard, variable, sizeof (Data)); gint64 time; time = g_get_monotonic_time (); if (time == data->last_time) return; variable_data->available = FALSE; if (var_data->available) { gdouble value = gimp_dashboard_variable_to_double (dashboard, var); if (data->last_available) { variable_data->available = TRUE; variable_data->value.rate_of_change = (value - data->last_value) * G_TIME_SPAN_SECOND / (time - data->last_time); } data->last_value = value; } data->last_time = time; data->last_available = var_data->available; } static void gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard, Variable variable) { typedef struct { guint64 free_space; gboolean has_free_space; gint64 last_check_time; } Data; GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; Data *data = gimp_dashboard_variable_get_data ( dashboard, variable, sizeof (Data)); gint64 time; /* we don't have a config option for limiting the swap size, so we simply * return the free space available on the filesystem containing the swap */ time = g_get_monotonic_time (); if (time - data->last_check_time >= G_TIME_SPAN_SECOND) { gchar *swap_dir; g_object_get (gegl_config (), "swap", &swap_dir, NULL); data->free_space = 0; data->has_free_space = FALSE; if (swap_dir) { GFile *file; GFileInfo *info; file = g_file_new_for_path (swap_dir); info = g_file_query_filesystem_info (file, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, NULL, NULL); if (info) { data->free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE); data->has_free_space = TRUE; g_object_unref (info); } g_object_unref (file); g_free (swap_dir); } data->last_check_time = time; } variable_data->available = data->has_free_space; if (data->has_free_space) { variable_data->value.size = data->free_space; if (priv->variables[VARIABLE_SWAP_SIZE].available) { /* the swap limit is the sum of free_space and swap_size, since the * swap itself occupies space in the filesystem */ variable_data->value.size += priv->variables[VARIABLE_SWAP_SIZE].value.size; } } } #ifdef HAVE_CPU_GROUP #ifdef HAVE_SYS_TIMES_H static void gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard, Variable variable) { typedef struct { clock_t prev_clock; clock_t prev_usage; } Data; GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; Data *data = gimp_dashboard_variable_get_data ( dashboard, variable, sizeof (Data)); clock_t curr_clock; clock_t curr_usage; struct tms tms; curr_clock = times (&tms); if (curr_clock == (clock_t) -1) { data->prev_clock = 0; variable_data->available = FALSE; return; } curr_usage = tms.tms_utime + tms.tms_stime; if (data->prev_clock && curr_clock != data->prev_clock) { variable_data->available = TRUE; variable_data->value.percentage = (gdouble) (curr_usage - data->prev_usage) / (curr_clock - data->prev_clock); variable_data->value.percentage /= g_get_num_processors (); } else { variable_data->available = FALSE; } data->prev_clock = curr_clock; data->prev_usage = curr_usage; } #elif defined (G_OS_WIN32) static void gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard, Variable variable) { typedef struct { guint64 prev_time; guint64 prev_usage; } Data; GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; Data *data = gimp_dashboard_variable_get_data ( dashboard, variable, sizeof (Data)); guint64 curr_time; guint64 curr_usage; FILETIME system_time; FILETIME process_creation_time; FILETIME process_exit_time; FILETIME process_kernel_time; FILETIME process_user_time; if (! GetProcessTimes (GetCurrentProcess (), &process_creation_time, &process_exit_time, &process_kernel_time, &process_user_time)) { data->prev_time = 0; variable_data->available = FALSE; return; } GetSystemTimeAsFileTime (&system_time); curr_time = ((guint64) system_time.dwHighDateTime << 32) | (guint64) system_time.dwLowDateTime; curr_usage = ((guint64) process_kernel_time.dwHighDateTime << 32) | (guint64) process_kernel_time.dwLowDateTime; curr_usage += ((guint64) process_user_time.dwHighDateTime << 32) | (guint64) process_user_time.dwLowDateTime; if (data->prev_time && curr_time != data->prev_time) { variable_data->available = TRUE; variable_data->value.percentage = (gdouble) (curr_usage - data->prev_usage) / (curr_time - data->prev_time); variable_data->value.percentage /= g_get_num_processors (); } else { variable_data->available = FALSE; } data->prev_time = curr_time; data->prev_usage = curr_usage; } #endif /* G_OS_WIN32 */ static void gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard, Variable variable) { typedef struct { gboolean active; } Data; GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; Data *data = gimp_dashboard_variable_get_data ( dashboard, variable, sizeof (Data)); gboolean active = FALSE; if (priv->variables[VARIABLE_CPU_USAGE].available) { if (! data->active) { active = priv->variables[VARIABLE_CPU_USAGE].value.percentage * g_get_num_processors () > CPU_ACTIVE_ON; } else { active = priv->variables[VARIABLE_CPU_USAGE].value.percentage * g_get_num_processors () > CPU_ACTIVE_OFF; } variable_data->available = TRUE; } else { variable_data->available = FALSE; } data->active = active; variable_data->value.boolean = active; } static void gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard, Variable variable) { typedef struct { gint64 prev_time; gint64 active_time; } Data; GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; Data *data = gimp_dashboard_variable_get_data ( dashboard, variable, sizeof (Data)); gint64 curr_time; curr_time = g_get_monotonic_time (); if (priv->variables[VARIABLE_CPU_ACTIVE].available) { gboolean active = priv->variables[VARIABLE_CPU_ACTIVE].value.boolean; if (active && data->prev_time) data->active_time += curr_time - data->prev_time; } data->prev_time = curr_time; variable_data->available = TRUE; variable_data->value.duration = data->active_time / 1000000.0; } #endif /* HAVE_CPU_GROUP */ #ifdef HAVE_MEMORY_GROUP #ifdef PLATFORM_OSX #if MAC_OS_X_VERSION_MAX_ALLOWED < 1080 #define MACH_TASK_BASIC_INFO_COUNT TASK_BASIC_INFO_COUNT #define mach_task_basic_info_data_t task_basic_info_data_t #define MACH_TASK_BASIC_INFO TASK_BASIC_INFO #define mach_task_basic_info task_basic_info #endif static void gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; variable_data->available = FALSE; #ifndef TASK_VM_INFO_REV0_COUNT /* phys_footprint added in REV1 */ struct mach_task_basic_info info; mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; if( task_info(mach_task_self (), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount ) != KERN_SUCCESS ) return; /* Can't access? */ variable_data->available = TRUE; variable_data->value.size = info.resident_size; #else task_vm_info_data_t info; mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT; if( task_info(mach_task_self (), TASK_VM_INFO, (task_info_t)&info, &infoCount ) != KERN_SUCCESS ) return; /* Can't access? */ variable_data->available = TRUE; variable_data->value.size = info.phys_footprint; #endif /* ! TASK_VM_INFO_REV0_COUNT */ } static void gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; vm_statistics_data_t info; mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT; variable_data->available = FALSE; if( host_statistics(mach_host_self (), HOST_VM_INFO, (host_info_t)&info, &infoCount ) != KERN_SUCCESS ) return; /* Can't access? */ variable_data->available = TRUE; variable_data->value.size = info.free_count * PAGE_SIZE; } #elif defined(G_OS_WIN32) static void gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; PROCESS_MEMORY_COUNTERS_EX pmc = {}; variable_data->available = FALSE; if (! GetProcessMemoryInfo (GetCurrentProcess (), (PPROCESS_MEMORY_COUNTERS) &pmc, sizeof (pmc)) || pmc.cb != sizeof (pmc)) { return; } variable_data->available = TRUE; variable_data->value.size = pmc.PrivateUsage; } static void gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; MEMORYSTATUSEX ms; variable_data->available = FALSE; ms.dwLength = sizeof (ms); if (! GlobalMemoryStatusEx (&ms)) return; variable_data->available = TRUE; variable_data->value.size = ms.ullAvailPhys; } #elif defined(__OpenBSD__) #include #include #include static void gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; struct rusage rusage; variable_data->available = FALSE; if (getrusage (RUSAGE_SELF, &rusage) == -1) return; variable_data->available = TRUE; variable_data->value.size = (guint64) (rusage.ru_maxrss * 1024); } static void gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; int mib[] = { CTL_HW, HW_PHYSMEM64 }; int64_t result; size_t sz = sizeof(int64_t); variable_data->available = FALSE; if (sysctl (mib, 2, &result, &sz, NULL, 0) == -1) return; variable_data->available = TRUE; variable_data->value.size = (guint64) result; } #else /* ! G_OS_WIN32 && ! PLATFORM_OSX */ static void gimp_dashboard_sample_memory_used (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; static gboolean initialized = FALSE; static long page_size; static gint fd = -1; gchar buffer[128]; gint size; unsigned long long resident; unsigned long long shared; if (! initialized) { page_size = sysconf (_SC_PAGE_SIZE); if (page_size > 0) fd = open ("/proc/self/statm", O_RDONLY); initialized = TRUE; } variable_data->available = FALSE; if (fd < 0) return; if (lseek (fd, 0, SEEK_SET)) return; size = read (fd, buffer, sizeof (buffer) - 1); if (size <= 0) return; buffer[size] = '\0'; if (sscanf (buffer, "%*u %llu %llu", &resident, &shared) != 2) return; variable_data->available = TRUE; variable_data->value.size = (guint64) (resident - shared) * page_size; } static void gimp_dashboard_sample_memory_available (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; static gboolean initialized = FALSE; static gint64 last_check_time = 0; static gint fd; static guint64 available; static gboolean has_available = FALSE; gint64 time; if (! initialized) { fd = open ("/proc/meminfo", O_RDONLY); initialized = TRUE; } variable_data->available = FALSE; if (fd < 0) return; /* we don't have a config option for limiting the swap size, so we simply * return the free space available on the filesystem containing the swap */ time = g_get_monotonic_time (); if (time - last_check_time >= G_TIME_SPAN_SECOND) { gchar buffer[512]; gint size; gchar *str; last_check_time = time; has_available = FALSE; if (lseek (fd, 0, SEEK_SET)) return; size = read (fd, buffer, sizeof (buffer) - 1); if (size <= 0) return; buffer[size] = '\0'; str = strstr (buffer, "MemAvailable:"); if (! str) return; available = strtoull (str + 13, &str, 0); if (! str) return; for (; *str; str++) { if (*str == 'k') { available <<= 10; break; } else if (*str == 'M') { available <<= 20; break; } } if (! *str) return; has_available = TRUE; } if (! has_available) return; variable_data->available = TRUE; variable_data->value.size = available; } #endif static void gimp_dashboard_sample_memory_size (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; variable_data->value.size = gimp_get_physical_memory_size (); variable_data->available = variable_data->value.size > 0; } #endif /* HAVE_MEMORY_GROUP */ static void gimp_dashboard_sample_object (GimpDashboard *dashboard, GObject *object, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; GObjectClass *klass = G_OBJECT_GET_CLASS (object); const VariableInfo *variable_info = &variables[variable]; VariableData *variable_data = &priv->variables[variable]; variable_data->available = FALSE; switch (variable_info->type) { case VARIABLE_TYPE_BOOLEAN: if (g_object_class_find_property (klass, variable_info->data)) { variable_data->available = TRUE; g_object_get (object, variable_info->data, &variable_data->value.boolean, NULL); } break; case VARIABLE_TYPE_INTEGER: if (g_object_class_find_property (klass, variable_info->data)) { variable_data->available = TRUE; g_object_get (object, variable_info->data, &variable_data->value.integer, NULL); } break; case VARIABLE_TYPE_SIZE: if (g_object_class_find_property (klass, variable_info->data)) { variable_data->available = TRUE; g_object_get (object, variable_info->data, &variable_data->value.size, NULL); } break; case VARIABLE_TYPE_SIZE_RATIO: { const gchar *antecedent = variable_info->data; const gchar *consequent = antecedent + strlen (antecedent) + 1; if (g_object_class_find_property (klass, antecedent) && g_object_class_find_property (klass, consequent)) { variable_data->available = TRUE; g_object_get (object, antecedent, &variable_data->value.size_ratio.antecedent, consequent, &variable_data->value.size_ratio.consequent, NULL); } } break; case VARIABLE_TYPE_INT_RATIO: { const gchar *antecedent = variable_info->data; const gchar *consequent = antecedent + strlen (antecedent) + 1; if (g_object_class_find_property (klass, antecedent) && g_object_class_find_property (klass, consequent)) { variable_data->available = TRUE; g_object_get (object, antecedent, &variable_data->value.int_ratio.antecedent, consequent, &variable_data->value.int_ratio.consequent, NULL); } } break; case VARIABLE_TYPE_PERCENTAGE: if (g_object_class_find_property (klass, variable_info->data)) { variable_data->available = TRUE; g_object_get (object, variable_info->data, &variable_data->value.percentage, NULL); } break; case VARIABLE_TYPE_DURATION: if (g_object_class_find_property (klass, variable_info->data)) { variable_data->available = TRUE; g_object_get (object, variable_info->data, &variable_data->value.duration, NULL); } break; case VARIABLE_TYPE_RATE_OF_CHANGE: if (g_object_class_find_property (klass, variable_info->data)) { variable_data->available = TRUE; g_object_get (object, variable_info->data, &variable_data->value.rate_of_change, NULL); } break; } } static void gimp_dashboard_group_menu_position (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data) { gimp_button_menu_position (user_data, menu, GTK_POS_LEFT, x, y); } static void gimp_dashboard_update_groups (GimpDashboard *dashboard) { Group group; for (group = FIRST_GROUP; group < N_GROUPS; group++) gimp_dashboard_update_group (dashboard, group); } static void gimp_dashboard_update_group (GimpDashboard *dashboard, Group group) { GimpDashboardPrivate *priv = dashboard->priv; const GroupInfo *group_info = &groups[group]; GroupData *group_data = &priv->groups[group]; gint n_rows; gboolean add_separator; gint field; gtk_widget_set_visible (GTK_WIDGET (group_data->expander), group_data->active); if (! group_data->active) return; n_rows = 0; add_separator = FALSE; for (field = 0; field < group_data->n_fields; field++) { const FieldInfo *field_info = &group_info->fields[field]; const FieldData *field_data = &group_data->fields[field]; if (field_info->variable != VARIABLE_SEPARATOR) { if (group_info->has_meter && field_info->meter_value) { gimp_meter_set_value_active (group_data->meter, field_info->meter_value - 1, field_data->active); } if (field_data->active) { if (add_separator) { add_separator = FALSE; n_rows++; } n_rows++; } } else { if (n_rows > 0) add_separator = TRUE; } } gimp_gtk_container_clear (GTK_CONTAINER (group_data->table)); gtk_table_resize (group_data->table, MAX (n_rows, 1), 3); n_rows = 0; add_separator = FALSE; for (field = 0; field < group_data->n_fields; field++) { const FieldInfo *field_info = &group_info->fields[field]; FieldData *field_data = &group_data->fields[field]; if (field_info->variable != VARIABLE_SEPARATOR) { const VariableInfo *variable_info = &variables[field_info->variable]; GtkWidget *separator; GtkWidget *color_area; GtkWidget *label; const gchar *description; gchar *str; if (! field_data->active) continue; description = g_dgettext (NULL, variable_info->description); if (add_separator) { separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_table_attach (group_data->table, separator, 0, 3, n_rows, n_rows + 1, GTK_EXPAND | GTK_FILL, 0, 0, 0); gtk_widget_show (separator); add_separator = FALSE; n_rows++; } if (group_info->has_meter && field_info->meter_value) { color_area = gimp_color_area_new (&variable_info->color, GIMP_COLOR_AREA_FLAT, 0); gimp_help_set_help_data (color_area, description, NULL); gtk_widget_set_size_request (color_area, 5, 5); gtk_table_attach (group_data->table, color_area, 0, 1, n_rows, n_rows + 1, 0, 0, 0, 0); gtk_widget_show (color_area); } str = g_strdup_printf ("%s:", g_dpgettext2 (NULL, "dashboard-variable", field_info->title ? field_info->title : variable_info->title)); label = gtk_label_new (str); gimp_help_set_help_data (label, description, NULL); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_table_attach (group_data->table, label, 1, 2, n_rows, n_rows + 1, GTK_FILL, 0, 0, 0); gtk_widget_show (label); g_free (str); label = gtk_label_new (NULL); field_data->value_label = GTK_LABEL (label); gimp_help_set_help_data (label, description, NULL); gtk_label_set_xalign (GTK_LABEL (label), 0.0); gtk_table_attach (group_data->table, label, 2, 3, n_rows, n_rows + 1, GTK_EXPAND | GTK_FILL, 0, 0, 0); gtk_widget_show (label); n_rows++; } else { if (n_rows > 0) add_separator = TRUE; } } g_mutex_lock (&priv->mutex); gimp_dashboard_update_group_values (dashboard, group); g_mutex_unlock (&priv->mutex); } static void gimp_dashboard_update_group_values (GimpDashboard *dashboard, Group group) { GimpDashboardPrivate *priv = dashboard->priv; const GroupInfo *group_info = &groups[group]; GroupData *group_data = &priv->groups[group]; gdouble limit = 0.0; GString *header_values; gint field; if (! group_data->active) return; if (group_info->has_meter) { if (group_info->meter_limit) { const VariableData *variable_data = &priv->variables[group_info->meter_limit]; if (variable_data->available) { limit = gimp_dashboard_variable_to_double (dashboard, group_info->meter_limit); } else { for (field = 0; field < group_data->n_fields; field++) { const FieldInfo *field_info = &group_info->fields[field]; const FieldData *field_data = &group_data->fields[field]; if (field_info->meter_value && field_data->active) { gdouble value; value = gimp_dashboard_variable_to_double (dashboard, field_info->variable); limit = MAX (limit, value); } } } gimp_meter_set_range (group_data->meter, 0.0, limit); } if (group_info->meter_led) { GimpRGB color = {0.0, 0.0, 0.0, 1.0}; gboolean active = FALSE; const Variable *var; for (var = group_info->meter_led; *var; var++) { if (gimp_dashboard_variable_to_boolean (dashboard, *var)) { const VariableInfo *variable_info = &variables[*var]; color.r = MAX (color.r, variable_info->color.r); color.g = MAX (color.g, variable_info->color.g); color.b = MAX (color.b, variable_info->color.b); active = TRUE; } } if (active) gimp_meter_set_led_color (group_data->meter, &color); gimp_meter_set_led_active (group_data->meter, active); } } group_data->limit = limit; header_values = g_string_new (NULL); for (field = 0; field < group_data->n_fields; field++) { const FieldInfo *field_info = &group_info->fields[field]; const FieldData *field_data = &group_data->fields[field]; if (field_data->active) { gchar *text; text = gimp_dashboard_field_to_string (dashboard, group, field, TRUE); gimp_dashboard_label_set_text (field_data->value_label, text); g_free (text); if (field_info->show_in_header) { text = gimp_dashboard_field_to_string (dashboard, group, field, FALSE); if (header_values->len > 0) g_string_append (header_values, ", "); g_string_append (header_values, text); g_free (text); } } } if (header_values->len > 0) { g_string_prepend (header_values, "("); g_string_append (header_values, ")"); } gimp_dashboard_label_set_text (group_data->header_values_label, header_values->str); g_string_free (header_values, TRUE); } static void gimp_dashboard_group_set_active (GimpDashboard *dashboard, Group group, gboolean active) { GimpDashboardPrivate *priv = dashboard->priv; GroupData *group_data = &priv->groups[group]; if (active != group_data->active) { group_data->active = active; if (group_data->action) { g_signal_handlers_block_by_func (group_data->action, gimp_dashboard_group_action_toggled, dashboard); gimp_toggle_action_set_active (group_data->action, active); g_signal_handlers_unblock_by_func (group_data->action, gimp_dashboard_group_action_toggled, dashboard); } } } static void gimp_dashboard_field_set_active (GimpDashboard *dashboard, Group group, gint field, gboolean active) { GimpDashboardPrivate *priv = dashboard->priv; GroupData *group_data = &priv->groups[group]; FieldData *field_data = &group_data->fields[field]; if (active != field_data->active) { field_data->active = active; g_signal_handlers_block_by_func (field_data->menu_item, gimp_dashboard_field_menu_item_toggled, dashboard); gtk_check_menu_item_set_active (field_data->menu_item, active); g_signal_handlers_unblock_by_func (field_data->menu_item, gimp_dashboard_field_menu_item_toggled, 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) { GimpDashboardPrivate *priv = dashboard->priv; Variable variable; for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++) { const VariableInfo *variable_info = &variables[variable]; VariableData *variable_data = &priv->variables[variable]; if (variable_info->reset_func) variable_info->reset_func (dashboard, variable); g_clear_pointer (&variable_data->data, g_free); variable_data->data_size = 0; } } static gpointer gimp_dashboard_variable_get_data (GimpDashboard *dashboard, Variable variable, gsize size) { GimpDashboardPrivate *priv = dashboard->priv; VariableData *variable_data = &priv->variables[variable]; if (variable_data->data_size != size) { variable_data->data = g_realloc (variable_data->data, size); if (variable_data->data_size < size) { memset ((guint8 *) variable_data->data + variable_data->data_size, 0, size - variable_data->data_size); } variable_data->data_size = size; } return variable_data->data; } static gboolean gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; const VariableInfo *variable_info = &variables[variable]; const VariableData *variable_data = &priv->variables[variable]; if (variable_data->available) { switch (variable_info->type) { case VARIABLE_TYPE_BOOLEAN: return variable_data->value.boolean; case VARIABLE_TYPE_INTEGER: return variable_data->value.integer != 0; case VARIABLE_TYPE_SIZE: return variable_data->value.size > 0; case VARIABLE_TYPE_SIZE_RATIO: return variable_data->value.size_ratio.antecedent != 0 && variable_data->value.size_ratio.consequent != 0; case VARIABLE_TYPE_INT_RATIO: return variable_data->value.int_ratio.antecedent != 0 && variable_data->value.int_ratio.consequent != 0; case VARIABLE_TYPE_PERCENTAGE: return variable_data->value.percentage != 0.0; case VARIABLE_TYPE_DURATION: return variable_data->value.duration != 0.0; case VARIABLE_TYPE_RATE_OF_CHANGE: return variable_data->value.rate_of_change != 0.0; } } return FALSE; } static gdouble gimp_dashboard_variable_to_double (GimpDashboard *dashboard, Variable variable) { GimpDashboardPrivate *priv = dashboard->priv; const VariableInfo *variable_info = &variables[variable]; const VariableData *variable_data = &priv->variables[variable]; if (variable_data->available) { switch (variable_info->type) { case VARIABLE_TYPE_BOOLEAN: return variable_data->value.boolean ? 1.0 : 0.0; case VARIABLE_TYPE_INTEGER: return variable_data->value.integer; case VARIABLE_TYPE_SIZE: return variable_data->value.size; case VARIABLE_TYPE_SIZE_RATIO: if (variable_data->value.size_ratio.consequent) { return (gdouble) variable_data->value.size_ratio.antecedent / (gdouble) variable_data->value.size_ratio.consequent; } break; case VARIABLE_TYPE_INT_RATIO: if (variable_data->value.int_ratio.consequent) { return (gdouble) variable_data->value.int_ratio.antecedent / (gdouble) variable_data->value.int_ratio.consequent; } break; case VARIABLE_TYPE_PERCENTAGE: return variable_data->value.percentage; case VARIABLE_TYPE_DURATION: return variable_data->value.duration; case VARIABLE_TYPE_RATE_OF_CHANGE: return variable_data->value.rate_of_change; } } return 0.0; } static gchar * gimp_dashboard_field_to_string (GimpDashboard *dashboard, Group group, gint field, gboolean full) { GimpDashboardPrivate *priv = dashboard->priv; const GroupInfo *group_info = &groups[group]; const GroupData *group_data = &priv->groups[group]; const FieldInfo *field_info = &group_info->fields[field]; const VariableInfo *variable_info = &variables[field_info->variable]; const VariableData *variable_data = &priv->variables[field_info->variable]; /* Tranlators: "N/A" is an abbreviation for "not available" */ const gchar *str = C_("dashboard-value", "N/A"); gboolean static_str = TRUE; gboolean show_limit = TRUE; if (variable_data->available) { switch (variable_info->type) { case VARIABLE_TYPE_BOOLEAN: str = variable_data->value.boolean ? C_("dashboard-value", "Yes") : C_("dashboard-value", "No"); break; case VARIABLE_TYPE_INTEGER: str = g_strdup_printf ("%d", variable_data->value.integer); static_str = FALSE; break; case VARIABLE_TYPE_SIZE: str = g_format_size_full (variable_data->value.size, G_FORMAT_SIZE_IEC_UNITS); static_str = FALSE; break; case VARIABLE_TYPE_SIZE_RATIO: { if (variable_data->value.size_ratio.consequent) { gdouble value; value = 100.0 * variable_data->value.size_ratio.antecedent / variable_data->value.size_ratio.consequent; str = g_strdup_printf ("%d%%", SIGNED_ROUND (value)); static_str = FALSE; } } break; case VARIABLE_TYPE_INT_RATIO: { gdouble min; gdouble max; gdouble antecedent; gdouble consequent; antecedent = variable_data->value.int_ratio.antecedent; consequent = variable_data->value.int_ratio.consequent; min = MIN (ABS (antecedent), ABS (consequent)); max = MAX (ABS (antecedent), ABS (consequent)); if (min) { antecedent /= min; consequent /= min; } else if (max) { antecedent /= max; consequent /= max; } if (max) { str = g_strdup_printf ("%g:%g", RINT (100.0 * antecedent) / 100.0, RINT (100.0 * consequent) / 100.0); static_str = FALSE; } } break; case VARIABLE_TYPE_PERCENTAGE: str = g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * variable_data->value.percentage)); static_str = FALSE; show_limit = FALSE; break; case VARIABLE_TYPE_DURATION: str = g_strdup_printf ("%02d:%02d:%04.1f", (gint) floor (variable_data->value.duration / 3600.0), (gint) floor (fmod (variable_data->value.duration / 60.0, 60.0)), floor (fmod (variable_data->value.duration, 60.0) * 10.0) / 10.0); static_str = FALSE; show_limit = FALSE; break; case VARIABLE_TYPE_RATE_OF_CHANGE: /* Translators: This string reports the rate of change of a measured * value. The "%g" is replaced by a certain quantity, and the "/s" * is an abbreviation for "per second". */ str = g_strdup_printf (_("%g/s"), variable_data->value.rate_of_change); static_str = FALSE; break; } if (show_limit && variable_data->available && field_info->meter_value && ! field_info->meter_variable && group_data->limit) { gdouble value; gchar *tmp; value = gimp_dashboard_variable_to_double (dashboard, field_info->variable); if (full) { tmp = g_strdup_printf ("%s (%d%%)", str, SIGNED_ROUND (100.0 * value / group_data->limit)); } else { tmp = g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value / group_data->limit)); } if (! static_str) g_free ((gpointer) str); str = tmp; static_str = FALSE; } else if (full && field_info->meter_variable && variables[field_info->meter_variable].type == VARIABLE_TYPE_RATE_OF_CHANGE && priv->variables[field_info->meter_variable].available) { gdouble value; gchar *value_str; gchar *rate_of_change_str; gchar *tmp; value = gimp_dashboard_variable_to_double (dashboard, field_info->meter_variable); value_str = gimp_dashboard_format_value (variable_info->type, value); rate_of_change_str = gimp_dashboard_format_rate_of_change (value_str); g_free (value_str); tmp = g_strdup_printf ("%s (%s)", str, rate_of_change_str); g_free (rate_of_change_str); if (! static_str) g_free ((gpointer) str); str = tmp; static_str = FALSE; } } if (static_str) return g_strdup (str); else 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, gboolean include_current_thread) { GimpDashboardPrivate *priv = dashboard->priv; GimpBacktrace *backtrace = NULL; GArray *addresses = 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) #define LOG_VAR_FLOAT(value) \ G_STMT_START \ { \ gchar buffer[G_ASCII_DTOSTR_BUF_SIZE]; \ \ LOG_VAR ("%s", g_ascii_dtostr (buffer, sizeof (buffer), \ value)); \ } \ G_STMT_END 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_FLOAT ( variable_data->value.percentage); break; case VARIABLE_TYPE_DURATION: LOG_VAR_FLOAT ( variable_data->value.duration); break; case VARIABLE_TYPE_RATE_OF_CHANGE: LOG_VAR_FLOAT ( variable_data->value.rate_of_change); break; } #undef LOG_VAR #undef LOG_VAR_FLOAT } else { gimp_dashboard_log_printf (dashboard, "<%s />\n", variable_info->name); } } gimp_dashboard_log_printf (dashboard, "\n"); } if (priv->log_params.backtrace) backtrace = gimp_backtrace_new (include_current_thread); 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 last_running = -1; gint running; gint last_n_frames = -1; 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); running = gimp_backtrace_is_thread_running (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; last_running = gimp_backtrace_is_thread_running ( priv->log_backtrace, other_thread); last_n_frames = gimp_backtrace_get_n_frames ( priv->log_backtrace, other_thread); n = MIN (n_frames, last_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 (running == last_running && n_frames == last_n_frames && 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); } if (n_frames == 0 || n_head + n_tail < n_frames) { gimp_dashboard_log_printf (dashboard, ">\n"); for (frame = n_head; frame < n_frames - n_tail; frame++) { guintptr address; address = gimp_backtrace_get_frame_address (backtrace, thread, frame); gimp_dashboard_log_printf (dashboard, "\n", (unsigned long long) address); if (g_hash_table_add (priv->log_addresses, (gpointer) address) && priv->log_params.progressive) { if (! addresses) { addresses = g_array_new (FALSE, FALSE, sizeof (guintptr)); } g_array_append_val (addresses, address); } } gimp_dashboard_log_printf (dashboard, "\n"); } else { 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"); } if (addresses) { gimp_dashboard_log_write_address_map (dashboard, (guintptr *) addresses->data, addresses->len, NULL); g_array_free (addresses, TRUE); } if (priv->log_params.progressive) g_output_stream_flush (priv->log_output, NULL, NULL); #undef NONEMPTY priv->log_n_samples++; } static void gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard, const gchar *description) { GimpDashboardPrivate *priv = dashboard->priv; 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"); } gimp_dashboard_log_update_n_markers (dashboard); } static void gimp_dashboard_log_update_highlight (GimpDashboard *dashboard) { GimpDashboardPrivate *priv = dashboard->priv; gimp_highlightable_button_set_highlight ( priv->log_record_button, gimp_dashboard_log_is_recording (dashboard)); } 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 (GimpDashboard *dashboard, guintptr *addresses, gint n_addresses, GimpAsync *async) { GimpBacktraceAddressInfo infos[2]; gint i; gint n; if (n_addresses == 0) return; 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++) { GimpBacktraceAddressInfo *info = &infos[n % 2]; const GimpBacktraceAddressInfo *prev_info = &infos[(n + 1) % 2]; if (async && gimp_async_is_canceled (async)) break; if (gimp_backtrace_get_address_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 (n == 0 || strcmp (info->source_file, prev_info->source_file)) { NONEMPTY (); if (info->source_file[0]) { gimp_dashboard_log_printf (dashboard, ""); gimp_dashboard_log_print_escaped (dashboard, info->source_file); gimp_dashboard_log_printf (dashboard, "\n"); } else { gimp_dashboard_log_printf (dashboard, "\n"); } } if (n == 0 || info->source_line != prev_info->source_line) { NONEMPTY (); if (info->source_line) { gimp_dashboard_log_printf (dashboard, "%d\n", info->source_line); } 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++; } } gimp_dashboard_log_printf (dashboard, "\n" "\n"); } static void gimp_dashboard_log_write_global_address_map (GimpAsync *async, GimpDashboard *dashboard) { GimpDashboardPrivate *priv = dashboard->priv; gint n_addresses; n_addresses = g_hash_table_size (priv->log_addresses); if (n_addresses > 0) { guintptr *addresses; GList *iter; gint i; 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; } gimp_dashboard_log_write_address_map (dashboard, addresses, n_addresses, async); g_free (addresses); } gimp_async_finish (async, NULL); } static void gimp_dashboard_log_log_func (const gchar *log_domain, GLogLevelFlags log_levels, const gchar *message, GimpDashboard *dashboard) { GimpDashboardPrivate *priv = dashboard->priv; const gchar *log_level = NULL; gchar *description; g_mutex_lock (&priv->mutex); switch (log_levels & G_LOG_LEVEL_MASK) { case G_LOG_LEVEL_ERROR: log_level = "ERROR"; break; case G_LOG_LEVEL_CRITICAL: log_level = "CRITICAL"; break; case G_LOG_LEVEL_WARNING: log_level = "WARNING"; break; case G_LOG_LEVEL_MESSAGE: log_level = "MESSAGE"; break; case G_LOG_LEVEL_INFO: log_level = "INFO"; break; case G_LOG_LEVEL_DEBUG: log_level = "DEBUG"; break; default: log_level = "UNKNOWN"; break; } description = g_strdup_printf ("[%s] %s: %s", log_domain, log_level, message); gimp_dashboard_log_add_marker_unlocked (dashboard, description); gimp_dashboard_log_sample (dashboard, FALSE, TRUE); g_free (description); g_mutex_unlock (&priv->mutex); } static gboolean gimp_dashboard_field_use_meter_underlay (Group group, gint field) { const GroupInfo *group_info = &groups[group]; Variable variable = group_info->fields[field].variable; const VariableInfo *variable_info; if (group_info->fields[field].meter_variable) variable = group_info->fields[field].meter_variable; variable_info = &variables [variable]; return variable_info->type == VARIABLE_TYPE_BOOLEAN || (group_info->fields[field].meter_variable && variable_info->type == VARIABLE_TYPE_RATE_OF_CHANGE); } static gchar * gimp_dashboard_format_rate_of_change (const gchar *value) { /* Translators: This string reports the rate of change of a measured value. * The first "%s" is replaced by a certain quantity, usually followed by a * unit of measurement (e.g., "10 bytes"). and the final "/s" is an * abbreviation for "per second" (so the full string would read * "10 bytes/s", that is, "10 bytes per second". */ return g_strdup_printf (_("%s/s"), value); } static gchar * gimp_dashboard_format_value (VariableType type, gdouble value) { switch (type) { case VARIABLE_TYPE_BOOLEAN: return g_strdup (value ? C_("dashboard-value", "Yes") : C_("dashboard-value", "No")); case VARIABLE_TYPE_INTEGER: return g_strdup_printf ("%g", value); case VARIABLE_TYPE_SIZE: return g_format_size_full (value, G_FORMAT_SIZE_IEC_UNITS); case VARIABLE_TYPE_SIZE_RATIO: if (isfinite (value)) return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value)); break; case VARIABLE_TYPE_INT_RATIO: if (isfinite (value)) { gdouble min; gdouble max; gdouble antecedent; gdouble consequent; antecedent = value; consequent = 1.0; min = MIN (ABS (antecedent), ABS (consequent)); max = MAX (ABS (antecedent), ABS (consequent)); if (min) { antecedent /= min; consequent /= min; } else { antecedent /= max; consequent /= max; } return g_strdup_printf ("%g:%g", RINT (100.0 * antecedent) / 100.0, RINT (100.0 * consequent) / 100.0); } else if (isinf (value)) { return g_strdup ("1:0"); } break; case VARIABLE_TYPE_PERCENTAGE: return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value)); case VARIABLE_TYPE_DURATION: return g_strdup_printf ("%02d:%02d:%04.1f", (gint) floor (value / 3600.0), (gint) floor (fmod (value / 60.0, 60.0)), floor (fmod (value, 60.0) * 10.0) / 10.0); case VARIABLE_TYPE_RATE_OF_CHANGE: { gchar buf[64]; g_snprintf (buf, sizeof (buf), "%g", value); return gimp_dashboard_format_rate_of_change (buf); } } return g_strdup (_("N/A")); } static void gimp_dashboard_label_set_text (GtkLabel *label, const gchar *text) { /* the strcmp() reduces the overhead of gtk_label_set_text() when the * text hasn't changed */ if (g_strcmp0 (gtk_label_get_text (label), text)) gtk_label_set_text (label, text); } /* public functions */ GtkWidget * gimp_dashboard_new (Gimp *gimp, GimpMenuFactory *menu_factory) { GimpDashboard *dashboard; dashboard = g_object_new (GIMP_TYPE_DASHBOARD, "menu-factory", menu_factory, "menu-identifier", "", "ui-path", "/dashboard-popup", NULL); dashboard->priv->gimp = gimp; return GTK_WIDGET (dashboard); } gboolean gimp_dashboard_log_start_recording (GimpDashboard *dashboard, GFile *file, const GimpDashboardLogParams *params, GError **error) { GimpDashboardPrivate *priv; GimpUIManager *ui_manager; GimpActionGroup *action_group; gchar *version; gchar **envp; gchar **env; GParamSpec **pspecs; guint n_pspecs; gboolean has_backtrace; Variable variable; guint i; 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); if (! params) params = gimp_dashboard_log_get_default_params (dashboard); priv->log_params = *params; if (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY")) { priv->log_params.sample_frequency = atoi (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY")); } if (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE")) { priv->log_params.backtrace = atoi (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE")) ? 1 : 0; } if (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES")) { priv->log_params.messages = atoi (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES")) ? 1 : 0; } if (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE")) { priv->log_params.progressive = atoi (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE")) ? 1 : 0; } priv->log_params.sample_frequency = CLAMP (priv->log_params.sample_frequency, LOG_SAMPLE_FREQUENCY_MIN, LOG_SAMPLE_FREQUENCY_MAX); g_mutex_lock (&priv->mutex); if (priv->log_params.progressive && g_file_query_exists (file, NULL) && ! g_file_delete (file, NULL, error)) { g_mutex_unlock (&priv->mutex); return FALSE; } 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_n_samples = 0; priv->log_n_markers = 0; priv->log_backtrace = NULL; priv->log_addresses = g_hash_table_new (NULL, NULL); if (priv->log_params.backtrace) has_backtrace = gimp_backtrace_start (); else has_backtrace = FALSE; gimp_dashboard_log_printf (dashboard, "\n" "\n", LOG_VERSION); gimp_dashboard_log_printf (dashboard, "\n" "\n" "%d\n" "%d\n" "%d\n" "%d\n" "\n", priv->log_params.sample_frequency, has_backtrace, priv->log_params.messages, priv->log_params.progressive); gimp_dashboard_log_printf (dashboard, "\n" "\n"); 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"); envp = g_get_environ (); for (env = envp; *env; env++) { if (g_str_has_prefix (*env, "BABL_") || g_str_has_prefix (*env, "GEGL_") || g_str_has_prefix (*env, "GIMP_")) { gchar *delim = strchr (*env, '='); const gchar *s; if (! delim) continue; for (s = *env; s != delim && (g_ascii_isalnum (*s) || *s == '_' || *s == '-'); s++); if (s != delim) continue; *delim = '\0'; gimp_dashboard_log_printf (dashboard, "<%s>", *env); gimp_dashboard_log_print_escaped (dashboard, delim + 1); gimp_dashboard_log_printf (dashboard, "\n", *env); } } g_strfreev (envp); gimp_dashboard_log_printf (dashboard, "\n"); gimp_dashboard_log_printf (dashboard, "\n" "\n"); pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (gegl_config ()), &n_pspecs); for (i = 0; i < n_pspecs; i++) { const GParamSpec *pspec = pspecs[i]; GValue value = {}; GValue str_value = {}; g_value_init (&value, pspec->value_type); g_value_init (&str_value, G_TYPE_STRING); g_object_get_property (G_OBJECT (gegl_config ()), pspec->name, &value); if (g_value_transform (&value, &str_value)) { gimp_dashboard_log_printf (dashboard, "<%s>", pspec->name); gimp_dashboard_log_print_escaped (dashboard, g_value_get_string (&str_value)); gimp_dashboard_log_printf (dashboard, "\n", pspec->name); } g_value_unset (&str_value); g_value_unset (&value); } g_free (pspecs); gimp_dashboard_log_printf (dashboard, "\n"); gimp_dashboard_log_printf (dashboard, "\n" "\n"); 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, "name, type); gimp_dashboard_log_print_escaped (dashboard, /* intentionally untranslated */ variable_info->description); gimp_dashboard_log_printf (dashboard, "\" />\n"); } gimp_dashboard_log_printf (dashboard, "\n"); gimp_dashboard_log_printf (dashboard, "\n" "\n"); if (priv->log_error) { GCancellable *cancellable = g_cancellable_new (); gimp_backtrace_stop (); /* Cancel the overwrite initiated by g_file_replace(). */ g_cancellable_cancel (cancellable); g_output_stream_close (priv->log_output, cancellable, NULL); g_object_unref (cancellable); 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); if (priv->log_params.messages) { priv->log_log_handler = gimp_log_set_handler ( TRUE, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION, (GLogFunc) gimp_dashboard_log_log_func, 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_log_handler) { gimp_log_remove_handler (priv->log_log_handler); priv->log_log_handler = 0; } gimp_dashboard_log_printf (dashboard, "\n" "\n"); if (! priv->log_params.progressive && g_hash_table_size (priv->log_addresses) > 0) { GimpAsync *async; async = gimp_parallel_run_async_independent ( (GimpRunAsyncFunc) gimp_dashboard_log_write_global_address_map, dashboard); gimp_wait (priv->gimp, GIMP_WAITABLE (async), _("Resolving symbol information...")); g_object_unref (async); } gimp_dashboard_log_printf (dashboard, "\n" "\n"); if (priv->log_params.backtrace) gimp_backtrace_stop (); if (! priv->log_error) { g_output_stream_close (priv->log_output, NULL, &priv->log_error); } else { GCancellable *cancellable = g_cancellable_new (); /* Cancel the overwrite initiated by g_file_replace(). */ g_cancellable_cancel (cancellable); g_output_stream_close (priv->log_output, cancellable, NULL); g_object_unref (cancellable); } 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; } const GimpDashboardLogParams * gimp_dashboard_log_get_default_params (GimpDashboard *dashboard) { static const GimpDashboardLogParams default_params = { .sample_frequency = LOG_DEFAULT_SAMPLE_FREQUENCY, .backtrace = LOG_DEFAULT_BACKTRACE, .messages = LOG_DEFAULT_MESSAGES, .progressive = LOG_DEFAULT_PROGRESSIVE }; g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), NULL); return &default_params; } 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); gimp_dashboard_log_add_marker_unlocked (dashboard, description); g_mutex_unlock (&priv->mutex); } void gimp_dashboard_reset (GimpDashboard *dashboard) { GimpDashboardPrivate *priv; g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); priv = dashboard->priv; g_mutex_lock (&priv->mutex); gimp_dashboard_reset_unlocked (dashboard); priv->update_now = TRUE; g_cond_signal (&priv->cond); g_mutex_unlock (&priv->mutex); } void gimp_dashboard_set_update_interval (GimpDashboard *dashboard, GimpDashboardUpdateInteval update_interval) { GimpDashboardPrivate *priv; g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); priv = dashboard->priv; if (update_interval != priv->update_interval) { Group group; g_mutex_lock (&priv->mutex); priv->update_interval = update_interval; for (group = FIRST_GROUP; group < N_GROUPS; group++) { GroupData *group_data = &priv->groups[group]; if (group_data->meter) { gimp_meter_set_history_resolution (group_data->meter, update_interval / 1000.0); } } priv->update_now = TRUE; g_cond_signal (&priv->cond); g_mutex_unlock (&priv->mutex); } } GimpDashboardUpdateInteval gimp_dashboard_get_update_interval (GimpDashboard *dashboard) { g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_UPDATE_INTERVAL); return dashboard->priv->update_interval; } void gimp_dashboard_set_history_duration (GimpDashboard *dashboard, GimpDashboardHistoryDuration history_duration) { GimpDashboardPrivate *priv; g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); priv = dashboard->priv; if (history_duration != priv->history_duration) { Group group; g_mutex_lock (&priv->mutex); priv->history_duration = history_duration; for (group = FIRST_GROUP; group < N_GROUPS; group++) { GroupData *group_data = &priv->groups[group]; if (group_data->meter) { gimp_meter_set_history_duration (group_data->meter, history_duration / 1000.0); } } priv->update_now = TRUE; g_cond_signal (&priv->cond); g_mutex_unlock (&priv->mutex); } } GimpDashboardHistoryDuration gimp_dashboard_get_history_duration (GimpDashboard *dashboard) { g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_HISTORY_DURATION); return dashboard->priv->history_duration; } void gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard, gboolean low_swap_space_warning) { GimpDashboardPrivate *priv; g_return_if_fail (GIMP_IS_DASHBOARD (dashboard)); priv = dashboard->priv; if (low_swap_space_warning != priv->low_swap_space_warning) { g_mutex_lock (&priv->mutex); priv->low_swap_space_warning = low_swap_space_warning; g_mutex_unlock (&priv->mutex); } } gboolean gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard) { g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_LOW_SWAP_SPACE_WARNING); return dashboard->priv->low_swap_space_warning; } void gimp_dashboard_menu_setup (GimpUIManager *manager, const gchar *ui_path) { guint merge_id; Group group; g_return_if_fail (GIMP_IS_UI_MANAGER (manager)); g_return_if_fail (ui_path != NULL); merge_id = gimp_ui_manager_new_merge_id (manager); for (group = FIRST_GROUP; group < N_GROUPS; group++) { const GroupInfo *group_info = &groups[group]; gchar *action_name; gchar *action_path; action_name = g_strdup_printf ("dashboard-group-%s", group_info->name); action_path = g_strdup_printf ("%s/Groups/Groups", ui_path); gimp_ui_manager_add_ui (manager, merge_id, action_path, action_name, action_name, GTK_UI_MANAGER_MENUITEM, FALSE); g_free (action_name); g_free (action_path); } }