
Add a new "Messages" boolean parameter to performance logs, which,
when set, records GLIB log messages in the performance log as
markers, with an accompanying sample capturing their backtrace.
This option is enabled by default.
(cherry picked from commit 9e0fdc8e2c
)
5016 lines
163 KiB
C
5016 lines
163 KiB
C
/* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#include <gegl.h>
|
|
#include <gio/gio.h>
|
|
#include <gtk/gtk.h>
|
|
|
|
#ifdef G_OS_WIN32
|
|
#include <windows.h>
|
|
#include <psapi.h>
|
|
#define HAVE_CPU_GROUP
|
|
#define HAVE_MEMORY_GROUP
|
|
#elif defined(PLATFORM_OSX)
|
|
#include <mach/mach.h>
|
|
#include <sys/times.h>
|
|
#define HAVE_CPU_GROUP
|
|
#define HAVE_MEMORY_GROUP
|
|
#else /* ! G_OS_WIN32 && ! PLATFORM_OSX */
|
|
#ifdef HAVE_SYS_TIMES_H
|
|
#include <sys/times.h>
|
|
#define HAVE_CPU_GROUP
|
|
#endif /* HAVE_SYS_TIMES_H */
|
|
#if defined (HAVE_UNISTD_H) && defined (HAVE_FCNTL_H)
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#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
|
|
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;
|
|
}
|
|
|
|
#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"
|
|
"<sample id=\"%d\" t=\"%lld\"",
|
|
priv->log_n_samples,
|
|
(long long) gimp_dashboard_log_time (dashboard));
|
|
|
|
if (priv->log_n_samples == 0 || variables_changed)
|
|
{
|
|
NONEMPTY ();
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<vars>\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 "</%s>\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,
|
|
"</vars>\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, \
|
|
"<backtrace>\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,
|
|
"<thread id=\"%llu\"",
|
|
(unsigned long long) thread_id);
|
|
|
|
if (thread_name)
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
" name=\"");
|
|
gimp_dashboard_log_print_escaped (dashboard, thread_name);
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\"");
|
|
}
|
|
|
|
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,
|
|
"<thread id=\"%llu\"",
|
|
(unsigned long long) thread_id);
|
|
|
|
if (thread_name)
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
" name=\"");
|
|
gimp_dashboard_log_print_escaped (dashboard, thread_name);
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\"");
|
|
}
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
" running=\"%d\"",
|
|
running);
|
|
|
|
if (n_head > 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,
|
|
"<frame address=\"0x%llx\" />\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,
|
|
"</thread>\n");
|
|
}
|
|
else
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
" />\n");
|
|
}
|
|
}
|
|
|
|
if (! backtrace_empty)
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</backtrace>\n");
|
|
}
|
|
|
|
#undef BACKTRACE_NONEMPTY
|
|
}
|
|
else if (priv->log_backtrace)
|
|
{
|
|
NONEMPTY ();
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<backtrace />\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,
|
|
"</sample>\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"
|
|
"<marker id=\"%d\" t=\"%lld\"",
|
|
priv->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"
|
|
"</marker>\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"
|
|
"<address-map>\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"
|
|
"<address value=\"0x%llx\"",
|
|
(unsigned long long) addresses[i]);
|
|
|
|
if (n == 0 || strcmp (info->object_name, prev_info->object_name))
|
|
{
|
|
NONEMPTY ();
|
|
|
|
if (info->object_name[0])
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<object>");
|
|
gimp_dashboard_log_print_escaped (dashboard,
|
|
info->object_name);
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</object>\n");
|
|
}
|
|
else
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<object />\n");
|
|
}
|
|
}
|
|
|
|
if (n == 0 || strcmp (info->symbol_name, prev_info->symbol_name))
|
|
{
|
|
NONEMPTY ();
|
|
|
|
if (info->symbol_name[0])
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<symbol>");
|
|
gimp_dashboard_log_print_escaped (dashboard,
|
|
info->symbol_name);
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</symbol>\n");
|
|
}
|
|
else
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<symbol />\n");
|
|
}
|
|
}
|
|
|
|
if (n == 0 || info->symbol_address != prev_info->symbol_address)
|
|
{
|
|
NONEMPTY ();
|
|
|
|
if (info->symbol_address)
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<base>0x%llx</base>\n",
|
|
(unsigned long long)
|
|
info->symbol_address);
|
|
}
|
|
else
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<base />\n");
|
|
}
|
|
}
|
|
|
|
if (n == 0 || strcmp (info->source_file, prev_info->source_file))
|
|
{
|
|
NONEMPTY ();
|
|
|
|
if (info->source_file[0])
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<source>");
|
|
gimp_dashboard_log_print_escaped (dashboard,
|
|
info->source_file);
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</source>\n");
|
|
}
|
|
else
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<source />\n");
|
|
}
|
|
}
|
|
|
|
if (n == 0 || info->source_line != prev_info->source_line)
|
|
{
|
|
NONEMPTY ();
|
|
|
|
if (info->source_line)
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<line>%d</line>\n",
|
|
info->source_line);
|
|
}
|
|
else
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"<line />\n");
|
|
}
|
|
}
|
|
|
|
if (empty)
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
" />\n");
|
|
}
|
|
else
|
|
{
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</address>\n");
|
|
}
|
|
|
|
#undef NONEMPTY
|
|
|
|
n++;
|
|
}
|
|
}
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"</address-map>\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", "<Dashboard>",
|
|
"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,
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
"<gimp-performance-log version=\"%d\">\n",
|
|
LOG_VERSION);
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"<params>\n"
|
|
"<sample-frequency>%d</sample-frequency>\n"
|
|
"<backtrace>%d</backtrace>\n"
|
|
"<messages>%d</messages>\n"
|
|
"<progressive>%d</progressive>\n"
|
|
"</params>\n",
|
|
priv->log_params.sample_frequency,
|
|
has_backtrace,
|
|
priv->log_params.messages,
|
|
priv->log_params.progressive);
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"<info>\n");
|
|
|
|
version = gimp_version (TRUE, FALSE);
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"<gimp-version>\n");
|
|
gimp_dashboard_log_print_escaped (dashboard, version);
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</gimp-version>\n");
|
|
|
|
g_free (version);
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"<env>\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,
|
|
"</%s>\n",
|
|
*env);
|
|
}
|
|
}
|
|
|
|
g_strfreev (envp);
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</env>\n");
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"<gegl-config>\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,
|
|
"</%s>\n",
|
|
pspec->name);
|
|
}
|
|
|
|
g_value_unset (&str_value);
|
|
g_value_unset (&value);
|
|
}
|
|
|
|
g_free (pspecs);
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</gegl-config>\n");
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"</info>\n");
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"<var-defs>\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,
|
|
"<var name=\"%s\" type=\"%s\" desc=\"",
|
|
variable_info->name,
|
|
type);
|
|
gimp_dashboard_log_print_escaped (dashboard,
|
|
/* intentionally untranslated */
|
|
variable_info->description);
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\" />\n");
|
|
}
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"</var-defs>\n");
|
|
|
|
gimp_dashboard_log_printf (dashboard,
|
|
"\n"
|
|
"<samples>\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"
|
|
"</samples>\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"
|
|
"</gimp-performance-log>\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);
|
|
}
|
|
}
|