1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
3  *
4  * gimpdashboard.c
5  * Copyright (C) 2017 Ell
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <stdlib.h>
24 #include <string.h>
25 #include <stdarg.h>
26 
27 #include <gegl.h>
28 #include <gio/gio.h>
29 #include <gtk/gtk.h>
30 
31 #ifdef G_OS_WIN32
32 #include <windows.h>
33 #include <psapi.h>
34 #define HAVE_CPU_GROUP
35 #define HAVE_MEMORY_GROUP
36 #elif defined(PLATFORM_OSX)
37 #include <mach/mach.h>
38 #include <sys/times.h>
39 #define HAVE_CPU_GROUP
40 #define HAVE_MEMORY_GROUP
41 #else /* ! G_OS_WIN32 && ! PLATFORM_OSX */
42 #ifdef HAVE_SYS_TIMES_H
43 #include <sys/times.h>
44 #define HAVE_CPU_GROUP
45 #endif /* HAVE_SYS_TIMES_H */
46 #if defined (HAVE_UNISTD_H) && defined (HAVE_FCNTL_H)
47 #include <unistd.h>
48 #include <fcntl.h>
49 #ifdef _SC_PAGE_SIZE
50 #define HAVE_MEMORY_GROUP
51 #endif /* _SC_PAGE_SIZE */
52 #endif /* HAVE_UNISTD_H && HAVE_FCNTL_H */
53 #endif /* ! G_OS_WIN32 */
54 
55 #include "libgimpbase/gimpbase.h"
56 #include "libgimpmath/gimpmath.h"
57 #include "libgimpwidgets/gimpwidgets.h"
58 
59 #include "widgets-types.h"
60 
61 #include "core/gimp.h"
62 #include "core/gimp-gui.h"
63 #include "core/gimp-utils.h"
64 #include "core/gimp-parallel.h"
65 #include "core/gimpasync.h"
66 #include "core/gimpbacktrace.h"
67 #include "core/gimptempbuf.h"
68 #include "core/gimpwaitable.h"
69 
70 #include "gimpactiongroup.h"
71 #include "gimpdocked.h"
72 #include "gimpdashboard.h"
73 #include "gimpdialogfactory.h"
74 #include "gimphelp-ids.h"
75 #include "gimphighlightablebutton.h"
76 #include "gimpmeter.h"
77 #include "gimpsessioninfo-aux.h"
78 #include "gimptoggleaction.h"
79 #include "gimpuimanager.h"
80 #include "gimpwidgets-utils.h"
81 #include "gimpwindowstrategy.h"
82 
83 #include "gimp-intl.h"
84 #include "gimp-log.h"
85 #include "gimp-version.h"
86 
87 
88 #define DEFAULT_UPDATE_INTERVAL        GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC
89 #define DEFAULT_HISTORY_DURATION       GIMP_DASHBOARD_HISTORY_DURATION_60_SEC
90 #define DEFAULT_LOW_SWAP_SPACE_WARNING TRUE
91 
92 #define LOW_SWAP_SPACE_WARNING_ON      /* swap occupied is above */ 0.90 /* of swap limit */
93 #define LOW_SWAP_SPACE_WARNING_OFF     /* swap occupied is below */ 0.85 /* of swap limit */
94 
95 #define CPU_ACTIVE_ON                  /* individual cpu usage is above */ 0.75
96 #define CPU_ACTIVE_OFF                 /* individual cpu usage is below */ 0.25
97 
98 #define LOG_VERSION                    1
99 #define LOG_SAMPLE_FREQUENCY_MIN       1 /* samples per second */
100 #define LOG_SAMPLE_FREQUENCY_MAX       1000 /* samples per second */
101 #define LOG_DEFAULT_SAMPLE_FREQUENCY   10 /* samples per second */
102 #define LOG_DEFAULT_BACKTRACE          TRUE
103 #define LOG_DEFAULT_MESSAGES           TRUE
104 #define LOG_DEFAULT_PROGRESSIVE        FALSE
105 
106 
107 typedef enum
108 {
109   VARIABLE_NONE,
110   FIRST_VARIABLE,
111 
112 
113   /* cache */
114   VARIABLE_CACHE_OCCUPIED = FIRST_VARIABLE,
115   VARIABLE_CACHE_MAXIMUM,
116   VARIABLE_CACHE_LIMIT,
117 
118   VARIABLE_CACHE_COMPRESSION,
119   VARIABLE_CACHE_HIT_MISS,
120 
121   /* swap */
122   VARIABLE_SWAP_OCCUPIED,
123   VARIABLE_SWAP_SIZE,
124   VARIABLE_SWAP_LIMIT,
125 
126   VARIABLE_SWAP_QUEUED,
127   VARIABLE_SWAP_QUEUE_STALLS,
128   VARIABLE_SWAP_QUEUE_FULL,
129 
130   VARIABLE_SWAP_READ,
131   VARIABLE_SWAP_READ_THROUGHPUT,
132   VARIABLE_SWAP_WRITTEN,
133   VARIABLE_SWAP_WRITE_THROUGHPUT,
134 
135   VARIABLE_SWAP_COMPRESSION,
136 
137 #ifdef HAVE_CPU_GROUP
138   /* cpu */
139   VARIABLE_CPU_USAGE,
140   VARIABLE_CPU_ACTIVE,
141   VARIABLE_CPU_ACTIVE_TIME,
142 #endif
143 
144 #ifdef HAVE_MEMORY_GROUP
145   /* memory */
146   VARIABLE_MEMORY_USED,
147   VARIABLE_MEMORY_AVAILABLE,
148   VARIABLE_MEMORY_SIZE,
149 #endif
150 
151   /* misc */
152   VARIABLE_MIPMAPED,
153   VARIABLE_ASSIGNED_THREADS,
154   VARIABLE_ACTIVE_THREADS,
155   VARIABLE_ASYNC_RUNNING,
156   VARIABLE_TILE_ALLOC_TOTAL,
157   VARIABLE_SCRATCH_TOTAL,
158   VARIABLE_TEMP_BUF_TOTAL,
159 
160 
161   N_VARIABLES,
162 
163   VARIABLE_SEPARATOR
164 } Variable;
165 
166 typedef enum
167 {
168   VARIABLE_TYPE_BOOLEAN,
169   VARIABLE_TYPE_INTEGER,
170   VARIABLE_TYPE_SIZE,
171   VARIABLE_TYPE_SIZE_RATIO,
172   VARIABLE_TYPE_INT_RATIO,
173   VARIABLE_TYPE_PERCENTAGE,
174   VARIABLE_TYPE_DURATION,
175   VARIABLE_TYPE_RATE_OF_CHANGE
176 } VariableType;
177 
178 typedef enum
179 {
180   FIRST_GROUP,
181 
182   GROUP_CACHE = FIRST_GROUP,
183   GROUP_SWAP,
184 #ifdef HAVE_CPU_GROUP
185   GROUP_CPU,
186 #endif
187 #ifdef HAVE_MEMORY_GROUP
188   GROUP_MEMORY,
189 #endif
190   GROUP_MISC,
191 
192   N_GROUPS
193 } Group;
194 
195 
196 typedef struct _VariableInfo  VariableInfo;
197 typedef struct _FieldInfo     FieldInfo;
198 typedef struct _GroupInfo     GroupInfo;
199 typedef struct _VariableData  VariableData;
200 typedef struct _FieldData     FieldData;
201 typedef struct _GroupData     GroupData;
202 
203 typedef void (* VariableFunc) (GimpDashboard *dashboard,
204                                Variable       variable);
205 
206 
207 struct _VariableInfo
208 {
209   const gchar   *name;
210   const gchar   *title;
211   const gchar   *description;
212   VariableType   type;
213   gboolean       exclude_from_log;
214   GimpRGB        color;
215   VariableFunc   sample_func;
216   VariableFunc   reset_func;
217   gconstpointer  data;
218 };
219 
220 struct _FieldInfo
221 {
222   Variable     variable;
223   const gchar *title;
224   gboolean     default_active;
225   gboolean     show_in_header;
226   Variable     meter_variable;
227   gint         meter_value;
228   gboolean     meter_cumulative;
229 };
230 
231 struct _GroupInfo
232 {
233   const gchar     *name;
234   const gchar     *title;
235   const gchar     *description;
236   gboolean         default_active;
237   gboolean         default_expanded;
238   gboolean         has_meter;
239   Variable         meter_limit;
240   const Variable  *meter_led;
241   const FieldInfo *fields;
242 };
243 
244 struct _VariableData
245 {
246   gboolean available;
247 
248   union
249   {
250     gboolean  boolean;
251     gint      integer;
252     guint64   size;           /* in bytes                   */
253     struct
254     {
255       guint64 antecedent;
256       guint64 consequent;
257     } size_ratio;
258     struct
259     {
260       gint    antecedent;
261       gint    consequent;
262     } int_ratio;
263     gdouble   percentage;     /* from 0 to 1                */
264     gdouble   duration;       /* in seconds                 */
265     gdouble   rate_of_change; /* in source units per second */
266   } value;
267 
268   gpointer data;
269   gsize    data_size;
270 };
271 
272 struct _FieldData
273 {
274   gboolean          active;
275 
276   GtkCheckMenuItem *menu_item;
277   GtkLabel         *value_label;
278 };
279 
280 struct _GroupData
281 {
282   gint              n_fields;
283   gint              n_meter_values;
284 
285   gboolean          active;
286   gdouble           limit;
287 
288   GimpToggleAction *action;
289   GtkExpander      *expander;
290   GtkLabel         *header_values_label;
291   GtkButton        *menu_button;
292   GtkMenu          *menu;
293   GimpMeter        *meter;
294   GtkTable         *table;
295 
296   FieldData        *fields;
297 };
298 
299 struct _GimpDashboardPrivate
300 {
301   Gimp                         *gimp;
302 
303   VariableData                  variables[N_VARIABLES];
304   GroupData                     groups[N_GROUPS];
305 
306   GThread                      *thread;
307   GMutex                        mutex;
308   GCond                         cond;
309   gboolean                      quit;
310   gboolean                      update_now;
311 
312   gint                          update_idle_id;
313   gint                          low_swap_space_idle_id;
314 
315   GimpDashboardUpdateInteval    update_interval;
316   GimpDashboardHistoryDuration  history_duration;
317   gboolean                      low_swap_space_warning;
318 
319   GOutputStream                *log_output;
320   GError                       *log_error;
321   GimpDashboardLogParams        log_params;
322   gint64                        log_start_time;
323   gint                          log_n_samples;
324   gint                          log_n_markers;
325   VariableData                  log_variables[N_VARIABLES];
326   GimpBacktrace                *log_backtrace;
327   GHashTable                   *log_addresses;
328   GimpLogHandler                log_log_handler;
329 
330   GimpHighlightableButton      *log_record_button;
331   GtkLabel                     *log_add_marker_label;
332 };
333 
334 
335 /*  local function prototypes  */
336 
337 static void       gimp_dashboard_docked_iface_init              (GimpDockedInterface *iface);
338 
339 static void       gimp_dashboard_constructed                    (GObject             *object);
340 static void       gimp_dashboard_dispose                        (GObject             *object);
341 static void       gimp_dashboard_finalize                       (GObject             *object);
342 
343 static void       gimp_dashboard_map                            (GtkWidget           *widget);
344 static void       gimp_dashboard_unmap                          (GtkWidget           *widget);
345 
346 static void       gimp_dashboard_set_aux_info                   (GimpDocked          *docked,
347                                                                  GList               *aux_info);
348 static GList    * gimp_dashboard_get_aux_info                   (GimpDocked          *docked);
349 
350 static gboolean   gimp_dashboard_group_expander_button_press    (GimpDashboard       *dashboard,
351                                                                  GdkEventButton      *bevent,
352                                                                  GtkWidget           *widget);
353 
354 static void       gimp_dashboard_group_action_toggled           (GimpDashboard       *dashboard,
355                                                                  GimpToggleAction    *action);
356 static void       gimp_dashboard_field_menu_item_toggled        (GimpDashboard       *dashboard,
357                                                                  GtkCheckMenuItem    *item);
358 
359 static gpointer   gimp_dashboard_sample                         (GimpDashboard       *dashboard);
360 
361 static gboolean   gimp_dashboard_update                         (GimpDashboard       *dashboard);
362 static gboolean   gimp_dashboard_low_swap_space                 (GimpDashboard       *dashboard);
363 
364 static void       gimp_dashboard_sample_function                (GimpDashboard       *dashboard,
365                                                                  Variable             variable);
366 static void       gimp_dashboard_sample_gegl_config             (GimpDashboard       *dashboard,
367                                                                  Variable             variable);
368 static void       gimp_dashboard_sample_gegl_stats              (GimpDashboard       *dashboard,
369                                                                  Variable             variable);
370 static void       gimp_dashboard_sample_variable_changed        (GimpDashboard       *dashboard,
371                                                                  Variable             variable);
372 static void       gimp_dashboard_sample_variable_rate_of_change (GimpDashboard       *dashboard,
373                                                                  Variable             variable);
374 static void       gimp_dashboard_sample_swap_limit              (GimpDashboard       *dashboard,
375                                                                  Variable             variable);
376 #ifdef HAVE_CPU_GROUP
377 static void       gimp_dashboard_sample_cpu_usage               (GimpDashboard       *dashboard,
378                                                                  Variable             variable);
379 static void       gimp_dashboard_sample_cpu_active              (GimpDashboard       *dashboard,
380                                                                  Variable             variable);
381 static void       gimp_dashboard_sample_cpu_active_time         (GimpDashboard       *dashboard,
382                                                                  Variable             variable);
383 #endif /* HAVE_CPU_GROUP */
384 
385 #ifdef HAVE_MEMORY_GROUP
386 static void       gimp_dashboard_sample_memory_used             (GimpDashboard       *dashboard,
387                                                                  Variable             variable);
388 static void       gimp_dashboard_sample_memory_available        (GimpDashboard       *dashboard,
389                                                                  Variable             variable);
390 static void       gimp_dashboard_sample_memory_size             (GimpDashboard       *dashboard,
391                                                                  Variable             variable);
392 #endif /* HAVE_MEMORY_GROUP */
393 
394 static void       gimp_dashboard_sample_object                  (GimpDashboard       *dashboard,
395                                                                  GObject             *object,
396                                                                  Variable             variable);
397 
398 static void       gimp_dashboard_group_menu_position            (GtkMenu             *menu,
399                                                                  gint                *x,
400                                                                  gint                *y,
401                                                                  gboolean            *push_in,
402                                                                  gpointer             user_data);
403 
404 static void       gimp_dashboard_update_groups                  (GimpDashboard       *dashboard);
405 static void       gimp_dashboard_update_group                   (GimpDashboard       *dashboard,
406                                                                  Group                group);
407 static void       gimp_dashboard_update_group_values            (GimpDashboard       *dashboard,
408                                                                  Group                group);
409 
410 static void       gimp_dashboard_group_set_active               (GimpDashboard       *dashboard,
411                                                                  Group                group,
412                                                                  gboolean             active);
413 static void       gimp_dashboard_field_set_active               (GimpDashboard       *dashboard,
414                                                                  Group                group,
415                                                                  gint                 field,
416                                                                  gboolean             active);
417 
418 static void       gimp_dashboard_reset_unlocked                 (GimpDashboard       *dashboard);
419 
420 static void       gimp_dashboard_reset_variables                (GimpDashboard       *dashboard);
421 
422 static gpointer   gimp_dashboard_variable_get_data              (GimpDashboard       *dashboard,
423                                                                  Variable             variable,
424                                                                  gsize                size);
425 
426 static gboolean   gimp_dashboard_variable_to_boolean            (GimpDashboard       *dashboard,
427                                                                  Variable             variable);
428 static gdouble    gimp_dashboard_variable_to_double             (GimpDashboard       *dashboard,
429                                                                  Variable             variable);
430 
431 static gchar    * gimp_dashboard_field_to_string                (GimpDashboard       *dashboard,
432                                                                  Group                group,
433                                                                  gint                 field,
434                                                                  gboolean             full);
435 
436 static gboolean   gimp_dashboard_log_printf                     (GimpDashboard       *dashboard,
437                                                                  const gchar         *format,
438                                                                  ...) G_GNUC_PRINTF (2, 3);
439 static gboolean   gimp_dashboard_log_print_escaped              (GimpDashboard       *dashboard,
440                                                                  const gchar         *string);
441 static gint64     gimp_dashboard_log_time                       (GimpDashboard       *dashboard);
442 static void       gimp_dashboard_log_sample                     (GimpDashboard       *dashboard,
443                                                                  gboolean             variables_changed,
444                                                                  gboolean             include_current_thread);
445 static void       gimp_dashboard_log_add_marker_unlocked        (GimpDashboard       *dashboard,
446                                                                  const gchar         *description);
447 static void       gimp_dashboard_log_update_highlight           (GimpDashboard       *dashboard);
448 static void       gimp_dashboard_log_update_n_markers           (GimpDashboard       *dashboard);
449 
450 static void       gimp_dashboard_log_write_address_map          (GimpDashboard       *dashboard,
451                                                                  guintptr            *addresses,
452                                                                  gint                 n_addresses,
453                                                                  GimpAsync           *async);
454 static void       gimp_dashboard_log_write_global_address_map   (GimpAsync           *async,
455                                                                  GimpDashboard       *dashboard);
456 static void       gimp_dashboard_log_log_func                   (const gchar         *log_domain,
457                                                                  GLogLevelFlags       log_levels,
458                                                                  const gchar         *message,
459                                                                  GimpDashboard       *dashboard);
460 
461 static gboolean   gimp_dashboard_field_use_meter_underlay       (Group                group,
462                                                                  gint                 field);
463 
464 static gchar    * gimp_dashboard_format_rate_of_change          (const gchar         *value);
465 static gchar    * gimp_dashboard_format_value                   (VariableType         type,
466                                                                  gdouble              value);
467 
468 static void       gimp_dashboard_label_set_text                 (GtkLabel            *label,
469                                                                  const gchar         *text);
470 
471 
472 /*  static variables  */
473 
474 static const VariableInfo variables[] =
475 {
476   /* cache variables */
477 
478   [VARIABLE_CACHE_OCCUPIED] =
479   { .name             = "cache-occupied",
480     .title            = NC_("dashboard-variable", "Occupied"),
481     .description      = N_("Tile cache occupied size"),
482     .type             = VARIABLE_TYPE_SIZE,
483     .color            = {0.3, 0.6, 0.3, 1.0},
484     .sample_func      = gimp_dashboard_sample_gegl_stats,
485     .data             = "tile-cache-total"
486   },
487 
488   [VARIABLE_CACHE_MAXIMUM] =
489   { .name             = "cache-maximum",
490     .title            = NC_("dashboard-variable", "Maximum"),
491     .description      = N_("Maximal tile cache occupied size"),
492     .type             = VARIABLE_TYPE_SIZE,
493     .color            = {0.3, 0.7, 0.8, 1.0},
494     .sample_func      = gimp_dashboard_sample_gegl_stats,
495     .data             = "tile-cache-total-max"
496   },
497 
498   [VARIABLE_CACHE_LIMIT] =
499   { .name             = "cache-limit",
500     .title            = NC_("dashboard-variable", "Limit"),
501     .description      = N_("Tile cache size limit"),
502     .type             = VARIABLE_TYPE_SIZE,
503     .sample_func      = gimp_dashboard_sample_gegl_config,
504     .data             = "tile-cache-size"
505   },
506 
507   [VARIABLE_CACHE_COMPRESSION] =
508   { .name             = "cache-compression",
509     .title            = NC_("dashboard-variable", "Compression"),
510     .description      = N_("Tile cache compression ratio"),
511     .type             = VARIABLE_TYPE_SIZE_RATIO,
512     .sample_func      = gimp_dashboard_sample_gegl_stats,
513     .data             = "tile-cache-total\0"
514                         "tile-cache-total-uncompressed"
515   },
516 
517   [VARIABLE_CACHE_HIT_MISS] =
518   { .name             = "cache-hit-miss",
519     .title            = NC_("dashboard-variable", "Hit/Miss"),
520     .description      = N_("Tile cache hit/miss ratio"),
521     .type             = VARIABLE_TYPE_INT_RATIO,
522     .sample_func      = gimp_dashboard_sample_gegl_stats,
523     .data             = "tile-cache-hits\0"
524                         "tile-cache-misses"
525   },
526 
527 
528   /* swap variables */
529 
530   [VARIABLE_SWAP_OCCUPIED] =
531   { .name             = "swap-occupied",
532     .title            = NC_("dashboard-variable", "Occupied"),
533     .description      = N_("Swap file occupied size"),
534     .type             = VARIABLE_TYPE_SIZE,
535     .color            = {0.8, 0.2, 0.2, 1.0},
536     .sample_func      = gimp_dashboard_sample_gegl_stats,
537     .data             = "swap-total"
538   },
539 
540   [VARIABLE_SWAP_SIZE] =
541   { .name             = "swap-size",
542     .title            = NC_("dashboard-variable", "Size"),
543     .description      = N_("Swap file size"),
544     .type             = VARIABLE_TYPE_SIZE,
545     .color            = {0.8, 0.6, 0.4, 1.0},
546     .sample_func      = gimp_dashboard_sample_gegl_stats,
547     .data             = "swap-file-size"
548   },
549 
550   [VARIABLE_SWAP_LIMIT] =
551   { .name             = "swap-limit",
552     .title            = NC_("dashboard-variable", "Limit"),
553     .description      = N_("Swap file size limit"),
554     .type             = VARIABLE_TYPE_SIZE,
555     .sample_func      = gimp_dashboard_sample_swap_limit,
556   },
557 
558   [VARIABLE_SWAP_QUEUED] =
559   { .name             = "swap-queued",
560     .title            = NC_("dashboard-variable", "Queued"),
561     .description      = N_("Size of data queued for writing to the swap"),
562     .type             = VARIABLE_TYPE_SIZE,
563     .color            = {0.8, 0.8, 0.2, 0.5},
564     .sample_func      = gimp_dashboard_sample_gegl_stats,
565     .data             = "swap-queued-total"
566   },
567 
568   [VARIABLE_SWAP_QUEUE_STALLS] =
569   { .name             = "swap-queue-stalls",
570     .title            = NC_("dashboard-variable", "Queue stalls"),
571     .description      = N_("Number of times the writing to the swap has been "
572                            "stalled, due to a full queue"),
573     .type             = VARIABLE_TYPE_INTEGER,
574     .sample_func      = gimp_dashboard_sample_gegl_stats,
575     .data             = "swap-queue-stalls"
576   },
577 
578   [VARIABLE_SWAP_QUEUE_FULL] =
579   { .name             = "swap-queue-full",
580     .title            = NC_("dashboard-variable", "Queue full"),
581     .description      = N_("Whether the swap queue is full"),
582     .type             = VARIABLE_TYPE_BOOLEAN,
583     .sample_func      = gimp_dashboard_sample_variable_changed,
584     .data             = GINT_TO_POINTER (VARIABLE_SWAP_QUEUE_STALLS)
585   },
586 
587   [VARIABLE_SWAP_READ] =
588   { .name             = "swap-read",
589     /* Translators: this is the past participle form of "read",
590      *              as in "total amount of data read from the swap".
591      */
592     .title            = NC_("dashboard-variable", "Read"),
593     .description      = N_("Total amount of data read from the swap"),
594     .type             = VARIABLE_TYPE_SIZE,
595     .color            = {0.2, 0.4, 1.0, 0.4},
596     .sample_func      = gimp_dashboard_sample_gegl_stats,
597     .data             = "swap-read-total"
598   },
599 
600   [VARIABLE_SWAP_READ_THROUGHPUT] =
601   { .name             = "swap-read-throughput",
602     .title            = NC_("dashboard-variable", "Read throughput"),
603     .description      = N_("The rate at which data is read from the swap"),
604     .type             = VARIABLE_TYPE_RATE_OF_CHANGE,
605     .color            = {0.2, 0.4, 1.0, 1.0},
606     .sample_func      = gimp_dashboard_sample_variable_rate_of_change,
607     .data             = GINT_TO_POINTER (VARIABLE_SWAP_READ)
608   },
609 
610   [VARIABLE_SWAP_WRITTEN] =
611   { .name             = "swap-written",
612     /* Translators: this is the past participle form of "write",
613      *              as in "total amount of data written to the swap".
614      */
615     .title            = NC_("dashboard-variable", "Written"),
616     .description      = N_("Total amount of data written to the swap"),
617     .type             = VARIABLE_TYPE_SIZE,
618     .color            = {0.8, 0.3, 0.2, 0.4},
619     .sample_func      = gimp_dashboard_sample_gegl_stats,
620     .data             = "swap-write-total"
621   },
622 
623   [VARIABLE_SWAP_WRITE_THROUGHPUT] =
624   { .name             = "swap-write-throughput",
625     .title            = NC_("dashboard-variable", "Write throughput"),
626     .description      = N_("The rate at which data is written to the swap"),
627     .type             = VARIABLE_TYPE_RATE_OF_CHANGE,
628     .color            = {0.8, 0.3, 0.2, 1.0},
629     .sample_func      = gimp_dashboard_sample_variable_rate_of_change,
630     .data             = GINT_TO_POINTER (VARIABLE_SWAP_WRITTEN)
631   },
632 
633   [VARIABLE_SWAP_COMPRESSION] =
634   { .name             = "swap-compression",
635     .title            = NC_("dashboard-variable", "Compression"),
636     .description      = N_("Swap compression ratio"),
637     .type             = VARIABLE_TYPE_SIZE_RATIO,
638     .sample_func      = gimp_dashboard_sample_gegl_stats,
639     .data             = "swap-total\0"
640                         "swap-total-uncompressed"
641   },
642 
643 
644 #ifdef HAVE_CPU_GROUP
645   /* cpu variables */
646 
647   [VARIABLE_CPU_USAGE] =
648   { .name             = "cpu-usage",
649     .title            = NC_("dashboard-variable", "Usage"),
650     .description      = N_("Total CPU usage"),
651     .type             = VARIABLE_TYPE_PERCENTAGE,
652     .color            = {0.8, 0.7, 0.2, 1.0},
653     .sample_func      = gimp_dashboard_sample_cpu_usage
654   },
655 
656   [VARIABLE_CPU_ACTIVE] =
657   { .name             = "cpu-active",
658     .title            = NC_("dashboard-variable", "Active"),
659     .description      = N_("Whether the CPU is active"),
660     .type             = VARIABLE_TYPE_BOOLEAN,
661     .color            = {0.9, 0.8, 0.3, 1.0},
662     .sample_func      = gimp_dashboard_sample_cpu_active
663   },
664 
665   [VARIABLE_CPU_ACTIVE_TIME] =
666   { .name             = "cpu-active-time",
667     .title            = NC_("dashboard-variable", "Active"),
668     .description      = N_("Total amount of time the CPU has been active"),
669     .type             = VARIABLE_TYPE_DURATION,
670     .color            = {0.8, 0.7, 0.2, 0.4},
671     .sample_func      = gimp_dashboard_sample_cpu_active_time
672   },
673 #endif /* HAVE_CPU_GROUP */
674 
675 
676 #ifdef HAVE_MEMORY_GROUP
677   /* memory variables */
678 
679   [VARIABLE_MEMORY_USED] =
680   { .name             = "memory-used",
681     .title            = NC_("dashboard-variable", "Used"),
682     .description      = N_("Amount of memory used by the process"),
683     .type             = VARIABLE_TYPE_SIZE,
684     .color            = {0.8, 0.5, 0.2, 1.0},
685     .sample_func      = gimp_dashboard_sample_memory_used
686   },
687 
688   [VARIABLE_MEMORY_AVAILABLE] =
689   { .name             = "memory-available",
690     .title            = NC_("dashboard-variable", "Available"),
691     .description      = N_("Amount of available physical memory"),
692     .type             = VARIABLE_TYPE_SIZE,
693     .color            = {0.8, 0.5, 0.2, 0.4},
694     .sample_func      = gimp_dashboard_sample_memory_available
695   },
696 
697   [VARIABLE_MEMORY_SIZE] =
698   { .name             = "memory-size",
699     .title            = NC_("dashboard-variable", "Size"),
700     .description      = N_("Physical memory size"),
701     .type             = VARIABLE_TYPE_SIZE,
702     .sample_func      = gimp_dashboard_sample_memory_size
703   },
704 #endif /* HAVE_MEMORY_GROUP */
705 
706 
707   /* misc variables */
708 
709   [VARIABLE_MIPMAPED] =
710   { .name             = "mipmapped",
711     .title            = NC_("dashboard-variable", "Mipmapped"),
712     .description      = N_("Total size of processed mipmapped data"),
713     .type             = VARIABLE_TYPE_SIZE,
714     .sample_func      = gimp_dashboard_sample_gegl_stats,
715     .data             = "zoom-total"
716   },
717 
718   [VARIABLE_ASSIGNED_THREADS] =
719   { .name             = "assigned-threads",
720     .title            = NC_("dashboard-variable", "Assigned"),
721     .description      = N_("Number of assigned worker threads"),
722     .type             = VARIABLE_TYPE_INTEGER,
723     .sample_func      = gimp_dashboard_sample_gegl_stats,
724     .data             = "assigned-threads"
725   },
726 
727   [VARIABLE_ACTIVE_THREADS] =
728   { .name             = "active-threads",
729     .title            = NC_("dashboard-variable", "Active"),
730     .description      = N_("Number of active worker threads"),
731     .type             = VARIABLE_TYPE_INTEGER,
732     .sample_func      = gimp_dashboard_sample_gegl_stats,
733     .data             = "active-threads"
734   },
735 
736   [VARIABLE_ASYNC_RUNNING] =
737   { .name             = "async-running",
738     .title            = NC_("dashboard-variable", "Async"),
739     .description      = N_("Number of ongoing asynchronous operations"),
740     .type             = VARIABLE_TYPE_INTEGER,
741     .sample_func      = gimp_dashboard_sample_function,
742     .data             = gimp_async_get_n_running
743   },
744 
745   [VARIABLE_TILE_ALLOC_TOTAL] =
746   { .name             = "tile-alloc-total",
747     .title            = NC_("dashboard-variable", "Tile"),
748     .description      = N_("Total size of tile memory"),
749     .type             = VARIABLE_TYPE_SIZE,
750     .color            = {0.3, 0.3, 1.0, 1.0},
751     .sample_func      = gimp_dashboard_sample_gegl_stats,
752     .data             = "tile-alloc-total"
753   },
754 
755   [VARIABLE_SCRATCH_TOTAL] =
756   { .name             = "scratch-total",
757     .title            = NC_("dashboard-variable", "Scratch"),
758     .description      = N_("Total size of scratch memory"),
759     .type             = VARIABLE_TYPE_SIZE,
760     .sample_func      = gimp_dashboard_sample_gegl_stats,
761     .data             = "scratch-total"
762   },
763 
764   [VARIABLE_TEMP_BUF_TOTAL] =
765   { .name             = "temp-buf-total",
766     /* Translators:  "TempBuf" is a technical term referring to an internal
767      * GIMP data structure.  It's probably OK to leave it untranslated.
768      */
769     .title            = NC_("dashboard-variable", "TempBuf"),
770     .description      = N_("Total size of temporary buffers"),
771     .type             = VARIABLE_TYPE_SIZE,
772     .sample_func      = gimp_dashboard_sample_function,
773     .data             = gimp_temp_buf_get_total_memsize
774   }
775 };
776 
777 static const GroupInfo groups[] =
778 {
779   /* cache group */
780   [GROUP_CACHE] =
781   { .name             = "cache",
782     .title            = NC_("dashboard-group", "Cache"),
783     .description      = N_("In-memory tile cache"),
784     .default_active   = TRUE,
785     .default_expanded = TRUE,
786     .has_meter        = TRUE,
787     .meter_limit      = VARIABLE_CACHE_LIMIT,
788     .fields           = (const FieldInfo[])
789                         {
790                           { .variable         = VARIABLE_CACHE_OCCUPIED,
791                             .default_active   = TRUE,
792                             .show_in_header   = TRUE,
793                             .meter_value      = 2
794                           },
795                           { .variable         = VARIABLE_CACHE_MAXIMUM,
796                             .default_active   = FALSE,
797                             .meter_value      = 1
798                           },
799                           { .variable         = VARIABLE_CACHE_LIMIT,
800                             .default_active   = TRUE
801                           },
802 
803                           { VARIABLE_SEPARATOR },
804 
805                           { .variable         = VARIABLE_CACHE_COMPRESSION,
806                             .default_active   = FALSE
807                           },
808                           { .variable         = VARIABLE_CACHE_HIT_MISS,
809                             .default_active   = FALSE
810                           },
811 
812                           {}
813                         }
814   },
815 
816   /* swap group */
817   [GROUP_SWAP] =
818   { .name             = "swap",
819     .title            = NC_("dashboard-group", "Swap"),
820     .description      = N_("On-disk tile swap"),
821     .default_active   = TRUE,
822     .default_expanded = TRUE,
823     .has_meter        = TRUE,
824     .meter_limit      = VARIABLE_SWAP_LIMIT,
825     .meter_led        = (const Variable[])
826                         {
827                           VARIABLE_SWAP_QUEUE_FULL,
828                           VARIABLE_SWAP_READ_THROUGHPUT,
829                           VARIABLE_SWAP_WRITE_THROUGHPUT,
830 
831                           VARIABLE_NONE
832                         },
833     .fields           = (const FieldInfo[])
834                         {
835                           { .variable         = VARIABLE_SWAP_OCCUPIED,
836                             .default_active   = TRUE,
837                             .show_in_header   = TRUE,
838                             .meter_value      = 5
839                           },
840                           { .variable         = VARIABLE_SWAP_SIZE,
841                             .default_active   = TRUE,
842                             .meter_value      = 4
843                           },
844                           { .variable         = VARIABLE_SWAP_LIMIT,
845                             .default_active   = TRUE
846                           },
847 
848                           { VARIABLE_SEPARATOR },
849 
850                           { .variable         = VARIABLE_SWAP_QUEUED,
851                             .default_active   = FALSE,
852                             .meter_variable   = VARIABLE_SWAP_QUEUE_FULL,
853                             .meter_value      = 3
854                           },
855 
856                           { VARIABLE_SEPARATOR },
857 
858                           { .variable         = VARIABLE_SWAP_READ,
859                             .default_active   = FALSE,
860                             .meter_variable   = VARIABLE_SWAP_READ_THROUGHPUT,
861                             .meter_value      = 2
862                           },
863 
864                           { .variable         = VARIABLE_SWAP_WRITTEN,
865                             .default_active   = FALSE,
866                             .meter_variable   = VARIABLE_SWAP_WRITE_THROUGHPUT,
867                             .meter_value      = 1
868                           },
869 
870                           { VARIABLE_SEPARATOR },
871 
872                           { .variable         = VARIABLE_SWAP_COMPRESSION,
873                             .default_active   = FALSE
874                           },
875 
876                           {}
877                         }
878   },
879 
880 #ifdef HAVE_CPU_GROUP
881   /* cpu group */
882   [GROUP_CPU] =
883   { .name             = "cpu",
884     .title            = NC_("dashboard-group", "CPU"),
885     .description      = N_("CPU usage"),
886     .default_active   = TRUE,
887     .default_expanded = FALSE,
888     .has_meter        = TRUE,
889     .meter_led        = (const Variable[])
890                         {
891                           VARIABLE_CPU_ACTIVE,
892 
893                           VARIABLE_NONE
894                         },
895     .fields           = (const FieldInfo[])
896                         {
897                           { .variable         = VARIABLE_CPU_USAGE,
898                             .default_active   = TRUE,
899                             .show_in_header   = TRUE,
900                             .meter_value      = 2
901                           },
902 
903                           { VARIABLE_SEPARATOR },
904 
905                           { .variable         = VARIABLE_CPU_ACTIVE_TIME,
906                             .default_active   = FALSE,
907                             .meter_variable   = VARIABLE_CPU_ACTIVE,
908                             .meter_value      = 1
909                           },
910 
911                           {}
912                         }
913   },
914 #endif /* HAVE_CPU_GROUP */
915 
916 #ifdef HAVE_MEMORY_GROUP
917   /* memory group */
918   [GROUP_MEMORY] =
919   { .name             = "memory",
920     .title            = NC_("dashboard-group", "Memory"),
921     .description      = N_("Memory usage"),
922     .default_active   = TRUE,
923     .default_expanded = FALSE,
924     .has_meter        = TRUE,
925     .meter_limit      = VARIABLE_MEMORY_SIZE,
926     .fields           = (const FieldInfo[])
927                         {
928                           { .variable         = VARIABLE_CACHE_OCCUPIED,
929                             .title            = NC_("dashboard-variable", "Cache"),
930                             .default_active   = FALSE,
931                             .meter_value      = 4
932                           },
933                           { .variable         = VARIABLE_TILE_ALLOC_TOTAL,
934                             .default_active   = FALSE,
935                             .meter_value      = 3
936                           },
937 
938                           { VARIABLE_SEPARATOR },
939 
940                           { .variable         = VARIABLE_MEMORY_USED,
941                             .default_active   = TRUE,
942                             .show_in_header   = TRUE,
943                             .meter_value      = 2,
944                             .meter_cumulative = TRUE
945                           },
946                           { .variable         = VARIABLE_MEMORY_AVAILABLE,
947                             .default_active   = TRUE,
948                             .meter_value      = 1,
949                             .meter_cumulative = TRUE
950                           },
951                           { .variable         = VARIABLE_MEMORY_SIZE,
952                             .default_active   = TRUE
953                           },
954 
955                           {}
956                         }
957   },
958 #endif /* HAVE_MEMORY_GROUP */
959 
960   /* misc group */
961   [GROUP_MISC] =
962   { .name             = "misc",
963     .title            = NC_("dashboard-group", "Misc"),
964     .description      = N_("Miscellaneous information"),
965     .default_active   = FALSE,
966     .default_expanded = FALSE,
967     .has_meter        = FALSE,
968     .fields           = (const FieldInfo[])
969                         {
970                           { .variable       = VARIABLE_MIPMAPED,
971                             .default_active = TRUE
972                           },
973                           { .variable       = VARIABLE_ASSIGNED_THREADS,
974                             .default_active = TRUE
975                           },
976                           { .variable       = VARIABLE_ACTIVE_THREADS,
977                             .default_active = TRUE
978                           },
979                           { .variable       = VARIABLE_ASYNC_RUNNING,
980                             .default_active = TRUE
981                           },
982                           { .variable       = VARIABLE_TILE_ALLOC_TOTAL,
983                             .default_active = TRUE
984                           },
985                           { .variable       = VARIABLE_SCRATCH_TOTAL,
986                             .default_active = TRUE
987                           },
988                           { .variable       = VARIABLE_TEMP_BUF_TOTAL,
989                             .default_active = TRUE
990                           },
991 
992                           {}
993                         }
994   },
995 };
996 
997 
998 G_DEFINE_TYPE_WITH_CODE (GimpDashboard, gimp_dashboard, GIMP_TYPE_EDITOR,
999                          G_ADD_PRIVATE (GimpDashboard)
1000                          G_IMPLEMENT_INTERFACE (GIMP_TYPE_DOCKED,
1001                                                 gimp_dashboard_docked_iface_init))
1002 
1003 #define parent_class gimp_dashboard_parent_class
1004 
1005 static GimpDockedInterface *parent_docked_iface = NULL;
1006 
1007 
1008 /*  private functions  */
1009 
1010 
1011 static void
gimp_dashboard_class_init(GimpDashboardClass * klass)1012 gimp_dashboard_class_init (GimpDashboardClass *klass)
1013 {
1014   GObjectClass   *object_class = G_OBJECT_CLASS (klass);
1015   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1016 
1017   object_class->constructed = gimp_dashboard_constructed;
1018   object_class->dispose     = gimp_dashboard_dispose;
1019   object_class->finalize    = gimp_dashboard_finalize;
1020 
1021   widget_class->map         = gimp_dashboard_map;
1022   widget_class->unmap       = gimp_dashboard_unmap;
1023 }
1024 
1025 static void
gimp_dashboard_init(GimpDashboard * dashboard)1026 gimp_dashboard_init (GimpDashboard *dashboard)
1027 {
1028   GimpDashboardPrivate *priv;
1029   GtkWidget            *box;
1030   GtkWidget            *scrolled_window;
1031   GtkWidget            *viewport;
1032   GtkWidget            *vbox;
1033   GtkWidget            *expander;
1034   GtkWidget            *hbox;
1035   GtkWidget            *button;
1036   GtkWidget            *image;
1037   GtkWidget            *menu;
1038   GtkWidget            *item;
1039   GtkWidget            *frame;
1040   GtkWidget            *vbox2;
1041   GtkWidget            *meter;
1042   GtkWidget            *table;
1043   GtkWidget            *label;
1044   gint                  content_spacing;
1045   Group                 group;
1046   gint                  field;
1047 
1048   priv = dashboard->priv = gimp_dashboard_get_instance_private (dashboard);
1049 
1050   g_mutex_init (&priv->mutex);
1051   g_cond_init (&priv->cond);
1052 
1053   priv->update_interval        = DEFAULT_UPDATE_INTERVAL;
1054   priv->history_duration       = DEFAULT_HISTORY_DURATION;
1055   priv->low_swap_space_warning = DEFAULT_LOW_SWAP_SPACE_WARNING;
1056 
1057   gtk_widget_style_get (GTK_WIDGET (dashboard),
1058                         "content-spacing", &content_spacing,
1059                         NULL);
1060 
1061   /* we put the dashboard inside an event box, so that it gets its own window,
1062    * which reduces the overhead of updating the ui, since it gets updated
1063    * frequently.  unfortunately, this means that the dashboard's background
1064    * color may be a bit off for some themes.
1065    */
1066   box = gtk_event_box_new ();
1067   gtk_box_pack_start (GTK_BOX (dashboard), box, TRUE, TRUE, 0);
1068   gtk_widget_show (box);
1069 
1070   /* scrolled window */
1071   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
1072   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
1073                                   GTK_POLICY_AUTOMATIC,
1074                                   GTK_POLICY_AUTOMATIC);
1075   gtk_container_add (GTK_CONTAINER (box), scrolled_window);
1076   gtk_widget_show (scrolled_window);
1077 
1078   /* viewport */
1079   viewport = gtk_viewport_new (
1080     gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled_window)),
1081     gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)));
1082   gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
1083   gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
1084   gtk_widget_show (viewport);
1085 
1086   /* main vbox */
1087   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing);
1088   gtk_container_add (GTK_CONTAINER (viewport), vbox);
1089   gtk_widget_show (vbox);
1090 
1091   /* construct the groups */
1092   for (group = FIRST_GROUP; group < N_GROUPS; group++)
1093     {
1094       const GroupInfo *group_info = &groups[group];
1095       GroupData       *group_data = &priv->groups[group];
1096 
1097       group_data->n_fields       = 0;
1098       group_data->n_meter_values = 0;
1099 
1100       for (field = 0; group_info->fields[field].variable; field++)
1101         {
1102           const FieldInfo *field_info = &group_info->fields[field];
1103 
1104           group_data->n_fields++;
1105           group_data->n_meter_values = MAX (group_data->n_meter_values,
1106                                             field_info->meter_value);
1107         }
1108 
1109       group_data->fields = g_new0 (FieldData, group_data->n_fields);
1110 
1111       /* group expander */
1112       expander = gtk_expander_new (NULL);
1113       group_data->expander = GTK_EXPANDER (expander);
1114       gtk_expander_set_expanded (GTK_EXPANDER (expander),
1115                                  group_info->default_expanded);
1116       gtk_expander_set_label_fill (GTK_EXPANDER (expander), TRUE);
1117       gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0);
1118 
1119       g_object_set_data (G_OBJECT (expander),
1120                          "gimp-dashboard-group", GINT_TO_POINTER (group));
1121       g_signal_connect_swapped (expander, "button-press-event",
1122                                 G_CALLBACK (gimp_dashboard_group_expander_button_press),
1123                                 dashboard);
1124 
1125       /* group expander label box */
1126       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1127       gimp_help_set_help_data (hbox,
1128                                g_dgettext (NULL, group_info->description),
1129                                NULL);
1130       gtk_expander_set_label_widget (GTK_EXPANDER (expander), hbox);
1131       gtk_widget_show (hbox);
1132 
1133       /* group expander label */
1134       label = gtk_label_new (g_dpgettext2 (NULL, "dashboard-group",
1135                                            group_info->title));
1136       gtk_label_set_xalign (GTK_LABEL (label), 0.0);
1137       gimp_label_set_attributes (GTK_LABEL (label),
1138                                  PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD,
1139                                  -1);
1140       gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1141       gtk_widget_show (label);
1142 
1143       /* group expander values label */
1144       label = gtk_label_new (NULL);
1145       group_data->header_values_label = GTK_LABEL (label);
1146       gtk_label_set_xalign (GTK_LABEL (label), 0.0);
1147       gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 4);
1148 
1149       g_object_bind_property (expander, "expanded",
1150                               label,    "visible",
1151                               G_BINDING_SYNC_CREATE |
1152                               G_BINDING_INVERT_BOOLEAN);
1153 
1154       /* group expander menu button */
1155       button = gtk_button_new ();
1156       group_data->menu_button = GTK_BUTTON (button);
1157       gimp_help_set_help_data (button, _("Select fields"),
1158                                NULL);
1159       gtk_widget_set_can_focus (button, FALSE);
1160       gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
1161       gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
1162       gtk_widget_show (button);
1163 
1164       image = gtk_image_new_from_icon_name (GIMP_ICON_MENU_LEFT,
1165                                             GTK_ICON_SIZE_MENU);
1166       gtk_image_set_pixel_size (GTK_IMAGE (image), 12);
1167       gtk_image_set_from_icon_name (GTK_IMAGE (image), GIMP_ICON_MENU_LEFT,
1168                                     GTK_ICON_SIZE_MENU);
1169       gtk_container_add (GTK_CONTAINER (button), image);
1170       gtk_widget_show (image);
1171 
1172       /* group menu */
1173       menu = gtk_menu_new ();
1174       group_data->menu = GTK_MENU (menu);
1175       gtk_menu_attach_to_widget (GTK_MENU (menu), button, NULL);
1176 
1177       for (field = 0; field < group_data->n_fields; field++)
1178         {
1179           const FieldInfo *field_info = &group_info->fields[field];
1180           FieldData       *field_data = &group_data->fields[field];
1181 
1182           if (field_info->variable != VARIABLE_SEPARATOR)
1183             {
1184               const VariableInfo *variable_info = &variables[field_info->variable];
1185 
1186               item = gtk_check_menu_item_new_with_label (
1187                 g_dpgettext2 (NULL, "dashboard-variable",
1188                               field_info->title ? field_info->title :
1189                                                   variable_info->title));
1190               field_data->menu_item = GTK_CHECK_MENU_ITEM (item);
1191               gimp_help_set_help_data (item,
1192                                        g_dgettext (NULL, variable_info->description),
1193                                        NULL);
1194 
1195               g_object_set_data (G_OBJECT (item),
1196                                  "gimp-dashboard-group", GINT_TO_POINTER (group));
1197               g_object_set_data (G_OBJECT (item),
1198                                  "gimp-dashboard-field", GINT_TO_POINTER (field));
1199               g_signal_connect_swapped (item, "toggled",
1200                                         G_CALLBACK (gimp_dashboard_field_menu_item_toggled),
1201                                         dashboard);
1202 
1203               gimp_dashboard_field_set_active (dashboard, group, field,
1204                                                field_info->default_active);
1205             }
1206           else
1207             {
1208               item = gtk_separator_menu_item_new ();
1209             }
1210 
1211           gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1212           gtk_widget_show (item);
1213         }
1214 
1215       /* group frame */
1216       frame = gimp_frame_new (NULL);
1217       gtk_container_add (GTK_CONTAINER (expander), frame);
1218       gtk_widget_show (frame);
1219 
1220       /* group vbox */
1221       vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2 * content_spacing);
1222       gtk_container_add (GTK_CONTAINER (frame), vbox2);
1223       gtk_widget_show (vbox2);
1224 
1225       /* group meter */
1226       if (group_info->has_meter)
1227         {
1228           meter = gimp_meter_new (group_data->n_meter_values);
1229           group_data->meter = GIMP_METER (meter);
1230           gimp_help_set_help_data (meter,
1231                                    g_dgettext (NULL, group_info->description),
1232                                    NULL);
1233           gimp_meter_set_history_resolution (GIMP_METER (meter),
1234                                              priv->update_interval / 1000.0);
1235           gimp_meter_set_history_duration (GIMP_METER (meter),
1236                                            priv->history_duration / 1000.0);
1237           gtk_box_pack_start (GTK_BOX (vbox2), meter, FALSE, FALSE, 0);
1238           gtk_widget_show (meter);
1239 
1240           for (field = 0; field < group_data->n_fields; field++)
1241             {
1242               const FieldInfo *field_info = &group_info->fields[field];
1243 
1244               if (field_info->meter_value)
1245                 {
1246                   const VariableInfo *variable_info = &variables[field_info->variable];
1247 
1248                   gimp_meter_set_value_color (GIMP_METER (meter),
1249                                               field_info->meter_value - 1,
1250                                               &variable_info->color);
1251 
1252                   if (gimp_dashboard_field_use_meter_underlay (group, field))
1253                     {
1254                       gimp_meter_set_value_show_in_gauge (GIMP_METER (meter),
1255                                                           field_info->meter_value - 1,
1256                                                           FALSE);
1257                       gimp_meter_set_value_interpolation (GIMP_METER (meter),
1258                                                           field_info->meter_value - 1,
1259                                                           GIMP_INTERPOLATION_NONE);
1260                     }
1261                 }
1262             }
1263         }
1264 
1265       /* group table */
1266       table = gtk_table_new (1, 1, FALSE);
1267       group_data->table = GTK_TABLE (table);
1268       gtk_table_set_row_spacings (GTK_TABLE (table), content_spacing);
1269       gtk_table_set_col_spacings (GTK_TABLE (table), 4);
1270       gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0);
1271       gtk_widget_show (table);
1272 
1273       gimp_dashboard_group_set_active (dashboard, group,
1274                                        group_info->default_active);
1275       gimp_dashboard_update_group (dashboard, group);
1276     }
1277 
1278   /* sampler thread
1279    *
1280    * we use a separate thread for sampling, so that data is sampled even when
1281    * the main thread is busy
1282    */
1283   priv->thread = g_thread_new ("dashboard",
1284                                (GThreadFunc) gimp_dashboard_sample,
1285                                dashboard);
1286 }
1287 
1288 static void
gimp_dashboard_docked_iface_init(GimpDockedInterface * iface)1289 gimp_dashboard_docked_iface_init (GimpDockedInterface *iface)
1290 {
1291   parent_docked_iface = g_type_interface_peek_parent (iface);
1292 
1293   if (! parent_docked_iface)
1294     parent_docked_iface = g_type_default_interface_peek (GIMP_TYPE_DOCKED);
1295 
1296   iface->set_aux_info = gimp_dashboard_set_aux_info;
1297   iface->get_aux_info = gimp_dashboard_get_aux_info;
1298 }
1299 
1300 static void
gimp_dashboard_constructed(GObject * object)1301 gimp_dashboard_constructed (GObject *object)
1302 {
1303   GimpDashboard        *dashboard = GIMP_DASHBOARD (object);
1304   GimpDashboardPrivate *priv = dashboard->priv;
1305   GimpUIManager        *ui_manager;
1306   GimpActionGroup      *action_group;
1307   GimpAction           *action;
1308   GtkWidget            *button;
1309   GtkWidget            *alignment;
1310   GtkWidget            *box;
1311   GtkWidget            *image;
1312   GtkWidget            *label;
1313   Group                 group;
1314 
1315   G_OBJECT_CLASS (parent_class)->constructed (object);
1316 
1317   ui_manager   = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
1318   action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
1319 
1320   /* group actions */
1321   for (group = FIRST_GROUP; group < N_GROUPS; group++)
1322     {
1323       const GroupInfo       *group_info = &groups[group];
1324       GroupData             *group_data = &priv->groups[group];
1325       GimpToggleActionEntry  entry      = {};
1326 
1327       entry.name      = g_strdup_printf ("dashboard-group-%s", group_info->name);
1328       entry.label     = g_dpgettext2 (NULL, "dashboard-group", group_info->title);
1329       entry.tooltip   = g_dgettext (NULL, group_info->description);
1330       entry.help_id   = GIMP_HELP_DASHBOARD_GROUPS;
1331       entry.is_active = group_data->active;
1332 
1333       gimp_action_group_add_toggle_actions (action_group, "dashboard-groups",
1334                                             &entry, 1);
1335 
1336       action = gimp_ui_manager_find_action (ui_manager, "dashboard", entry.name);
1337       group_data->action = GIMP_TOGGLE_ACTION (action);
1338 
1339       g_object_set_data (G_OBJECT (action),
1340                          "gimp-dashboard-group", GINT_TO_POINTER (group));
1341       g_signal_connect_swapped (action, "toggled",
1342                                 G_CALLBACK (gimp_dashboard_group_action_toggled),
1343                                 dashboard);
1344 
1345       g_free ((gpointer) entry.name);
1346     }
1347 
1348   button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
1349                                           "dashboard-log-record", NULL);
1350   priv->log_record_button = GIMP_HIGHLIGHTABLE_BUTTON (button);
1351   gimp_highlightable_button_set_highlight_color (
1352     GIMP_HIGHLIGHTABLE_BUTTON (button),
1353     GIMP_HIGHLIGHTABLE_BUTTON_COLOR_AFFIRMATIVE);
1354 
1355   button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
1356                                           "dashboard-log-add-marker",
1357                                           "dashboard-log-add-empty-marker",
1358                                           gimp_get_extend_selection_mask (),
1359                                           NULL);
1360 
1361   action = gimp_action_group_get_action (action_group,
1362                                          "dashboard-log-add-marker");
1363   g_object_bind_property (action, "sensitive",
1364                           button, "visible",
1365                           G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1366 
1367   image = g_object_ref (gtk_bin_get_child (GTK_BIN (button)));
1368   gtk_container_remove (GTK_CONTAINER (button), image);
1369 
1370   alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
1371   gtk_container_add (GTK_CONTAINER (button), alignment);
1372   gtk_widget_show (alignment);
1373 
1374   box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
1375   gtk_container_add (GTK_CONTAINER (alignment), box);
1376   gtk_widget_show (box);
1377 
1378   gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
1379   g_object_unref (image);
1380 
1381   label = gtk_label_new (NULL);
1382   priv->log_add_marker_label = GTK_LABEL (label);
1383   gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
1384   gtk_widget_show (label);
1385 
1386   button = gimp_editor_add_action_button (GIMP_EDITOR (dashboard), "dashboard",
1387                                           "dashboard-reset", NULL);
1388 
1389   action = gimp_action_group_get_action (action_group,
1390                                          "dashboard-reset");
1391   g_object_bind_property (action, "sensitive",
1392                           button, "visible",
1393                           G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1394 
1395   gimp_action_group_update (action_group, dashboard);
1396 }
1397 
1398 static void
gimp_dashboard_dispose(GObject * object)1399 gimp_dashboard_dispose (GObject *object)
1400 {
1401   GimpDashboard        *dashboard = GIMP_DASHBOARD (object);
1402   GimpDashboardPrivate *priv      = dashboard->priv;
1403 
1404   if (priv->thread)
1405     {
1406       g_mutex_lock (&priv->mutex);
1407 
1408       priv->quit = TRUE;
1409       g_cond_signal (&priv->cond);
1410 
1411       g_mutex_unlock (&priv->mutex);
1412 
1413       g_clear_pointer (&priv->thread, g_thread_join);
1414     }
1415 
1416   if (priv->update_idle_id)
1417     {
1418       g_source_remove (priv->update_idle_id);
1419       priv->update_idle_id = 0;
1420     }
1421 
1422   if (priv->low_swap_space_idle_id)
1423     {
1424       g_source_remove (priv->low_swap_space_idle_id);
1425       priv->low_swap_space_idle_id = 0;
1426     }
1427 
1428   gimp_dashboard_log_stop_recording (dashboard, NULL);
1429 
1430   gimp_dashboard_reset_variables (dashboard);
1431 
1432   G_OBJECT_CLASS (parent_class)->dispose (object);
1433 }
1434 
1435 static void
gimp_dashboard_finalize(GObject * object)1436 gimp_dashboard_finalize (GObject *object)
1437 {
1438   GimpDashboard        *dashboard = GIMP_DASHBOARD (object);
1439   GimpDashboardPrivate *priv      = dashboard->priv;
1440   gint                  i;
1441 
1442   for (i = FIRST_GROUP; i < N_GROUPS; i++)
1443     g_free (priv->groups[i].fields);
1444 
1445   g_mutex_clear (&priv->mutex);
1446   g_cond_clear (&priv->cond);
1447 
1448   G_OBJECT_CLASS (parent_class)->finalize (object);
1449 }
1450 
1451 static void
gimp_dashboard_map(GtkWidget * widget)1452 gimp_dashboard_map (GtkWidget *widget)
1453 {
1454   GimpDashboard *dashboard = GIMP_DASHBOARD (widget);
1455 
1456   GTK_WIDGET_CLASS (parent_class)->map (widget);
1457 
1458   gimp_dashboard_update (dashboard);
1459 }
1460 
1461 static void
gimp_dashboard_unmap(GtkWidget * widget)1462 gimp_dashboard_unmap (GtkWidget *widget)
1463 {
1464   GimpDashboard        *dashboard = GIMP_DASHBOARD (widget);
1465   GimpDashboardPrivate *priv      = dashboard->priv;
1466 
1467   g_mutex_lock (&priv->mutex);
1468 
1469   if (priv->update_idle_id)
1470     {
1471       g_source_remove (priv->update_idle_id);
1472       priv->update_idle_id = 0;
1473     }
1474 
1475   g_mutex_unlock (&priv->mutex);
1476 
1477   GTK_WIDGET_CLASS (parent_class)->unmap (widget);
1478 }
1479 
1480 #define AUX_INFO_UPDATE_INTERVAL        "update-interval"
1481 #define AUX_INFO_HISTORY_DURATION       "history-duration"
1482 #define AUX_INFO_LOW_SWAP_SPACE_WARNING "low-swap-space-warning"
1483 
1484 static void
gimp_dashboard_set_aux_info(GimpDocked * docked,GList * aux_info)1485 gimp_dashboard_set_aux_info (GimpDocked *docked,
1486                              GList      *aux_info)
1487 {
1488   GimpDashboard        *dashboard = GIMP_DASHBOARD (docked);
1489   GimpDashboardPrivate *priv      = dashboard->priv;
1490   gchar                *name;
1491   GList                *list;
1492 
1493   parent_docked_iface->set_aux_info (docked, aux_info);
1494 
1495   for (list = aux_info; list; list = g_list_next (list))
1496     {
1497       GimpSessionInfoAux *aux = list->data;
1498 
1499       if (! strcmp (aux->name, AUX_INFO_UPDATE_INTERVAL))
1500         {
1501           gint                       value = atoi (aux->value);
1502           GimpDashboardUpdateInteval update_interval;
1503 
1504           for (update_interval = GIMP_DASHBOARD_UPDATE_INTERVAL_0_25_SEC;
1505                update_interval < value &&
1506                update_interval < GIMP_DASHBOARD_UPDATE_INTERVAL_4_SEC;
1507                update_interval *= 2);
1508 
1509           gimp_dashboard_set_update_interval (dashboard, update_interval);
1510         }
1511       else if (! strcmp (aux->name, AUX_INFO_HISTORY_DURATION))
1512         {
1513           gint                         value = atoi (aux->value);
1514           GimpDashboardHistoryDuration history_duration;
1515 
1516           for (history_duration = GIMP_DASHBOARD_HISTORY_DURATION_15_SEC;
1517                history_duration < value &&
1518                history_duration < GIMP_DASHBOARD_HISTORY_DURATION_240_SEC;
1519                history_duration *= 2);
1520 
1521           gimp_dashboard_set_history_duration (dashboard, history_duration);
1522         }
1523       else if (! strcmp (aux->name, AUX_INFO_LOW_SWAP_SPACE_WARNING))
1524         {
1525           gimp_dashboard_set_low_swap_space_warning (dashboard,
1526                                                      ! strcmp (aux->value, "yes"));
1527         }
1528       else
1529         {
1530           Group group;
1531           gint  field;
1532 
1533           for (group = FIRST_GROUP; group < N_GROUPS; group++)
1534             {
1535               const GroupInfo *group_info = &groups[group];
1536               GroupData       *group_data = &priv->groups[group];
1537 
1538               name = g_strdup_printf ("%s-active", group_info->name);
1539 
1540               if (! strcmp (aux->name, name))
1541                 {
1542                   gboolean active = ! strcmp (aux->value, "yes");
1543 
1544                   gimp_dashboard_group_set_active (dashboard, group, active);
1545 
1546                   g_free (name);
1547                   goto next_aux_info;
1548                 }
1549 
1550               g_free (name);
1551 
1552               name = g_strdup_printf ("%s-expanded", group_info->name);
1553 
1554               if (! strcmp (aux->name, name))
1555                 {
1556                   gboolean expanded = ! strcmp (aux->value, "yes");
1557 
1558                   gtk_expander_set_expanded (group_data->expander, expanded);
1559 
1560                   g_free (name);
1561                   goto next_aux_info;
1562                 }
1563 
1564               g_free (name);
1565 
1566               for (field = 0; field < group_data->n_fields; field++)
1567                 {
1568                   const FieldInfo *field_info = &group_info->fields[field];
1569 
1570                   if (field_info->variable != VARIABLE_SEPARATOR)
1571                     {
1572                       const VariableInfo *variable_info = &variables[field_info->variable];
1573 
1574                       name = g_strdup_printf ("%s-%s-active",
1575                                               group_info->name,
1576                                               variable_info->name);
1577 
1578                       if (! strcmp (aux->name, name))
1579                         {
1580                           gboolean active = ! strcmp (aux->value, "yes");
1581 
1582                           gimp_dashboard_field_set_active (dashboard,
1583                                                            group, field,
1584                                                            active);
1585 
1586                           g_free (name);
1587                           goto next_aux_info;
1588                         }
1589 
1590                       g_free (name);
1591                     }
1592                 }
1593             }
1594         }
1595 next_aux_info: ;
1596     }
1597 
1598   gimp_dashboard_update_groups (dashboard);
1599 }
1600 
1601 static GList *
gimp_dashboard_get_aux_info(GimpDocked * docked)1602 gimp_dashboard_get_aux_info (GimpDocked *docked)
1603 {
1604   GimpDashboard        *dashboard = GIMP_DASHBOARD (docked);
1605   GimpDashboardPrivate *priv      = dashboard->priv;
1606   GList                *aux_info;
1607   GimpSessionInfoAux   *aux;
1608   gchar                *name;
1609   gchar                *value;
1610   Group                 group;
1611   gint                  field;
1612 
1613   aux_info = parent_docked_iface->get_aux_info (docked);
1614 
1615   if (priv->update_interval != DEFAULT_UPDATE_INTERVAL)
1616     {
1617       value    = g_strdup_printf ("%d", priv->update_interval);
1618       aux      = gimp_session_info_aux_new (AUX_INFO_UPDATE_INTERVAL, value);
1619       aux_info = g_list_append (aux_info, aux);
1620       g_free (value);
1621     }
1622 
1623   if (priv->history_duration != DEFAULT_HISTORY_DURATION)
1624     {
1625       value    = g_strdup_printf ("%d", priv->history_duration);
1626       aux      = gimp_session_info_aux_new (AUX_INFO_HISTORY_DURATION, value);
1627       aux_info = g_list_append (aux_info, aux);
1628       g_free (value);
1629     }
1630 
1631   if (priv->low_swap_space_warning != DEFAULT_LOW_SWAP_SPACE_WARNING)
1632     {
1633       value    = priv->low_swap_space_warning ? "yes" : "no";
1634       aux      = gimp_session_info_aux_new (AUX_INFO_LOW_SWAP_SPACE_WARNING, value);
1635       aux_info = g_list_append (aux_info, aux);
1636     }
1637 
1638   for (group = FIRST_GROUP; group < N_GROUPS; group++)
1639     {
1640       const GroupInfo *group_info = &groups[group];
1641       GroupData       *group_data = &priv->groups[group];
1642       gboolean         active     = group_data->active;
1643       gboolean         expanded   = gtk_expander_get_expanded (group_data->expander);
1644 
1645       if (active != group_info->default_active)
1646         {
1647           name     = g_strdup_printf ("%s-active", group_info->name);
1648           value    = active ? "yes" : "no";
1649           aux      = gimp_session_info_aux_new (name, value);
1650           aux_info = g_list_append (aux_info, aux);
1651           g_free (name);
1652         }
1653 
1654       if (expanded != group_info->default_expanded)
1655         {
1656           name     = g_strdup_printf ("%s-expanded", group_info->name);
1657           value    = expanded ? "yes" : "no";
1658           aux      = gimp_session_info_aux_new (name, value);
1659           aux_info = g_list_append (aux_info, aux);
1660           g_free (name);
1661         }
1662 
1663       for (field = 0; field < group_data->n_fields; field++)
1664         {
1665           const FieldInfo *field_info = &group_info->fields[field];
1666           FieldData       *field_data = &group_data->fields[field];
1667           gboolean         active     = field_data->active;
1668 
1669           if (field_info->variable != VARIABLE_SEPARATOR)
1670             {
1671               const VariableInfo *variable_info = &variables[field_info->variable];
1672 
1673               if (active != field_info->default_active)
1674                 {
1675                   name = g_strdup_printf ("%s-%s-active",
1676                                           group_info->name,
1677                                           variable_info->name);
1678                   value    = active ? "yes" : "no";
1679                   aux      = gimp_session_info_aux_new (name, value);
1680                   aux_info = g_list_append (aux_info, aux);
1681                   g_free (name);
1682                 }
1683             }
1684         }
1685     }
1686 
1687   return aux_info;
1688 }
1689 
1690 static gboolean
gimp_dashboard_group_expander_button_press(GimpDashboard * dashboard,GdkEventButton * bevent,GtkWidget * widget)1691 gimp_dashboard_group_expander_button_press (GimpDashboard  *dashboard,
1692                                             GdkEventButton *bevent,
1693                                             GtkWidget      *widget)
1694 {
1695   GimpDashboardPrivate *priv = dashboard->priv;
1696   Group                 group;
1697   GroupData            *group_data;
1698   GtkAllocation         expander_allocation;
1699   GtkAllocation         allocation;
1700 
1701   group      = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget),
1702                                                    "gimp-dashboard-group"));
1703   group_data = &priv->groups[group];
1704 
1705   gtk_widget_get_allocation (GTK_WIDGET (group_data->expander),
1706                              &expander_allocation);
1707   gtk_widget_get_allocation (GTK_WIDGET (group_data->menu_button),
1708                              &allocation);
1709 
1710   allocation.x -= expander_allocation.x;
1711   allocation.y -= expander_allocation.y;
1712 
1713   if (bevent->button == 1                          &&
1714       bevent->x >= allocation.x                    &&
1715       bevent->x <  allocation.x + allocation.width &&
1716       bevent->y >= allocation.y                    &&
1717       bevent->y <  allocation.y + allocation.height)
1718     {
1719       gtk_menu_popup (group_data->menu,
1720                       NULL, NULL,
1721                       gimp_dashboard_group_menu_position,
1722                       group_data->menu_button,
1723                       bevent->button, bevent->time);
1724 
1725       return TRUE;
1726     }
1727 
1728   return FALSE;
1729 }
1730 
1731 static void
gimp_dashboard_group_action_toggled(GimpDashboard * dashboard,GimpToggleAction * action)1732 gimp_dashboard_group_action_toggled (GimpDashboard    *dashboard,
1733                                      GimpToggleAction *action)
1734 {
1735   GimpDashboardPrivate *priv = dashboard->priv;
1736   Group                 group;
1737   GroupData            *group_data;
1738 
1739   group      = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (action),
1740                                                    "gimp-dashboard-group"));
1741   group_data = &priv->groups[group];
1742 
1743   group_data->active = gimp_toggle_action_get_active (action);
1744 
1745   gimp_dashboard_update_group (dashboard, group);
1746 }
1747 
1748 static void
gimp_dashboard_field_menu_item_toggled(GimpDashboard * dashboard,GtkCheckMenuItem * item)1749 gimp_dashboard_field_menu_item_toggled (GimpDashboard    *dashboard,
1750                                         GtkCheckMenuItem *item)
1751 {
1752   GimpDashboardPrivate *priv = dashboard->priv;
1753   Group                 group;
1754   GroupData            *group_data;
1755   gint                  field;
1756   FieldData            *field_data;
1757 
1758   group      = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item),
1759                                                    "gimp-dashboard-group"));
1760   group_data = &priv->groups[group];
1761 
1762   field      = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item),
1763                                                    "gimp-dashboard-field"));
1764   field_data = &group_data->fields[field];
1765 
1766   field_data->active = gtk_check_menu_item_get_active (item);
1767 
1768   gimp_dashboard_update_group (dashboard, group);
1769 }
1770 
1771 static gpointer
gimp_dashboard_sample(GimpDashboard * dashboard)1772 gimp_dashboard_sample (GimpDashboard *dashboard)
1773 {
1774   GimpDashboardPrivate *priv                = dashboard->priv;
1775   gint64                last_sample_time    = 0;
1776   gint64                last_update_time    = 0;
1777   gboolean              seen_low_swap_space = FALSE;
1778 
1779   g_mutex_lock (&priv->mutex);
1780 
1781   while (! priv->quit)
1782     {
1783       gint64 update_interval;
1784       gint64 sample_interval;
1785       gint64 end_time;
1786 
1787       update_interval = priv->update_interval * G_TIME_SPAN_SECOND / 1000;
1788 
1789       if (priv->log_output)
1790         {
1791           sample_interval = G_TIME_SPAN_SECOND /
1792                             priv->log_params.sample_frequency;
1793         }
1794       else
1795         {
1796           sample_interval = update_interval;
1797         }
1798 
1799       end_time = last_sample_time + sample_interval;
1800 
1801       if (! g_cond_wait_until (&priv->cond, &priv->mutex, end_time) ||
1802           priv->update_now)
1803         {
1804           gint64   time;
1805           gboolean variables_changed = FALSE;
1806           Variable variable;
1807           Group    group;
1808           gint     field;
1809 
1810           time = g_get_monotonic_time ();
1811 
1812           /* sample all variables */
1813           for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
1814             {
1815               const VariableInfo *variable_info      = &variables[variable];
1816               const VariableData *variable_data      = &priv->variables[variable];
1817               VariableData        prev_variable_data = *variable_data;
1818 
1819               variable_info->sample_func (dashboard, variable);
1820 
1821               variables_changed = variables_changed ||
1822                                   memcmp (variable_data, &prev_variable_data,
1823                                           sizeof (VariableData));
1824             }
1825 
1826           /* log sample */
1827           if (priv->log_output)
1828             gimp_dashboard_log_sample (dashboard, variables_changed, FALSE);
1829 
1830           /* update gui */
1831           if (priv->update_now   ||
1832               ! priv->log_output ||
1833               time - last_update_time >= update_interval)
1834             {
1835               /* add samples to meters */
1836               for (group = FIRST_GROUP; group < N_GROUPS; group++)
1837                 {
1838                   const GroupInfo *group_info = &groups[group];
1839                   GroupData       *group_data = &priv->groups[group];
1840                   gdouble         *sample;
1841                   gdouble          total      = 0.0;
1842 
1843                   if (! group_info->has_meter)
1844                     continue;
1845 
1846                   sample = g_new (gdouble, group_data->n_meter_values);
1847 
1848                   for (field = 0; field < group_data->n_fields; field++)
1849                     {
1850                       const FieldInfo *field_info = &group_info->fields[field];
1851 
1852                       if (field_info->meter_value)
1853                         {
1854                           gdouble value;
1855 
1856                           if (field_info->meter_variable)
1857                             variable = field_info->meter_variable;
1858                           else
1859                             variable = field_info->variable;
1860 
1861                           value = gimp_dashboard_variable_to_double (dashboard,
1862                                                                      variable);
1863 
1864                           if (value &&
1865                               gimp_dashboard_field_use_meter_underlay (group,
1866                                                                        field))
1867                             {
1868                               value = G_MAXDOUBLE;
1869                             }
1870 
1871                           if (field_info->meter_cumulative)
1872                             {
1873                               total += value;
1874                               value  = total;
1875                             }
1876 
1877                           sample[field_info->meter_value - 1] = value;
1878                         }
1879                     }
1880 
1881                   gimp_meter_add_sample (group_data->meter, sample);
1882 
1883                   g_free (sample);
1884                 }
1885 
1886               if (variables_changed)
1887                 {
1888                   /* enqueue update source */
1889                   if (! priv->update_idle_id &&
1890                       gtk_widget_get_mapped (GTK_WIDGET (dashboard)))
1891                     {
1892                       priv->update_idle_id = g_idle_add_full (
1893                         G_PRIORITY_DEFAULT,
1894                         (GSourceFunc) gimp_dashboard_update,
1895                         dashboard, NULL);
1896                     }
1897 
1898                   /* check for low swap space */
1899                   if (priv->low_swap_space_warning                      &&
1900                       priv->variables[VARIABLE_SWAP_OCCUPIED].available &&
1901                       priv->variables[VARIABLE_SWAP_LIMIT].available)
1902                     {
1903                       guint64 swap_occupied;
1904                       guint64 swap_limit;
1905 
1906                       swap_occupied = priv->variables[VARIABLE_SWAP_OCCUPIED].value.size;
1907                       swap_limit    = priv->variables[VARIABLE_SWAP_LIMIT].value.size;
1908 
1909                       if (! seen_low_swap_space &&
1910                           swap_occupied >= LOW_SWAP_SPACE_WARNING_ON * swap_limit)
1911                         {
1912                           if (! priv->low_swap_space_idle_id)
1913                             {
1914                               priv->low_swap_space_idle_id =
1915                                 g_idle_add_full (G_PRIORITY_HIGH,
1916                                                  (GSourceFunc) gimp_dashboard_low_swap_space,
1917                                                  dashboard, NULL);
1918                             }
1919 
1920                           seen_low_swap_space = TRUE;
1921                         }
1922                       else if (seen_low_swap_space &&
1923                                swap_occupied <= LOW_SWAP_SPACE_WARNING_OFF * swap_limit)
1924                         {
1925                           if (priv->low_swap_space_idle_id)
1926                             {
1927                               g_source_remove (priv->low_swap_space_idle_id);
1928 
1929                               priv->low_swap_space_idle_id = 0;
1930                             }
1931 
1932                           seen_low_swap_space = FALSE;
1933                         }
1934                     }
1935                 }
1936 
1937               priv->update_now = FALSE;
1938 
1939               last_update_time = time;
1940             }
1941 
1942           last_sample_time = time;
1943         }
1944     }
1945 
1946   g_mutex_unlock (&priv->mutex);
1947 
1948   return NULL;
1949 }
1950 
1951 static gboolean
gimp_dashboard_update(GimpDashboard * dashboard)1952 gimp_dashboard_update (GimpDashboard *dashboard)
1953 {
1954   GimpDashboardPrivate *priv = dashboard->priv;
1955   Group                 group;
1956 
1957   g_mutex_lock (&priv->mutex);
1958 
1959   for (group = FIRST_GROUP; group < N_GROUPS; group++)
1960     gimp_dashboard_update_group_values (dashboard, group);
1961 
1962   priv->update_idle_id = 0;
1963 
1964   g_mutex_unlock (&priv->mutex);
1965 
1966   return G_SOURCE_REMOVE;
1967 }
1968 
1969 static gboolean
gimp_dashboard_low_swap_space(GimpDashboard * dashboard)1970 gimp_dashboard_low_swap_space (GimpDashboard *dashboard)
1971 {
1972   GimpDashboardPrivate *priv = dashboard->priv;
1973 
1974   if (priv->gimp)
1975     {
1976       GdkScreen *screen;
1977       gint       monitor;
1978       gint       field;
1979 
1980       gtk_expander_set_expanded (priv->groups[GROUP_SWAP].expander, TRUE);
1981 
1982       for (field = 0; field < priv->groups[GROUP_SWAP].n_fields; field++)
1983         {
1984           const FieldInfo *field_info = &groups[GROUP_SWAP].fields[field];
1985 
1986           if (field_info->variable == VARIABLE_SWAP_OCCUPIED ||
1987               field_info->variable == VARIABLE_SWAP_LIMIT)
1988             {
1989               gimp_dashboard_field_set_active (dashboard,
1990                                                GROUP_SWAP, field, TRUE);
1991             }
1992         }
1993 
1994       gimp_dashboard_update_groups (dashboard);
1995 
1996       monitor = gimp_get_monitor_at_pointer (&screen);
1997 
1998       gimp_window_strategy_show_dockable_dialog (
1999         GIMP_WINDOW_STRATEGY (gimp_get_window_strategy (priv->gimp)),
2000         priv->gimp,
2001         gimp_dialog_factory_get_singleton (),
2002         screen, monitor,
2003         "gimp-dashboard");
2004 
2005       g_mutex_lock (&priv->mutex);
2006 
2007       priv->low_swap_space_idle_id = 0;
2008 
2009       g_mutex_unlock (&priv->mutex);
2010     }
2011 
2012   return G_SOURCE_REMOVE;
2013 }
2014 
2015 static void
gimp_dashboard_sample_function(GimpDashboard * dashboard,Variable variable)2016 gimp_dashboard_sample_function (GimpDashboard *dashboard,
2017                                 Variable       variable)
2018 {
2019   GimpDashboardPrivate *priv          = dashboard->priv;
2020   const VariableInfo   *variable_info = &variables[variable];
2021   VariableData         *variable_data = &priv->variables[variable];
2022 
2023   #define CALL_FUNC(result_type) \
2024     (((result_type (*) (void)) variable_info->data) ())
2025 
2026   switch (variable_info->type)
2027     {
2028     case VARIABLE_TYPE_BOOLEAN:
2029       variable_data->value.boolean = CALL_FUNC (gboolean);
2030       break;
2031 
2032     case VARIABLE_TYPE_INTEGER:
2033       variable_data->value.integer = CALL_FUNC (gint);
2034 
2035     case VARIABLE_TYPE_SIZE:
2036       variable_data->value.size = CALL_FUNC (guint64);
2037       break;
2038 
2039     case VARIABLE_TYPE_PERCENTAGE:
2040       variable_data->value.percentage = CALL_FUNC (gdouble);
2041       break;
2042 
2043     case VARIABLE_TYPE_DURATION:
2044       variable_data->value.duration = CALL_FUNC (gdouble);
2045       break;
2046 
2047     case VARIABLE_TYPE_RATE_OF_CHANGE:
2048       variable_data->value.rate_of_change = CALL_FUNC (gdouble);
2049       break;
2050 
2051     case VARIABLE_TYPE_SIZE_RATIO:
2052     case VARIABLE_TYPE_INT_RATIO:
2053       g_return_if_reached ();
2054       break;
2055     }
2056 
2057   #undef CALL_FUNC
2058 
2059   variable_data->available = TRUE;
2060 }
2061 
2062 static void
gimp_dashboard_sample_gegl_config(GimpDashboard * dashboard,Variable variable)2063 gimp_dashboard_sample_gegl_config (GimpDashboard *dashboard,
2064                                    Variable       variable)
2065 {
2066   gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_config ()), variable);
2067 }
2068 
2069 static void
gimp_dashboard_sample_gegl_stats(GimpDashboard * dashboard,Variable variable)2070 gimp_dashboard_sample_gegl_stats (GimpDashboard *dashboard,
2071                                   Variable       variable)
2072 {
2073   gimp_dashboard_sample_object (dashboard, G_OBJECT (gegl_stats ()), variable);
2074 }
2075 
2076 static void
gimp_dashboard_sample_variable_changed(GimpDashboard * dashboard,Variable variable)2077 gimp_dashboard_sample_variable_changed (GimpDashboard *dashboard,
2078                                         Variable       variable)
2079 {
2080   GimpDashboardPrivate *priv          = dashboard->priv;
2081   const VariableInfo   *variable_info = &variables[variable];
2082   VariableData         *variable_data = &priv->variables[variable];
2083   Variable              var           = GPOINTER_TO_INT (variable_info->data);
2084   const VariableData   *var_data      = &priv->variables[var];
2085   gpointer              prev_value    = gimp_dashboard_variable_get_data (
2086                                           dashboard, variable,
2087                                           sizeof (var_data->value));
2088 
2089   if (var_data->available)
2090     {
2091       variable_data->available     = TRUE;
2092       variable_data->value.boolean = memcmp (&var_data->value, prev_value,
2093                                              sizeof (var_data->value)) != 0;
2094 
2095       if (variable_data->value.boolean)
2096         memcpy (prev_value, &var_data->value, sizeof (var_data->value));
2097     }
2098   else
2099     {
2100       variable_data->available     = FALSE;
2101     }
2102 }
2103 
2104 static void
gimp_dashboard_sample_variable_rate_of_change(GimpDashboard * dashboard,Variable variable)2105 gimp_dashboard_sample_variable_rate_of_change (GimpDashboard *dashboard,
2106                                                Variable       variable)
2107 {
2108   typedef struct
2109   {
2110     gint64   last_time;
2111     gboolean last_available;
2112     gdouble  last_value;
2113   } Data;
2114 
2115   GimpDashboardPrivate *priv          = dashboard->priv;
2116   const VariableInfo   *variable_info = &variables[variable];
2117   VariableData         *variable_data = &priv->variables[variable];
2118   Variable              var           = GPOINTER_TO_INT (variable_info->data);
2119   const VariableData   *var_data      = &priv->variables[var];
2120   Data                 *data          = gimp_dashboard_variable_get_data (
2121                                           dashboard, variable, sizeof (Data));
2122   gint64                time;
2123 
2124   time = g_get_monotonic_time ();
2125 
2126   if (time == data->last_time)
2127     return;
2128 
2129   variable_data->available = FALSE;
2130 
2131   if (var_data->available)
2132     {
2133       gdouble value = gimp_dashboard_variable_to_double (dashboard, var);
2134 
2135       if (data->last_available)
2136         {
2137           variable_data->available = TRUE;
2138           variable_data->value.rate_of_change = (value - data->last_value) *
2139                                                 G_TIME_SPAN_SECOND         /
2140                                                 (time - data->last_time);
2141         }
2142 
2143       data->last_value = value;
2144     }
2145 
2146   data->last_time      = time;
2147   data->last_available = var_data->available;
2148 }
2149 
2150 static void
gimp_dashboard_sample_swap_limit(GimpDashboard * dashboard,Variable variable)2151 gimp_dashboard_sample_swap_limit (GimpDashboard *dashboard,
2152                                   Variable       variable)
2153 {
2154   typedef struct
2155   {
2156     guint64  free_space;
2157     gboolean has_free_space;
2158     gint64   last_check_time;
2159   } Data;
2160 
2161   GimpDashboardPrivate *priv          = dashboard->priv;
2162   VariableData         *variable_data = &priv->variables[variable];
2163   Data                 *data          = gimp_dashboard_variable_get_data (
2164                                           dashboard, variable, sizeof (Data));
2165   gint64                time;
2166 
2167   /* we don't have a config option for limiting the swap size, so we simply
2168    * return the free space available on the filesystem containing the swap
2169    */
2170 
2171   time = g_get_monotonic_time ();
2172 
2173   if (time - data->last_check_time >= G_TIME_SPAN_SECOND)
2174     {
2175       gchar *swap_dir;
2176 
2177       g_object_get (gegl_config (),
2178                     "swap", &swap_dir,
2179                     NULL);
2180 
2181       data->free_space     = 0;
2182       data->has_free_space = FALSE;
2183 
2184       if (swap_dir)
2185         {
2186           GFile     *file;
2187           GFileInfo *info;
2188 
2189           file = g_file_new_for_path (swap_dir);
2190 
2191           info = g_file_query_filesystem_info (file,
2192                                                G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
2193                                                NULL, NULL);
2194 
2195           if (info)
2196             {
2197               data->free_space     =
2198                 g_file_info_get_attribute_uint64 (info,
2199                                                   G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
2200               data->has_free_space = TRUE;
2201 
2202               g_object_unref (info);
2203             }
2204 
2205           g_object_unref (file);
2206 
2207           g_free (swap_dir);
2208         }
2209 
2210       data->last_check_time = time;
2211     }
2212 
2213   variable_data->available = data->has_free_space;
2214 
2215   if (data->has_free_space)
2216     {
2217       variable_data->value.size = data->free_space;
2218 
2219       if (priv->variables[VARIABLE_SWAP_SIZE].available)
2220         {
2221           /* the swap limit is the sum of free_space and swap_size, since the
2222            * swap itself occupies space in the filesystem
2223            */
2224           variable_data->value.size +=
2225             priv->variables[VARIABLE_SWAP_SIZE].value.size;
2226         }
2227     }
2228 }
2229 
2230 #ifdef HAVE_CPU_GROUP
2231 
2232 #ifdef HAVE_SYS_TIMES_H
2233 
2234 static void
gimp_dashboard_sample_cpu_usage(GimpDashboard * dashboard,Variable variable)2235 gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
2236                                  Variable       variable)
2237 {
2238   typedef struct
2239   {
2240     clock_t prev_clock;
2241     clock_t prev_usage;
2242   } Data;
2243 
2244   GimpDashboardPrivate *priv          = dashboard->priv;
2245   VariableData         *variable_data = &priv->variables[variable];
2246   Data                 *data          = gimp_dashboard_variable_get_data (
2247                                           dashboard, variable, sizeof (Data));
2248   clock_t               curr_clock;
2249   clock_t               curr_usage;
2250   struct tms            tms;
2251 
2252   curr_clock = times (&tms);
2253 
2254   if (curr_clock == (clock_t) -1)
2255     {
2256       data->prev_clock = 0;
2257 
2258       variable_data->available = FALSE;
2259 
2260       return;
2261     }
2262 
2263   curr_usage = tms.tms_utime + tms.tms_stime;
2264 
2265   if (data->prev_clock && curr_clock != data->prev_clock)
2266     {
2267       variable_data->available         = TRUE;
2268       variable_data->value.percentage  = (gdouble) (curr_usage - data->prev_usage) /
2269                                                    (curr_clock - data->prev_clock);
2270       variable_data->value.percentage /= g_get_num_processors ();
2271     }
2272   else
2273     {
2274       variable_data->available         = FALSE;
2275     }
2276 
2277   data->prev_clock = curr_clock;
2278   data->prev_usage = curr_usage;
2279 }
2280 
2281 #elif defined (G_OS_WIN32)
2282 
2283 static void
gimp_dashboard_sample_cpu_usage(GimpDashboard * dashboard,Variable variable)2284 gimp_dashboard_sample_cpu_usage (GimpDashboard *dashboard,
2285                                  Variable       variable)
2286 {
2287   typedef struct
2288   {
2289     guint64 prev_time;
2290     guint64 prev_usage;
2291   } Data;
2292 
2293   GimpDashboardPrivate *priv            = dashboard->priv;
2294   VariableData         *variable_data   = &priv->variables[variable];
2295   Data                 *data            = gimp_dashboard_variable_get_data (
2296                                             dashboard, variable, sizeof (Data));
2297   guint64               curr_time;
2298   guint64               curr_usage;
2299   FILETIME              system_time;
2300   FILETIME              process_creation_time;
2301   FILETIME              process_exit_time;
2302   FILETIME              process_kernel_time;
2303   FILETIME              process_user_time;
2304 
2305   if (! GetProcessTimes (GetCurrentProcess (),
2306                          &process_creation_time,
2307                          &process_exit_time,
2308                          &process_kernel_time,
2309                          &process_user_time))
2310     {
2311       data->prev_time = 0;
2312 
2313       variable_data->available = FALSE;
2314 
2315       return;
2316     }
2317 
2318   GetSystemTimeAsFileTime (&system_time);
2319 
2320   curr_time   = ((guint64) system_time.dwHighDateTime << 32) |
2321                  (guint64) system_time.dwLowDateTime;
2322 
2323   curr_usage  = ((guint64) process_kernel_time.dwHighDateTime << 32) |
2324                  (guint64) process_kernel_time.dwLowDateTime;
2325   curr_usage += ((guint64) process_user_time.dwHighDateTime << 32) |
2326                  (guint64) process_user_time.dwLowDateTime;
2327 
2328   if (data->prev_time && curr_time != data->prev_time)
2329     {
2330       variable_data->available         = TRUE;
2331       variable_data->value.percentage  = (gdouble) (curr_usage - data->prev_usage) /
2332                                                    (curr_time  - data->prev_time);
2333       variable_data->value.percentage /= g_get_num_processors ();
2334     }
2335   else
2336     {
2337       variable_data->available         = FALSE;
2338     }
2339 
2340   data->prev_time  = curr_time;
2341   data->prev_usage = curr_usage;
2342 }
2343 
2344 #endif /* G_OS_WIN32 */
2345 
2346 static void
gimp_dashboard_sample_cpu_active(GimpDashboard * dashboard,Variable variable)2347 gimp_dashboard_sample_cpu_active (GimpDashboard *dashboard,
2348                                   Variable       variable)
2349 {
2350   typedef struct
2351   {
2352     gboolean active;
2353   } Data;
2354 
2355   GimpDashboardPrivate *priv          = dashboard->priv;
2356   VariableData         *variable_data = &priv->variables[variable];
2357   Data                 *data          = gimp_dashboard_variable_get_data (
2358                                           dashboard, variable, sizeof (Data));
2359   gboolean              active        = FALSE;
2360 
2361   if (priv->variables[VARIABLE_CPU_USAGE].available)
2362     {
2363       if (! data->active)
2364         {
2365           active =
2366             priv->variables[VARIABLE_CPU_USAGE].value.percentage *
2367             g_get_num_processors () > CPU_ACTIVE_ON;
2368         }
2369       else
2370         {
2371           active =
2372             priv->variables[VARIABLE_CPU_USAGE].value.percentage *
2373             g_get_num_processors () > CPU_ACTIVE_OFF;
2374         }
2375 
2376       variable_data->available = TRUE;
2377     }
2378   else
2379     {
2380       variable_data->available = FALSE;
2381     }
2382 
2383   data->active                 = active;
2384   variable_data->value.boolean = active;
2385 }
2386 
2387 static void
gimp_dashboard_sample_cpu_active_time(GimpDashboard * dashboard,Variable variable)2388 gimp_dashboard_sample_cpu_active_time (GimpDashboard *dashboard,
2389                                        Variable       variable)
2390 {
2391   typedef struct
2392   {
2393     gint64 prev_time;
2394     gint64 active_time;
2395   } Data;
2396 
2397   GimpDashboardPrivate *priv          = dashboard->priv;
2398   VariableData         *variable_data = &priv->variables[variable];
2399   Data                 *data          = gimp_dashboard_variable_get_data (
2400                                           dashboard, variable, sizeof (Data));
2401   gint64                curr_time;
2402 
2403   curr_time = g_get_monotonic_time ();
2404 
2405   if (priv->variables[VARIABLE_CPU_ACTIVE].available)
2406     {
2407       gboolean active = priv->variables[VARIABLE_CPU_ACTIVE].value.boolean;
2408 
2409       if (active && data->prev_time)
2410         data->active_time += curr_time - data->prev_time;
2411     }
2412 
2413   data->prev_time = curr_time;
2414 
2415   variable_data->available      = TRUE;
2416   variable_data->value.duration = data->active_time / 1000000.0;
2417 }
2418 
2419 #endif /* HAVE_CPU_GROUP */
2420 
2421 #ifdef HAVE_MEMORY_GROUP
2422 #ifdef PLATFORM_OSX
2423 static void
gimp_dashboard_sample_memory_used(GimpDashboard * dashboard,Variable variable)2424 gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
2425                                    Variable       variable)
2426 {
2427   GimpDashboardPrivate        *priv          = dashboard->priv;
2428   VariableData                *variable_data = &priv->variables[variable];
2429 
2430   variable_data->available = FALSE;
2431 #ifndef TASK_VM_INFO_REV0_COUNT /* phys_footprint added in REV1 */
2432   struct mach_task_basic_info info;
2433   mach_msg_type_number_t      infoCount      = MACH_TASK_BASIC_INFO_COUNT;
2434 
2435   if( task_info(mach_task_self (), MACH_TASK_BASIC_INFO,
2436                              (task_info_t)&info, &infoCount ) != KERN_SUCCESS )
2437     return;      /* Can't access? */
2438 
2439   variable_data->available  = TRUE;
2440   variable_data->value.size = info.resident_size;
2441 #else
2442   task_vm_info_data_t         info;
2443   mach_msg_type_number_t      infoCount      = TASK_VM_INFO_COUNT;
2444 
2445   if( task_info(mach_task_self (), TASK_VM_INFO,
2446                              (task_info_t)&info, &infoCount ) != KERN_SUCCESS )
2447     return;      /* Can't access? */
2448   variable_data->available  = TRUE;
2449   variable_data->value.size = info.phys_footprint;
2450 #endif /* ! TASK_VM_INFO_REV0_COUNT */
2451 }
2452 
2453 static void
gimp_dashboard_sample_memory_available(GimpDashboard * dashboard,Variable variable)2454 gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
2455                                         Variable       variable)
2456 {
2457   GimpDashboardPrivate        *priv          = dashboard->priv;
2458   VariableData                *variable_data = &priv->variables[variable];
2459   vm_statistics_data_t        info;
2460   mach_msg_type_number_t      infoCount      = HOST_VM_INFO_COUNT;
2461 
2462   variable_data->available = FALSE;
2463 
2464 
2465   if( host_statistics(mach_host_self (), HOST_VM_INFO,
2466                              (host_info_t)&info, &infoCount ) != KERN_SUCCESS )
2467     return;      /* Can't access? */
2468 
2469   variable_data->available  = TRUE;
2470   variable_data->value.size = info.free_count * PAGE_SIZE;
2471 }
2472 
2473 #elif defined(G_OS_WIN32)
2474 static void
gimp_dashboard_sample_memory_used(GimpDashboard * dashboard,Variable variable)2475 gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
2476                                    Variable       variable)
2477 {
2478   GimpDashboardPrivate       *priv          = dashboard->priv;
2479   VariableData               *variable_data = &priv->variables[variable];
2480   PROCESS_MEMORY_COUNTERS_EX  pmc           = {};
2481 
2482   variable_data->available = FALSE;
2483 
2484   if (! GetProcessMemoryInfo (GetCurrentProcess (),
2485                               (PPROCESS_MEMORY_COUNTERS) &pmc,
2486                               sizeof (pmc)) ||
2487       pmc.cb != sizeof (pmc))
2488     {
2489       return;
2490     }
2491 
2492   variable_data->available  = TRUE;
2493   variable_data->value.size = pmc.PrivateUsage;
2494 }
2495 
2496 static void
gimp_dashboard_sample_memory_available(GimpDashboard * dashboard,Variable variable)2497 gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
2498                                         Variable       variable)
2499 {
2500   GimpDashboardPrivate *priv          = dashboard->priv;
2501   VariableData         *variable_data = &priv->variables[variable];
2502   MEMORYSTATUSEX        ms;
2503 
2504   variable_data->available = FALSE;
2505 
2506   ms.dwLength = sizeof (ms);
2507 
2508   if (! GlobalMemoryStatusEx (&ms))
2509     return;
2510 
2511   variable_data->available  = TRUE;
2512   variable_data->value.size = ms.ullAvailPhys;
2513 }
2514 
2515 #elif defined(__OpenBSD__)
2516 #include <sys/resource.h>
2517 #include <sys/types.h>
2518 #include <sys/sysctl.h>
2519 
2520 static void
gimp_dashboard_sample_memory_used(GimpDashboard * dashboard,Variable variable)2521 gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
2522                                    Variable       variable)
2523 {
2524   GimpDashboardPrivate *priv          = dashboard->priv;
2525   VariableData         *variable_data = &priv->variables[variable];
2526   struct rusage         rusage;
2527 
2528   variable_data->available = FALSE;
2529 
2530   if (getrusage (RUSAGE_SELF, &rusage) == -1)
2531     return;
2532   variable_data->available  = TRUE;
2533   variable_data->value.size = (guint64) (rusage.ru_maxrss * 1024);
2534 }
2535 
2536 static void
gimp_dashboard_sample_memory_available(GimpDashboard * dashboard,Variable variable)2537 gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
2538                                         Variable       variable)
2539 {
2540   GimpDashboardPrivate *priv            = dashboard->priv;
2541   VariableData         *variable_data   = &priv->variables[variable];
2542   int                   mib[]           = { CTL_HW, HW_PHYSMEM64 };
2543   int64_t               result;
2544   size_t                sz              = sizeof(int64_t);
2545 
2546   variable_data->available = FALSE;
2547 
2548   if (sysctl (mib, 2, &result, &sz, NULL, 0) == -1)
2549     return;
2550   variable_data->available  = TRUE;
2551   variable_data->value.size = (guint64) result;
2552 }
2553 
2554 #else /* ! G_OS_WIN32 && ! PLATFORM_OSX */
2555 static void
gimp_dashboard_sample_memory_used(GimpDashboard * dashboard,Variable variable)2556 gimp_dashboard_sample_memory_used (GimpDashboard *dashboard,
2557                                    Variable       variable)
2558 {
2559   GimpDashboardPrivate *priv          = dashboard->priv;
2560   VariableData         *variable_data = &priv->variables[variable];
2561   static gboolean       initialized   = FALSE;
2562   static long           page_size;
2563   static gint           fd            = -1;
2564   gchar                 buffer[128];
2565   gint                  size;
2566   unsigned long long    resident;
2567   unsigned long long    shared;
2568 
2569   if (! initialized)
2570     {
2571       page_size = sysconf (_SC_PAGE_SIZE);
2572 
2573       if (page_size > 0)
2574         fd = open ("/proc/self/statm", O_RDONLY);
2575 
2576       initialized = TRUE;
2577     }
2578 
2579   variable_data->available = FALSE;
2580 
2581   if (fd < 0)
2582     return;
2583 
2584   if (lseek (fd, 0, SEEK_SET))
2585     return;
2586 
2587   size = read (fd, buffer, sizeof (buffer) - 1);
2588 
2589   if (size <= 0)
2590     return;
2591 
2592   buffer[size] = '\0';
2593 
2594   if (sscanf (buffer, "%*u %llu %llu", &resident, &shared) != 2)
2595     return;
2596 
2597   variable_data->available  = TRUE;
2598   variable_data->value.size = (guint64) (resident - shared) * page_size;
2599 }
2600 
2601 static void
gimp_dashboard_sample_memory_available(GimpDashboard * dashboard,Variable variable)2602 gimp_dashboard_sample_memory_available (GimpDashboard *dashboard,
2603                                         Variable       variable)
2604 {
2605   GimpDashboardPrivate *priv            = dashboard->priv;
2606   VariableData         *variable_data   = &priv->variables[variable];
2607   static gboolean       initialized     = FALSE;
2608   static gint64         last_check_time = 0;
2609   static gint           fd;
2610   static guint64        available;
2611   static gboolean       has_available   = FALSE;
2612   gint64                time;
2613 
2614   if (! initialized)
2615     {
2616       fd = open ("/proc/meminfo", O_RDONLY);
2617 
2618       initialized = TRUE;
2619     }
2620 
2621   variable_data->available = FALSE;
2622 
2623   if (fd < 0)
2624     return;
2625 
2626   /* we don't have a config option for limiting the swap size, so we simply
2627    * return the free space available on the filesystem containing the swap
2628    */
2629 
2630   time = g_get_monotonic_time ();
2631 
2632   if (time - last_check_time >= G_TIME_SPAN_SECOND)
2633     {
2634       gchar  buffer[512];
2635       gint   size;
2636       gchar *str;
2637 
2638       last_check_time = time;
2639 
2640       has_available = FALSE;
2641 
2642       if (lseek (fd, 0, SEEK_SET))
2643         return;
2644 
2645       size = read (fd, buffer, sizeof (buffer) - 1);
2646 
2647       if (size <= 0)
2648         return;
2649 
2650       buffer[size] = '\0';
2651 
2652       str = strstr (buffer, "MemAvailable:");
2653 
2654       if (! str)
2655         return;
2656 
2657       available = strtoull (str + 13, &str, 0);
2658 
2659       if (! str)
2660         return;
2661 
2662       for (; *str; str++)
2663         {
2664           if (*str == 'k')
2665             {
2666               available <<= 10;
2667               break;
2668             }
2669           else if (*str == 'M')
2670             {
2671               available <<= 20;
2672               break;
2673             }
2674         }
2675 
2676       if (! *str)
2677         return;
2678 
2679       has_available = TRUE;
2680     }
2681 
2682   if (! has_available)
2683     return;
2684 
2685   variable_data->available  = TRUE;
2686   variable_data->value.size = available;
2687 }
2688 
2689 #endif
2690 
2691 static void
gimp_dashboard_sample_memory_size(GimpDashboard * dashboard,Variable variable)2692 gimp_dashboard_sample_memory_size (GimpDashboard *dashboard,
2693                                    Variable       variable)
2694 {
2695   GimpDashboardPrivate *priv          = dashboard->priv;
2696   VariableData         *variable_data = &priv->variables[variable];
2697 
2698   variable_data->value.size = gimp_get_physical_memory_size ();
2699   variable_data->available  = variable_data->value.size > 0;
2700 }
2701 
2702 #endif /* HAVE_MEMORY_GROUP */
2703 
2704 static void
gimp_dashboard_sample_object(GimpDashboard * dashboard,GObject * object,Variable variable)2705 gimp_dashboard_sample_object (GimpDashboard *dashboard,
2706                               GObject       *object,
2707                               Variable       variable)
2708 {
2709   GimpDashboardPrivate *priv          = dashboard->priv;
2710   GObjectClass         *klass         = G_OBJECT_GET_CLASS (object);
2711   const VariableInfo   *variable_info = &variables[variable];
2712   VariableData         *variable_data = &priv->variables[variable];
2713 
2714   variable_data->available = FALSE;
2715 
2716   switch (variable_info->type)
2717     {
2718     case VARIABLE_TYPE_BOOLEAN:
2719       if (g_object_class_find_property (klass, variable_info->data))
2720         {
2721           variable_data->available = TRUE;
2722 
2723           g_object_get (object,
2724                         variable_info->data, &variable_data->value.boolean,
2725                         NULL);
2726         }
2727       break;
2728 
2729     case VARIABLE_TYPE_INTEGER:
2730       if (g_object_class_find_property (klass, variable_info->data))
2731         {
2732           variable_data->available = TRUE;
2733 
2734           g_object_get (object,
2735                         variable_info->data, &variable_data->value.integer,
2736                         NULL);
2737         }
2738       break;
2739 
2740     case VARIABLE_TYPE_SIZE:
2741       if (g_object_class_find_property (klass, variable_info->data))
2742         {
2743           variable_data->available = TRUE;
2744 
2745           g_object_get (object,
2746                         variable_info->data, &variable_data->value.size,
2747                         NULL);
2748         }
2749       break;
2750 
2751     case VARIABLE_TYPE_SIZE_RATIO:
2752       {
2753         const gchar *antecedent = variable_info->data;
2754         const gchar *consequent = antecedent + strlen (antecedent) + 1;
2755 
2756         if (g_object_class_find_property (klass, antecedent) &&
2757             g_object_class_find_property (klass, consequent))
2758           {
2759             variable_data->available = TRUE;
2760 
2761             g_object_get (object,
2762                           antecedent, &variable_data->value.size_ratio.antecedent,
2763                           consequent, &variable_data->value.size_ratio.consequent,
2764                           NULL);
2765           }
2766       }
2767       break;
2768 
2769     case VARIABLE_TYPE_INT_RATIO:
2770       {
2771         const gchar *antecedent = variable_info->data;
2772         const gchar *consequent = antecedent + strlen (antecedent) + 1;
2773 
2774         if (g_object_class_find_property (klass, antecedent) &&
2775             g_object_class_find_property (klass, consequent))
2776           {
2777             variable_data->available = TRUE;
2778 
2779             g_object_get (object,
2780                           antecedent, &variable_data->value.int_ratio.antecedent,
2781                           consequent, &variable_data->value.int_ratio.consequent,
2782                           NULL);
2783           }
2784       }
2785       break;
2786 
2787     case VARIABLE_TYPE_PERCENTAGE:
2788       if (g_object_class_find_property (klass, variable_info->data))
2789         {
2790           variable_data->available = TRUE;
2791 
2792           g_object_get (object,
2793                         variable_info->data, &variable_data->value.percentage,
2794                         NULL);
2795         }
2796       break;
2797 
2798     case VARIABLE_TYPE_DURATION:
2799       if (g_object_class_find_property (klass, variable_info->data))
2800         {
2801           variable_data->available = TRUE;
2802 
2803           g_object_get (object,
2804                         variable_info->data, &variable_data->value.duration,
2805                         NULL);
2806         }
2807       break;
2808 
2809     case VARIABLE_TYPE_RATE_OF_CHANGE:
2810       if (g_object_class_find_property (klass, variable_info->data))
2811         {
2812           variable_data->available = TRUE;
2813 
2814           g_object_get (object,
2815                         variable_info->data, &variable_data->value.rate_of_change,
2816                         NULL);
2817         }
2818       break;
2819     }
2820 }
2821 
2822 static void
gimp_dashboard_group_menu_position(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer user_data)2823 gimp_dashboard_group_menu_position (GtkMenu  *menu,
2824                                     gint     *x,
2825                                     gint     *y,
2826                                     gboolean *push_in,
2827                                     gpointer  user_data)
2828 {
2829   gimp_button_menu_position (user_data, menu, GTK_POS_LEFT, x, y);
2830 }
2831 
2832 static void
gimp_dashboard_update_groups(GimpDashboard * dashboard)2833 gimp_dashboard_update_groups (GimpDashboard *dashboard)
2834 {
2835   Group group;
2836 
2837   for (group = FIRST_GROUP; group < N_GROUPS; group++)
2838     gimp_dashboard_update_group (dashboard, group);
2839 }
2840 
2841 static void
gimp_dashboard_update_group(GimpDashboard * dashboard,Group group)2842 gimp_dashboard_update_group (GimpDashboard *dashboard,
2843                              Group          group)
2844 {
2845   GimpDashboardPrivate *priv       = dashboard->priv;
2846   const GroupInfo      *group_info = &groups[group];
2847   GroupData            *group_data = &priv->groups[group];
2848   gint                  n_rows;
2849   gboolean              add_separator;
2850   gint                  field;
2851 
2852   gtk_widget_set_visible (GTK_WIDGET (group_data->expander),
2853                           group_data->active);
2854 
2855   if (! group_data->active)
2856     return;
2857 
2858   n_rows        = 0;
2859   add_separator = FALSE;
2860 
2861   for (field = 0; field < group_data->n_fields; field++)
2862     {
2863       const FieldInfo *field_info = &group_info->fields[field];
2864       const FieldData *field_data = &group_data->fields[field];
2865 
2866       if (field_info->variable != VARIABLE_SEPARATOR)
2867         {
2868           if (group_info->has_meter && field_info->meter_value)
2869             {
2870               gimp_meter_set_value_active (group_data->meter,
2871                                            field_info->meter_value - 1,
2872                                            field_data->active);
2873             }
2874 
2875           if (field_data->active)
2876             {
2877               if (add_separator)
2878                 {
2879                   add_separator = FALSE;
2880                   n_rows++;
2881                 }
2882 
2883               n_rows++;
2884             }
2885         }
2886       else
2887         {
2888           if (n_rows > 0)
2889             add_separator = TRUE;
2890         }
2891     }
2892 
2893   gimp_gtk_container_clear (GTK_CONTAINER (group_data->table));
2894   gtk_table_resize (group_data->table, MAX (n_rows, 1), 3);
2895 
2896   n_rows        = 0;
2897   add_separator = FALSE;
2898 
2899   for (field = 0; field < group_data->n_fields; field++)
2900     {
2901       const FieldInfo *field_info = &group_info->fields[field];
2902       FieldData       *field_data = &group_data->fields[field];
2903 
2904       if (field_info->variable != VARIABLE_SEPARATOR)
2905         {
2906           const VariableInfo *variable_info = &variables[field_info->variable];
2907           GtkWidget          *separator;
2908           GtkWidget          *color_area;
2909           GtkWidget          *label;
2910           const gchar        *description;
2911           gchar              *str;
2912 
2913           if (! field_data->active)
2914             continue;
2915 
2916           description = g_dgettext (NULL, variable_info->description);
2917 
2918           if (add_separator)
2919             {
2920               separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
2921               gtk_table_attach (group_data->table, separator,
2922                                 0, 3, n_rows, n_rows + 1,
2923                                 GTK_EXPAND | GTK_FILL, 0,
2924                                 0, 0);
2925               gtk_widget_show (separator);
2926 
2927               add_separator = FALSE;
2928               n_rows++;
2929             }
2930 
2931           if (group_info->has_meter && field_info->meter_value)
2932             {
2933               color_area = gimp_color_area_new (&variable_info->color,
2934                                                 GIMP_COLOR_AREA_FLAT, 0);
2935               gimp_help_set_help_data (color_area, description,
2936                                        NULL);
2937               gtk_widget_set_size_request (color_area, 5, 5);
2938               gtk_table_attach (group_data->table, color_area,
2939                                 0, 1, n_rows, n_rows + 1,
2940                                 0, 0,
2941                                 0, 0);
2942               gtk_widget_show (color_area);
2943             }
2944 
2945           str = g_strdup_printf ("%s:",
2946                                  g_dpgettext2 (NULL, "dashboard-variable",
2947                                                field_info->title ?
2948                                                  field_info->title :
2949                                                  variable_info->title));
2950 
2951           label = gtk_label_new (str);
2952           gimp_help_set_help_data (label, description,
2953                                    NULL);
2954           gtk_label_set_xalign (GTK_LABEL (label), 0.0);
2955           gtk_table_attach (group_data->table, label,
2956                             1, 2, n_rows, n_rows + 1,
2957                             GTK_FILL, 0,
2958                             0, 0);
2959           gtk_widget_show (label);
2960 
2961           g_free (str);
2962 
2963           label = gtk_label_new (NULL);
2964           field_data->value_label = GTK_LABEL (label);
2965           gimp_help_set_help_data (label, description,
2966                                    NULL);
2967           gtk_label_set_xalign (GTK_LABEL (label), 0.0);
2968           gtk_table_attach (group_data->table, label,
2969                             2, 3, n_rows, n_rows + 1,
2970                             GTK_EXPAND | GTK_FILL, 0,
2971                             0, 0);
2972           gtk_widget_show (label);
2973 
2974           n_rows++;
2975         }
2976       else
2977         {
2978           if (n_rows > 0)
2979             add_separator = TRUE;
2980         }
2981     }
2982 
2983   g_mutex_lock (&priv->mutex);
2984 
2985   gimp_dashboard_update_group_values (dashboard, group);
2986 
2987   g_mutex_unlock (&priv->mutex);
2988 }
2989 
2990 static void
gimp_dashboard_update_group_values(GimpDashboard * dashboard,Group group)2991 gimp_dashboard_update_group_values (GimpDashboard *dashboard,
2992                                     Group          group)
2993 {
2994   GimpDashboardPrivate *priv       = dashboard->priv;
2995   const GroupInfo      *group_info = &groups[group];
2996   GroupData            *group_data = &priv->groups[group];
2997   gdouble               limit      = 0.0;
2998   GString              *header_values;
2999   gint                  field;
3000 
3001   if (! group_data->active)
3002     return;
3003 
3004   if (group_info->has_meter)
3005     {
3006       if (group_info->meter_limit)
3007         {
3008           const VariableData *variable_data = &priv->variables[group_info->meter_limit];
3009 
3010           if (variable_data->available)
3011             {
3012               limit = gimp_dashboard_variable_to_double (dashboard,
3013                                                         group_info->meter_limit);
3014             }
3015           else
3016             {
3017               for (field = 0; field < group_data->n_fields; field++)
3018                 {
3019                   const FieldInfo *field_info = &group_info->fields[field];
3020                   const FieldData *field_data = &group_data->fields[field];
3021 
3022                   if (field_info->meter_value && field_data->active)
3023                     {
3024                       gdouble value;
3025 
3026                       value = gimp_dashboard_variable_to_double (dashboard,
3027                                                                  field_info->variable);
3028 
3029                       limit = MAX (limit, value);
3030                     }
3031                 }
3032             }
3033 
3034           gimp_meter_set_range (group_data->meter, 0.0, limit);
3035         }
3036 
3037       if (group_info->meter_led)
3038         {
3039           GimpRGB         color  = {0.0, 0.0, 0.0, 1.0};
3040           gboolean        active = FALSE;
3041           const Variable *var;
3042 
3043           for (var = group_info->meter_led; *var; var++)
3044             {
3045               if (gimp_dashboard_variable_to_boolean (dashboard, *var))
3046                 {
3047                   const VariableInfo *variable_info = &variables[*var];
3048 
3049                   color.r = MAX (color.r, variable_info->color.r);
3050                   color.g = MAX (color.g, variable_info->color.g);
3051                   color.b = MAX (color.b, variable_info->color.b);
3052 
3053                   active = TRUE;
3054                 }
3055             }
3056 
3057           if (active)
3058             gimp_meter_set_led_color (group_data->meter, &color);
3059 
3060           gimp_meter_set_led_active (group_data->meter, active);
3061         }
3062     }
3063 
3064   group_data->limit = limit;
3065 
3066   header_values = g_string_new (NULL);
3067 
3068   for (field = 0; field < group_data->n_fields; field++)
3069     {
3070       const FieldInfo *field_info = &group_info->fields[field];
3071       const FieldData *field_data = &group_data->fields[field];
3072 
3073       if (field_data->active)
3074         {
3075           gchar *text;
3076 
3077           text = gimp_dashboard_field_to_string (dashboard,
3078                                                  group, field, TRUE);
3079 
3080           gimp_dashboard_label_set_text (field_data->value_label, text);
3081 
3082           g_free (text);
3083 
3084           if (field_info->show_in_header)
3085             {
3086               text = gimp_dashboard_field_to_string (dashboard,
3087                                                      group, field, FALSE);
3088 
3089               if (header_values->len > 0)
3090                 g_string_append (header_values, ", ");
3091 
3092               g_string_append (header_values, text);
3093 
3094               g_free (text);
3095             }
3096         }
3097     }
3098 
3099   if (header_values->len > 0)
3100     {
3101       g_string_prepend (header_values, "(");
3102       g_string_append  (header_values, ")");
3103     }
3104 
3105   gimp_dashboard_label_set_text (group_data->header_values_label,
3106                                  header_values->str);
3107 
3108   g_string_free (header_values, TRUE);
3109 }
3110 
3111 static void
gimp_dashboard_group_set_active(GimpDashboard * dashboard,Group group,gboolean active)3112 gimp_dashboard_group_set_active (GimpDashboard *dashboard,
3113                                  Group          group,
3114                                  gboolean       active)
3115 {
3116   GimpDashboardPrivate *priv       = dashboard->priv;
3117   GroupData            *group_data = &priv->groups[group];
3118 
3119   if (active != group_data->active)
3120     {
3121       group_data->active = active;
3122 
3123       if (group_data->action)
3124         {
3125           g_signal_handlers_block_by_func (group_data->action,
3126                                            gimp_dashboard_group_action_toggled,
3127                                            dashboard);
3128 
3129           gimp_toggle_action_set_active (group_data->action, active);
3130 
3131           g_signal_handlers_unblock_by_func (group_data->action,
3132                                              gimp_dashboard_group_action_toggled,
3133                                              dashboard);
3134         }
3135     }
3136 }
3137 
3138 static void
gimp_dashboard_field_set_active(GimpDashboard * dashboard,Group group,gint field,gboolean active)3139 gimp_dashboard_field_set_active (GimpDashboard *dashboard,
3140                                  Group          group,
3141                                  gint           field,
3142                                  gboolean       active)
3143 {
3144   GimpDashboardPrivate *priv       = dashboard->priv;
3145   GroupData            *group_data = &priv->groups[group];
3146   FieldData            *field_data = &group_data->fields[field];
3147 
3148   if (active != field_data->active)
3149     {
3150       field_data->active = active;
3151 
3152       g_signal_handlers_block_by_func (field_data->menu_item,
3153                                        gimp_dashboard_field_menu_item_toggled,
3154                                        dashboard);
3155 
3156       gtk_check_menu_item_set_active (field_data->menu_item, active);
3157 
3158       g_signal_handlers_unblock_by_func (field_data->menu_item,
3159                                          gimp_dashboard_field_menu_item_toggled,
3160                                          dashboard);
3161     }
3162 }
3163 
3164 static void
gimp_dashboard_reset_unlocked(GimpDashboard * dashboard)3165 gimp_dashboard_reset_unlocked (GimpDashboard *dashboard)
3166 {
3167   GimpDashboardPrivate *priv;
3168   Group                 group;
3169 
3170   priv = dashboard->priv;
3171 
3172   gegl_reset_stats ();
3173 
3174   gimp_dashboard_reset_variables (dashboard);
3175 
3176   for (group = FIRST_GROUP; group < N_GROUPS; group++)
3177     {
3178       GroupData *group_data = &priv->groups[group];
3179 
3180       if (group_data->meter)
3181         gimp_meter_clear_history (group_data->meter);
3182     }
3183 }
3184 
3185 static void
gimp_dashboard_reset_variables(GimpDashboard * dashboard)3186 gimp_dashboard_reset_variables (GimpDashboard *dashboard)
3187 {
3188   GimpDashboardPrivate *priv = dashboard->priv;
3189   Variable              variable;
3190 
3191   for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
3192     {
3193       const VariableInfo *variable_info = &variables[variable];
3194       VariableData       *variable_data = &priv->variables[variable];
3195 
3196       if (variable_info->reset_func)
3197         variable_info->reset_func (dashboard, variable);
3198 
3199       g_clear_pointer (&variable_data->data, g_free);
3200       variable_data->data_size = 0;
3201     }
3202 }
3203 
3204 static gpointer
gimp_dashboard_variable_get_data(GimpDashboard * dashboard,Variable variable,gsize size)3205 gimp_dashboard_variable_get_data (GimpDashboard *dashboard,
3206                                   Variable       variable,
3207                                   gsize          size)
3208 {
3209   GimpDashboardPrivate *priv          = dashboard->priv;
3210   VariableData         *variable_data = &priv->variables[variable];
3211 
3212   if (variable_data->data_size != size)
3213     {
3214       variable_data->data = g_realloc (variable_data->data, size);
3215 
3216       if (variable_data->data_size < size)
3217         {
3218           memset ((guint8 *) variable_data->data + variable_data->data_size,
3219                   0, size - variable_data->data_size);
3220         }
3221 
3222       variable_data->data_size = size;
3223     }
3224 
3225   return variable_data->data;
3226 }
3227 
3228 static gboolean
gimp_dashboard_variable_to_boolean(GimpDashboard * dashboard,Variable variable)3229 gimp_dashboard_variable_to_boolean (GimpDashboard *dashboard,
3230                                     Variable       variable)
3231 {
3232   GimpDashboardPrivate *priv          = dashboard->priv;
3233   const VariableInfo   *variable_info = &variables[variable];
3234   const VariableData   *variable_data = &priv->variables[variable];
3235 
3236   if (variable_data->available)
3237     {
3238       switch (variable_info->type)
3239         {
3240         case VARIABLE_TYPE_BOOLEAN:
3241           return variable_data->value.boolean;
3242 
3243         case VARIABLE_TYPE_INTEGER:
3244           return variable_data->value.integer != 0;
3245 
3246         case VARIABLE_TYPE_SIZE:
3247           return variable_data->value.size > 0;
3248 
3249         case VARIABLE_TYPE_SIZE_RATIO:
3250           return variable_data->value.size_ratio.antecedent != 0 &&
3251                  variable_data->value.size_ratio.consequent != 0;
3252 
3253         case VARIABLE_TYPE_INT_RATIO:
3254           return variable_data->value.int_ratio.antecedent != 0 &&
3255                  variable_data->value.int_ratio.consequent != 0;
3256 
3257         case VARIABLE_TYPE_PERCENTAGE:
3258           return variable_data->value.percentage != 0.0;
3259 
3260         case VARIABLE_TYPE_DURATION:
3261           return variable_data->value.duration != 0.0;
3262 
3263         case VARIABLE_TYPE_RATE_OF_CHANGE:
3264           return variable_data->value.rate_of_change != 0.0;
3265         }
3266     }
3267 
3268   return FALSE;
3269 }
3270 
3271 static gdouble
gimp_dashboard_variable_to_double(GimpDashboard * dashboard,Variable variable)3272 gimp_dashboard_variable_to_double (GimpDashboard *dashboard,
3273                                    Variable       variable)
3274 {
3275   GimpDashboardPrivate *priv          = dashboard->priv;
3276   const VariableInfo   *variable_info = &variables[variable];
3277   const VariableData   *variable_data = &priv->variables[variable];
3278 
3279   if (variable_data->available)
3280     {
3281       switch (variable_info->type)
3282         {
3283         case VARIABLE_TYPE_BOOLEAN:
3284           return variable_data->value.boolean ? 1.0 : 0.0;
3285 
3286         case VARIABLE_TYPE_INTEGER:
3287           return variable_data->value.integer;
3288 
3289         case VARIABLE_TYPE_SIZE:
3290           return variable_data->value.size;
3291 
3292         case VARIABLE_TYPE_SIZE_RATIO:
3293           if (variable_data->value.size_ratio.consequent)
3294             {
3295               return (gdouble) variable_data->value.size_ratio.antecedent /
3296                      (gdouble) variable_data->value.size_ratio.consequent;
3297             }
3298           break;
3299 
3300         case VARIABLE_TYPE_INT_RATIO:
3301           if (variable_data->value.int_ratio.consequent)
3302             {
3303               return (gdouble) variable_data->value.int_ratio.antecedent /
3304                      (gdouble) variable_data->value.int_ratio.consequent;
3305             }
3306           break;
3307 
3308         case VARIABLE_TYPE_PERCENTAGE:
3309           return variable_data->value.percentage;
3310 
3311         case VARIABLE_TYPE_DURATION:
3312           return variable_data->value.duration;
3313 
3314         case VARIABLE_TYPE_RATE_OF_CHANGE:
3315           return variable_data->value.rate_of_change;
3316         }
3317     }
3318 
3319   return 0.0;
3320 }
3321 
3322 static gchar *
gimp_dashboard_field_to_string(GimpDashboard * dashboard,Group group,gint field,gboolean full)3323 gimp_dashboard_field_to_string (GimpDashboard *dashboard,
3324                                 Group          group,
3325                                 gint           field,
3326                                 gboolean       full)
3327 {
3328   GimpDashboardPrivate *priv          = dashboard->priv;
3329   const GroupInfo      *group_info    = &groups[group];
3330   const GroupData      *group_data    = &priv->groups[group];
3331   const FieldInfo      *field_info    = &group_info->fields[field];
3332   const VariableInfo   *variable_info = &variables[field_info->variable];
3333   const VariableData   *variable_data = &priv->variables[field_info->variable];
3334   /* Tranlators: "N/A" is an abbreviation for "not available" */
3335   const gchar          *str           = C_("dashboard-value", "N/A");
3336   gboolean              static_str    = TRUE;
3337   gboolean              show_limit    = TRUE;
3338 
3339   if (variable_data->available)
3340     {
3341       switch (variable_info->type)
3342         {
3343         case VARIABLE_TYPE_BOOLEAN:
3344           str = variable_data->value.boolean ? C_("dashboard-value", "Yes") :
3345                                                C_("dashboard-value", "No");
3346           break;
3347 
3348         case VARIABLE_TYPE_INTEGER:
3349           str        = g_strdup_printf ("%d", variable_data->value.integer);
3350           static_str = FALSE;
3351           break;
3352 
3353         case VARIABLE_TYPE_SIZE:
3354           str        = g_format_size_full (variable_data->value.size,
3355                                            G_FORMAT_SIZE_IEC_UNITS);
3356           static_str = FALSE;
3357           break;
3358 
3359         case VARIABLE_TYPE_SIZE_RATIO:
3360           {
3361             if (variable_data->value.size_ratio.consequent)
3362               {
3363                 gdouble value;
3364 
3365                 value = 100.0 * variable_data->value.size_ratio.antecedent /
3366                                 variable_data->value.size_ratio.consequent;
3367 
3368                 str        = g_strdup_printf ("%d%%", SIGNED_ROUND (value));
3369                 static_str = FALSE;
3370               }
3371           }
3372           break;
3373 
3374         case VARIABLE_TYPE_INT_RATIO:
3375           {
3376             gdouble  min;
3377             gdouble  max;
3378             gdouble  antecedent;
3379             gdouble  consequent;
3380 
3381             antecedent = variable_data->value.int_ratio.antecedent;
3382             consequent = variable_data->value.int_ratio.consequent;
3383 
3384             min = MIN (ABS (antecedent), ABS (consequent));
3385             max = MAX (ABS (antecedent), ABS (consequent));
3386 
3387             if (min)
3388               {
3389                 antecedent /= min;
3390                 consequent /= min;
3391               }
3392             else if (max)
3393               {
3394                 antecedent /= max;
3395                 consequent /= max;
3396               }
3397 
3398             if (max)
3399               {
3400                 str        = g_strdup_printf ("%g:%g",
3401                                               RINT (100.0 * antecedent) / 100.0,
3402                                               RINT (100.0 * consequent) / 100.0);
3403                 static_str = FALSE;
3404               }
3405           }
3406           break;
3407 
3408         case VARIABLE_TYPE_PERCENTAGE:
3409           str        = g_strdup_printf ("%d%%",
3410                                         SIGNED_ROUND (100.0 * variable_data->value.percentage));
3411           static_str = FALSE;
3412           show_limit = FALSE;
3413           break;
3414 
3415         case VARIABLE_TYPE_DURATION:
3416           str        = g_strdup_printf ("%02d:%02d:%04.1f",
3417                                         (gint) floor (variable_data->value.duration / 3600.0),
3418                                         (gint) floor (fmod (variable_data->value.duration / 60.0, 60.0)),
3419                                         floor (fmod (variable_data->value.duration, 60.0) * 10.0) / 10.0);
3420           static_str = FALSE;
3421           show_limit = FALSE;
3422           break;
3423 
3424         case VARIABLE_TYPE_RATE_OF_CHANGE:
3425           /* Translators:  This string reports the rate of change of a measured
3426            * value.  The "%g" is replaced by a certain quantity, and the "/s"
3427            * is an abbreviation for "per second".
3428            */
3429           str        = g_strdup_printf (_("%g/s"),
3430                                         variable_data->value.rate_of_change);
3431           static_str = FALSE;
3432           break;
3433         }
3434 
3435       if (show_limit                   &&
3436           variable_data->available     &&
3437           field_info->meter_value      &&
3438           ! field_info->meter_variable &&
3439           group_data->limit)
3440         {
3441           gdouble  value;
3442           gchar   *tmp;
3443 
3444           value = gimp_dashboard_variable_to_double (dashboard,
3445                                                      field_info->variable);
3446 
3447           if (full)
3448             {
3449               tmp = g_strdup_printf ("%s (%d%%)",
3450                                      str,
3451                                      SIGNED_ROUND (100.0 * value /
3452                                                    group_data->limit));
3453             }
3454           else
3455             {
3456               tmp = g_strdup_printf ("%d%%",
3457                                      SIGNED_ROUND (100.0 * value /
3458                                                    group_data->limit));
3459             }
3460 
3461           if (! static_str)
3462             g_free ((gpointer) str);
3463 
3464           str        = tmp;
3465           static_str = FALSE;
3466         }
3467       else if (full                         &&
3468                field_info->meter_variable   &&
3469                variables[field_info->meter_variable].type ==
3470                VARIABLE_TYPE_RATE_OF_CHANGE &&
3471                priv->variables[field_info->meter_variable].available)
3472         {
3473           gdouble  value;
3474           gchar   *value_str;
3475           gchar   *rate_of_change_str;
3476           gchar   *tmp;
3477 
3478           value = gimp_dashboard_variable_to_double (dashboard,
3479                                                      field_info->meter_variable);
3480 
3481           value_str = gimp_dashboard_format_value (variable_info->type, value);
3482 
3483           rate_of_change_str = gimp_dashboard_format_rate_of_change (value_str);
3484 
3485           g_free (value_str);
3486 
3487           tmp = g_strdup_printf ("%s (%s)", str, rate_of_change_str);
3488 
3489           g_free (rate_of_change_str);
3490 
3491           if (! static_str)
3492             g_free ((gpointer) str);
3493 
3494           str        = tmp;
3495           static_str = FALSE;
3496         }
3497     }
3498 
3499   if (static_str)
3500     return g_strdup (str);
3501   else
3502     return (gpointer) str;
3503 }
3504 
3505 static gboolean
gimp_dashboard_log_printf(GimpDashboard * dashboard,const gchar * format,...)3506 gimp_dashboard_log_printf (GimpDashboard *dashboard,
3507                            const gchar   *format,
3508                            ...)
3509 {
3510   GimpDashboardPrivate *priv = dashboard->priv;
3511   va_list               args;
3512   gboolean              result;
3513 
3514   if (priv->log_error)
3515     return FALSE;
3516 
3517   va_start (args, format);
3518 
3519   result = g_output_stream_vprintf (priv->log_output,
3520                                     NULL, NULL,
3521                                     &priv->log_error,
3522                                     format, args);
3523 
3524   va_end (args);
3525 
3526   return result;
3527 }
3528 
3529 static gboolean
gimp_dashboard_log_print_escaped(GimpDashboard * dashboard,const gchar * string)3530 gimp_dashboard_log_print_escaped (GimpDashboard *dashboard,
3531                                   const gchar   *string)
3532 {
3533   GimpDashboardPrivate *priv = dashboard->priv;
3534   gchar                 buffer[1024];
3535   const gchar          *s;
3536   gint                  i;
3537 
3538   if (priv->log_error)
3539     return FALSE;
3540 
3541   i = 0;
3542 
3543   #define FLUSH()                                                 \
3544     G_STMT_START                                                  \
3545       {                                                           \
3546         if (! g_output_stream_write_all (priv->log_output,        \
3547                                          buffer, i, NULL,         \
3548                                          NULL, &priv->log_error)) \
3549           {                                                       \
3550             return FALSE;                                         \
3551           }                                                       \
3552                                                                   \
3553         i = 0;                                                    \
3554       }                                                           \
3555     G_STMT_END
3556 
3557   #define RESERVE(n)                   \
3558     G_STMT_START                       \
3559       {                                \
3560         if (i + (n) > sizeof (buffer)) \
3561           FLUSH ();                    \
3562       }                                \
3563     G_STMT_END
3564 
3565   for (s = string; *s; s++)
3566     {
3567       #define ESCAPE(from, to)                      \
3568         case from:                                  \
3569           RESERVE (sizeof (to) - 1);                \
3570           memcpy (&buffer[i], to, sizeof (to) - 1); \
3571           i += sizeof (to) - 1;                     \
3572           break;
3573 
3574       switch (*s)
3575         {
3576         ESCAPE ('"',  "&quot;")
3577         ESCAPE ('\'', "&apos;")
3578         ESCAPE ('<',  "&lt;")
3579         ESCAPE ('>',  "&gt;")
3580         ESCAPE ('&',  "&amp;")
3581 
3582         default:
3583           RESERVE (1);
3584           buffer[i++] = *s;
3585           break;
3586         }
3587 
3588       #undef ESCAPE
3589     }
3590 
3591   FLUSH ();
3592 
3593   #undef FLUSH
3594   #undef RESERVE
3595 
3596   return TRUE;
3597 }
3598 
3599 static gint64
gimp_dashboard_log_time(GimpDashboard * dashboard)3600 gimp_dashboard_log_time (GimpDashboard *dashboard)
3601 {
3602   GimpDashboardPrivate *priv = dashboard->priv;
3603 
3604   return g_get_monotonic_time () - priv->log_start_time;
3605 }
3606 
3607 static void
gimp_dashboard_log_sample(GimpDashboard * dashboard,gboolean variables_changed,gboolean include_current_thread)3608 gimp_dashboard_log_sample (GimpDashboard *dashboard,
3609                            gboolean       variables_changed,
3610                            gboolean       include_current_thread)
3611 {
3612   GimpDashboardPrivate *priv      = dashboard->priv;
3613   GimpBacktrace        *backtrace = NULL;
3614   GArray               *addresses = NULL;
3615   gboolean              empty     = TRUE;
3616   Variable              variable;
3617 
3618   #define NONEMPTY()                              \
3619     G_STMT_START                                  \
3620       {                                           \
3621         if (empty)                                \
3622           {                                       \
3623             gimp_dashboard_log_printf (dashboard, \
3624                                        ">\n");    \
3625                                                   \
3626             empty = FALSE;                        \
3627           }                                       \
3628       }                                           \
3629     G_STMT_END
3630 
3631   gimp_dashboard_log_printf (dashboard,
3632                              "\n"
3633                              "<sample id=\"%d\" t=\"%lld\"",
3634                              priv->log_n_samples,
3635                              (long long) gimp_dashboard_log_time (dashboard));
3636 
3637   if (priv->log_n_samples == 0 || variables_changed)
3638     {
3639       NONEMPTY ();
3640 
3641       gimp_dashboard_log_printf (dashboard,
3642                                  "<vars>\n");
3643 
3644       for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
3645         {
3646           const VariableInfo *variable_info     = &variables[variable];
3647           const VariableData *variable_data     = &priv->variables[variable];
3648           VariableData       *log_variable_data = &priv->log_variables[variable];
3649 
3650           if (variable_info->exclude_from_log)
3651             continue;
3652 
3653           if (priv->log_n_samples > 0 &&
3654               ! memcmp (variable_data, log_variable_data,
3655                         sizeof (VariableData)))
3656             {
3657               continue;
3658             }
3659 
3660           *log_variable_data = *variable_data;
3661 
3662           if (variable_data->available)
3663             {
3664               #define LOG_VAR(format, ...)                          \
3665                 gimp_dashboard_log_printf (dashboard,               \
3666                                            "<%s>" format "</%s>\n", \
3667                                            variable_info->name,     \
3668                                            __VA_ARGS__,             \
3669                                            variable_info->name)
3670 
3671               #define LOG_VAR_FLOAT(value)                                  \
3672                 G_STMT_START                                                \
3673                   {                                                         \
3674                     gchar buffer[G_ASCII_DTOSTR_BUF_SIZE];                  \
3675                                                                             \
3676                     LOG_VAR ("%s", g_ascii_dtostr (buffer, sizeof (buffer), \
3677                                                    value));                 \
3678                   }                                                         \
3679                 G_STMT_END
3680 
3681               switch (variable_info->type)
3682                 {
3683                 case VARIABLE_TYPE_BOOLEAN:
3684                   LOG_VAR (
3685                     "%d",
3686                     variable_data->value.boolean);
3687                   break;
3688 
3689                 case VARIABLE_TYPE_INTEGER:
3690                   LOG_VAR (
3691                     "%d",
3692                     variable_data->value.integer);
3693                   break;
3694 
3695                 case VARIABLE_TYPE_SIZE:
3696                   LOG_VAR (
3697                     "%llu",
3698                     (unsigned long long) variable_data->value.size);
3699                   break;
3700 
3701                 case VARIABLE_TYPE_SIZE_RATIO:
3702                   LOG_VAR (
3703                     "%llu/%llu",
3704                     (unsigned long long) variable_data->value.size_ratio.antecedent,
3705                     (unsigned long long) variable_data->value.size_ratio.consequent);
3706                   break;
3707 
3708                 case VARIABLE_TYPE_INT_RATIO:
3709                   LOG_VAR (
3710                     "%d:%d",
3711                     variable_data->value.int_ratio.antecedent,
3712                     variable_data->value.int_ratio.consequent);
3713                   break;
3714 
3715                 case VARIABLE_TYPE_PERCENTAGE:
3716                   LOG_VAR_FLOAT (
3717                     variable_data->value.percentage);
3718                   break;
3719 
3720                 case VARIABLE_TYPE_DURATION:
3721                   LOG_VAR_FLOAT (
3722                     variable_data->value.duration);
3723                   break;
3724 
3725                 case VARIABLE_TYPE_RATE_OF_CHANGE:
3726                   LOG_VAR_FLOAT (
3727                     variable_data->value.rate_of_change);
3728                   break;
3729                 }
3730 
3731               #undef LOG_VAR
3732               #undef LOG_VAR_FLOAT
3733             }
3734           else
3735             {
3736               gimp_dashboard_log_printf (dashboard,
3737                                          "<%s />\n",
3738                                          variable_info->name);
3739             }
3740         }
3741 
3742       gimp_dashboard_log_printf (dashboard,
3743                                  "</vars>\n");
3744     }
3745 
3746   if (priv->log_params.backtrace)
3747     backtrace = gimp_backtrace_new (include_current_thread);
3748 
3749   if (backtrace)
3750     {
3751       gboolean backtrace_empty = TRUE;
3752       gint     n_threads;
3753       gint     thread;
3754 
3755       #define BACKTRACE_NONEMPTY()                           \
3756         G_STMT_START                                         \
3757           {                                                  \
3758             if (backtrace_empty)                             \
3759               {                                              \
3760                 NONEMPTY ();                                 \
3761                                                              \
3762                 gimp_dashboard_log_printf (dashboard,        \
3763                                            "<backtrace>\n"); \
3764                                                              \
3765                 backtrace_empty = FALSE;                     \
3766               }                                              \
3767           }                                                  \
3768         G_STMT_END
3769 
3770       if (priv->log_backtrace)
3771         {
3772           n_threads = gimp_backtrace_get_n_threads (priv->log_backtrace);
3773 
3774           for (thread = 0; thread < n_threads; thread++)
3775             {
3776               guintptr thread_id;
3777 
3778               thread_id = gimp_backtrace_get_thread_id (priv->log_backtrace,
3779                                                         thread);
3780 
3781               if (gimp_backtrace_find_thread_by_id (backtrace,
3782                                                     thread_id, thread) < 0)
3783                 {
3784                   const gchar *thread_name;
3785 
3786                   BACKTRACE_NONEMPTY ();
3787 
3788                   thread_name =
3789                     gimp_backtrace_get_thread_name (priv->log_backtrace,
3790                                                     thread);
3791 
3792                   gimp_dashboard_log_printf (dashboard,
3793                                              "<thread id=\"%llu\"",
3794                                              (unsigned long long) thread_id);
3795 
3796                   if (thread_name)
3797                     {
3798                       gimp_dashboard_log_printf (dashboard,
3799                                                  " name=\"");
3800                       gimp_dashboard_log_print_escaped (dashboard, thread_name);
3801                       gimp_dashboard_log_printf (dashboard,
3802                                                  "\"");
3803                     }
3804 
3805                   gimp_dashboard_log_printf (dashboard,
3806                                              " />\n");
3807                 }
3808             }
3809         }
3810 
3811       n_threads = gimp_backtrace_get_n_threads (backtrace);
3812 
3813       for (thread = 0; thread < n_threads; thread++)
3814         {
3815           guintptr     thread_id;
3816           const gchar *thread_name;
3817           gint         last_running  = -1;
3818           gint         running;
3819           gint         last_n_frames = -1;
3820           gint         n_frames;
3821           gint         n_head        = 0;
3822           gint         n_tail        = 0;
3823           gint         frame;
3824 
3825           thread_id   = gimp_backtrace_get_thread_id     (backtrace, thread);
3826           thread_name = gimp_backtrace_get_thread_name   (backtrace, thread);
3827 
3828           running     = gimp_backtrace_is_thread_running (backtrace, thread);
3829           n_frames    = gimp_backtrace_get_n_frames      (backtrace, thread);
3830 
3831           if (priv->log_backtrace)
3832             {
3833               gint other_thread = gimp_backtrace_find_thread_by_id (
3834                 priv->log_backtrace, thread_id, thread);
3835 
3836               if (other_thread >= 0)
3837                 {
3838                   gint n;
3839                   gint i;
3840 
3841                   last_running  = gimp_backtrace_is_thread_running (
3842                     priv->log_backtrace, other_thread);
3843                   last_n_frames = gimp_backtrace_get_n_frames (
3844                     priv->log_backtrace, other_thread);
3845 
3846                   n = MIN (n_frames, last_n_frames);
3847 
3848                   for (i = 0; i < n; i++)
3849                     {
3850                       if (gimp_backtrace_get_frame_address (backtrace,
3851                                                             thread, i) !=
3852                           gimp_backtrace_get_frame_address (priv->log_backtrace,
3853                                                             other_thread, i))
3854                         {
3855                           break;
3856                         }
3857                     }
3858 
3859                   n_head  = i;
3860                   n      -= i;
3861 
3862                   for (i = 0; i < n; i++)
3863                     {
3864                       if (gimp_backtrace_get_frame_address (backtrace,
3865                                                             thread, -i - 1) !=
3866                           gimp_backtrace_get_frame_address (priv->log_backtrace,
3867                                                             other_thread, -i - 1))
3868                         {
3869                           break;
3870                         }
3871                     }
3872 
3873                   n_tail = i;
3874                 }
3875             }
3876 
3877           if (running         == last_running  &&
3878               n_frames        == last_n_frames &&
3879               n_head + n_tail == n_frames)
3880             {
3881               continue;
3882             }
3883 
3884           BACKTRACE_NONEMPTY ();
3885 
3886           gimp_dashboard_log_printf (dashboard,
3887                                      "<thread id=\"%llu\"",
3888                                      (unsigned long long) thread_id);
3889 
3890           if (thread_name)
3891             {
3892               gimp_dashboard_log_printf (dashboard,
3893                                          " name=\"");
3894               gimp_dashboard_log_print_escaped (dashboard, thread_name);
3895               gimp_dashboard_log_printf (dashboard,
3896                                          "\"");
3897             }
3898 
3899           gimp_dashboard_log_printf (dashboard,
3900                                      " running=\"%d\"",
3901                                      running);
3902 
3903           if (n_head > 0)
3904             {
3905               gimp_dashboard_log_printf (dashboard,
3906                                          " head=\"%d\"",
3907                                          n_head);
3908             }
3909 
3910           if (n_tail > 0)
3911             {
3912               gimp_dashboard_log_printf (dashboard,
3913                                          " tail=\"%d\"",
3914                                          n_tail);
3915             }
3916 
3917           if (n_frames == 0 || n_head + n_tail < n_frames)
3918             {
3919               gimp_dashboard_log_printf (dashboard,
3920                                           ">\n");
3921 
3922               for (frame = n_head; frame < n_frames - n_tail; frame++)
3923                 {
3924                   guintptr address;
3925 
3926                   address = gimp_backtrace_get_frame_address (backtrace,
3927                                                               thread, frame);
3928 
3929                   gimp_dashboard_log_printf (dashboard,
3930                                              "<frame address=\"0x%llx\" />\n",
3931                                              (unsigned long long) address);
3932 
3933                   if (g_hash_table_add (priv->log_addresses,
3934                                         (gpointer) address) &&
3935                       priv->log_params.progressive)
3936                     {
3937                       if (! addresses)
3938                         {
3939                           addresses = g_array_new (FALSE, FALSE,
3940                                                    sizeof (guintptr));
3941                         }
3942 
3943                       g_array_append_val (addresses, address);
3944                     }
3945                 }
3946 
3947               gimp_dashboard_log_printf (dashboard,
3948                                          "</thread>\n");
3949             }
3950           else
3951             {
3952               gimp_dashboard_log_printf (dashboard,
3953                                          " />\n");
3954             }
3955         }
3956 
3957       if (! backtrace_empty)
3958         {
3959           gimp_dashboard_log_printf (dashboard,
3960                                      "</backtrace>\n");
3961         }
3962 
3963       #undef BACKTRACE_NONEMPTY
3964     }
3965   else if (priv->log_backtrace)
3966     {
3967       NONEMPTY ();
3968 
3969       gimp_dashboard_log_printf (dashboard,
3970                                  "<backtrace />\n");
3971     }
3972 
3973   gimp_backtrace_free (priv->log_backtrace);
3974   priv->log_backtrace = backtrace;
3975 
3976   if (empty)
3977     {
3978       gimp_dashboard_log_printf (dashboard,
3979                                  " />\n");
3980     }
3981   else
3982     {
3983       gimp_dashboard_log_printf (dashboard,
3984                                  "</sample>\n");
3985     }
3986 
3987   if (addresses)
3988     {
3989       gimp_dashboard_log_write_address_map (dashboard,
3990                                             (guintptr *) addresses->data,
3991                                             addresses->len,
3992                                             NULL);
3993 
3994       g_array_free (addresses, TRUE);
3995     }
3996 
3997   if (priv->log_params.progressive)
3998     g_output_stream_flush (priv->log_output, NULL, NULL);
3999 
4000   #undef NONEMPTY
4001 
4002   priv->log_n_samples++;
4003 }
4004 
4005 static void
gimp_dashboard_log_add_marker_unlocked(GimpDashboard * dashboard,const gchar * description)4006 gimp_dashboard_log_add_marker_unlocked (GimpDashboard *dashboard,
4007                                         const gchar   *description)
4008 {
4009   GimpDashboardPrivate *priv = dashboard->priv;
4010 
4011   priv->log_n_markers++;
4012 
4013   gimp_dashboard_log_printf (dashboard,
4014                              "\n"
4015                              "<marker id=\"%d\" t=\"%lld\"",
4016                              priv->log_n_markers,
4017                              (long long) gimp_dashboard_log_time (dashboard));
4018 
4019   if (description && description[0])
4020     {
4021       gimp_dashboard_log_printf (dashboard,
4022                                  ">\n");
4023       gimp_dashboard_log_print_escaped (dashboard, description);
4024       gimp_dashboard_log_printf (dashboard,
4025                                  "\n"
4026                                  "</marker>\n");
4027     }
4028   else
4029     {
4030       gimp_dashboard_log_printf (dashboard,
4031                                  " />\n");
4032     }
4033 
4034   gimp_dashboard_log_update_n_markers (dashboard);
4035 }
4036 
4037 static void
gimp_dashboard_log_update_highlight(GimpDashboard * dashboard)4038 gimp_dashboard_log_update_highlight (GimpDashboard *dashboard)
4039 {
4040   GimpDashboardPrivate *priv = dashboard->priv;
4041 
4042   gimp_highlightable_button_set_highlight (
4043     priv->log_record_button,
4044     gimp_dashboard_log_is_recording (dashboard));
4045 }
4046 
4047 static void
gimp_dashboard_log_update_n_markers(GimpDashboard * dashboard)4048 gimp_dashboard_log_update_n_markers (GimpDashboard *dashboard)
4049 {
4050   GimpDashboardPrivate *priv = dashboard->priv;
4051   gchar                 buffer[32];
4052 
4053   g_snprintf (buffer, sizeof (buffer), "%d", priv->log_n_markers + 1);
4054 
4055   gtk_label_set_text (priv->log_add_marker_label, buffer);
4056 }
4057 
4058 static gint
gimp_dashboard_log_compare_addresses(gconstpointer a1,gconstpointer a2)4059 gimp_dashboard_log_compare_addresses (gconstpointer a1,
4060                                       gconstpointer a2)
4061 {
4062   guintptr address1 = *(const guintptr *) a1;
4063   guintptr address2 = *(const guintptr *) a2;
4064 
4065   if (address1 < address2)
4066     return -1;
4067   else if (address1 > address2)
4068     return +1;
4069   else
4070     return 0;
4071 }
4072 
4073 static void
gimp_dashboard_log_write_address_map(GimpDashboard * dashboard,guintptr * addresses,gint n_addresses,GimpAsync * async)4074 gimp_dashboard_log_write_address_map (GimpDashboard *dashboard,
4075                                       guintptr      *addresses,
4076                                       gint           n_addresses,
4077                                       GimpAsync     *async)
4078 {
4079   GimpBacktraceAddressInfo infos[2];
4080   gint                     i;
4081   gint                     n;
4082 
4083   if (n_addresses == 0)
4084     return;
4085 
4086   qsort (addresses, n_addresses, sizeof (guintptr),
4087          gimp_dashboard_log_compare_addresses);
4088 
4089   gimp_dashboard_log_printf (dashboard,
4090                              "\n"
4091                              "<address-map>\n");
4092 
4093   n = 0;
4094 
4095   for (i = 0; i < n_addresses; i++)
4096     {
4097       GimpBacktraceAddressInfo       *info      = &infos[n       % 2];
4098       const GimpBacktraceAddressInfo *prev_info = &infos[(n + 1) % 2];
4099 
4100       if (async && gimp_async_is_canceled (async))
4101         break;
4102 
4103       if (gimp_backtrace_get_address_info (addresses[i], info))
4104         {
4105           gboolean empty = TRUE;
4106 
4107           #define NONEMPTY()                              \
4108             G_STMT_START                                  \
4109               {                                           \
4110                 if (empty)                                \
4111                   {                                       \
4112                     gimp_dashboard_log_printf (dashboard, \
4113                                                ">\n");    \
4114                                                           \
4115                     empty = FALSE;                        \
4116                   }                                       \
4117               }                                           \
4118             G_STMT_END
4119 
4120           gimp_dashboard_log_printf (dashboard,
4121                                      "\n"
4122                                      "<address value=\"0x%llx\"",
4123                                      (unsigned long long) addresses[i]);
4124 
4125           if (n == 0 || strcmp (info->object_name, prev_info->object_name))
4126             {
4127               NONEMPTY ();
4128 
4129               if (info->object_name[0])
4130                 {
4131                   gimp_dashboard_log_printf (dashboard,
4132                                              "<object>");
4133                   gimp_dashboard_log_print_escaped (dashboard,
4134                                                     info->object_name);
4135                   gimp_dashboard_log_printf (dashboard,
4136                                              "</object>\n");
4137                 }
4138               else
4139                 {
4140                   gimp_dashboard_log_printf (dashboard,
4141                                              "<object />\n");
4142                 }
4143             }
4144 
4145           if (n == 0 || strcmp (info->symbol_name, prev_info->symbol_name))
4146             {
4147               NONEMPTY ();
4148 
4149               if (info->symbol_name[0])
4150                 {
4151                   gimp_dashboard_log_printf (dashboard,
4152                                              "<symbol>");
4153                   gimp_dashboard_log_print_escaped (dashboard,
4154                                                     info->symbol_name);
4155                   gimp_dashboard_log_printf (dashboard,
4156                                              "</symbol>\n");
4157                 }
4158               else
4159                 {
4160                   gimp_dashboard_log_printf (dashboard,
4161                                              "<symbol />\n");
4162                 }
4163             }
4164 
4165           if (n == 0 || info->symbol_address != prev_info->symbol_address)
4166             {
4167               NONEMPTY ();
4168 
4169               if (info->symbol_address)
4170                 {
4171                   gimp_dashboard_log_printf (dashboard,
4172                                              "<base>0x%llx</base>\n",
4173                                              (unsigned long long)
4174                                                info->symbol_address);
4175                 }
4176               else
4177                 {
4178                   gimp_dashboard_log_printf (dashboard,
4179                                              "<base />\n");
4180                 }
4181             }
4182 
4183           if (n == 0 || strcmp (info->source_file, prev_info->source_file))
4184             {
4185               NONEMPTY ();
4186 
4187               if (info->source_file[0])
4188                 {
4189                   gimp_dashboard_log_printf (dashboard,
4190                                              "<source>");
4191                   gimp_dashboard_log_print_escaped (dashboard,
4192                                                     info->source_file);
4193                   gimp_dashboard_log_printf (dashboard,
4194                                              "</source>\n");
4195                 }
4196               else
4197                 {
4198                   gimp_dashboard_log_printf (dashboard,
4199                                              "<source />\n");
4200                 }
4201             }
4202 
4203           if (n == 0 || info->source_line != prev_info->source_line)
4204             {
4205               NONEMPTY ();
4206 
4207               if (info->source_line)
4208                 {
4209                   gimp_dashboard_log_printf (dashboard,
4210                                              "<line>%d</line>\n",
4211                                              info->source_line);
4212                 }
4213               else
4214                 {
4215                   gimp_dashboard_log_printf (dashboard,
4216                                              "<line />\n");
4217                 }
4218             }
4219 
4220           if (empty)
4221             {
4222               gimp_dashboard_log_printf (dashboard,
4223                                          " />\n");
4224             }
4225           else
4226             {
4227               gimp_dashboard_log_printf (dashboard,
4228                                          "</address>\n");
4229             }
4230 
4231           #undef NONEMPTY
4232 
4233           n++;
4234         }
4235     }
4236 
4237   gimp_dashboard_log_printf (dashboard,
4238                              "\n"
4239                              "</address-map>\n");
4240 }
4241 
4242 static void
gimp_dashboard_log_write_global_address_map(GimpAsync * async,GimpDashboard * dashboard)4243 gimp_dashboard_log_write_global_address_map (GimpAsync     *async,
4244                                              GimpDashboard *dashboard)
4245 {
4246   GimpDashboardPrivate *priv = dashboard->priv;
4247   gint                  n_addresses;
4248 
4249   n_addresses = g_hash_table_size (priv->log_addresses);
4250 
4251   if (n_addresses > 0)
4252     {
4253       guintptr *addresses;
4254       GList    *iter;
4255       gint      i;
4256 
4257       addresses = g_new (guintptr, n_addresses);
4258 
4259       for (iter = g_hash_table_get_keys (priv->log_addresses), i = 0;
4260            iter;
4261            iter = g_list_next (iter), i++)
4262         {
4263           addresses[i] = (guintptr) iter->data;
4264         }
4265 
4266       gimp_dashboard_log_write_address_map (dashboard,
4267                                             addresses, n_addresses,
4268                                             async);
4269 
4270       g_free (addresses);
4271     }
4272 
4273   gimp_async_finish (async, NULL);
4274 }
4275 
4276 static void
gimp_dashboard_log_log_func(const gchar * log_domain,GLogLevelFlags log_levels,const gchar * message,GimpDashboard * dashboard)4277 gimp_dashboard_log_log_func (const gchar    *log_domain,
4278                              GLogLevelFlags  log_levels,
4279                              const gchar    *message,
4280                              GimpDashboard  *dashboard)
4281 {
4282   GimpDashboardPrivate *priv      = dashboard->priv;
4283   const gchar          *log_level = NULL;
4284   gchar                *description;
4285 
4286   g_mutex_lock (&priv->mutex);
4287 
4288   switch (log_levels & G_LOG_LEVEL_MASK)
4289     {
4290     case G_LOG_LEVEL_ERROR:    log_level = "ERROR";    break;
4291     case G_LOG_LEVEL_CRITICAL: log_level = "CRITICAL"; break;
4292     case G_LOG_LEVEL_WARNING:  log_level = "WARNING";  break;
4293     case G_LOG_LEVEL_MESSAGE:  log_level = "MESSAGE";  break;
4294     case G_LOG_LEVEL_INFO:     log_level = "INFO";     break;
4295     case G_LOG_LEVEL_DEBUG:    log_level = "DEBUG";    break;
4296     default:                   log_level = "UNKNOWN";  break;
4297     }
4298 
4299   description = g_strdup_printf ("[%s] %s: %s", log_domain, log_level, message);
4300 
4301   gimp_dashboard_log_add_marker_unlocked (dashboard, description);
4302 
4303   gimp_dashboard_log_sample (dashboard, FALSE, TRUE);
4304 
4305   g_free (description);
4306 
4307   g_mutex_unlock (&priv->mutex);
4308 }
4309 
4310 static gboolean
gimp_dashboard_field_use_meter_underlay(Group group,gint field)4311 gimp_dashboard_field_use_meter_underlay (Group group,
4312                                          gint  field)
4313 {
4314   const GroupInfo    *group_info = &groups[group];
4315   Variable            variable   = group_info->fields[field].variable;
4316   const VariableInfo *variable_info;
4317 
4318   if (group_info->fields[field].meter_variable)
4319     variable = group_info->fields[field].meter_variable;
4320 
4321   variable_info = &variables [variable];
4322 
4323   return variable_info->type == VARIABLE_TYPE_BOOLEAN ||
4324          (group_info->fields[field].meter_variable    &&
4325           variable_info->type == VARIABLE_TYPE_RATE_OF_CHANGE);
4326 }
4327 
4328 static gchar *
gimp_dashboard_format_rate_of_change(const gchar * value)4329 gimp_dashboard_format_rate_of_change (const gchar *value)
4330 {
4331   /* Translators:  This string reports the rate of change of a measured value.
4332    * The first "%s" is replaced by a certain quantity, usually followed by a
4333    * unit of measurement (e.g., "10 bytes"). and the final "/s" is an
4334    * abbreviation for "per second" (so the full string would read
4335    * "10 bytes/s", that is, "10 bytes per second".
4336    */
4337   return g_strdup_printf (_("%s/s"), value);
4338 }
4339 
4340 static gchar *
gimp_dashboard_format_value(VariableType type,gdouble value)4341 gimp_dashboard_format_value (VariableType type,
4342                              gdouble      value)
4343 {
4344   switch (type)
4345     {
4346     case VARIABLE_TYPE_BOOLEAN:
4347       return g_strdup (value ? C_("dashboard-value", "Yes") :
4348                                C_("dashboard-value", "No"));
4349 
4350     case VARIABLE_TYPE_INTEGER:
4351       return g_strdup_printf ("%g", value);
4352 
4353     case VARIABLE_TYPE_SIZE:
4354       return g_format_size_full (value, G_FORMAT_SIZE_IEC_UNITS);
4355 
4356     case VARIABLE_TYPE_SIZE_RATIO:
4357       if (isfinite (value))
4358         return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value));
4359       break;
4360 
4361     case VARIABLE_TYPE_INT_RATIO:
4362       if (isfinite (value))
4363         {
4364           gdouble  min;
4365           gdouble  max;
4366           gdouble  antecedent;
4367           gdouble  consequent;
4368 
4369           antecedent = value;
4370           consequent = 1.0;
4371 
4372           min = MIN (ABS (antecedent), ABS (consequent));
4373           max = MAX (ABS (antecedent), ABS (consequent));
4374 
4375           if (min)
4376             {
4377               antecedent /= min;
4378               consequent /= min;
4379             }
4380           else
4381             {
4382               antecedent /= max;
4383               consequent /= max;
4384             }
4385 
4386           return g_strdup_printf ("%g:%g",
4387                                   RINT (100.0 * antecedent) / 100.0,
4388                                   RINT (100.0 * consequent) / 100.0);
4389         }
4390       else if (isinf (value))
4391         {
4392           return g_strdup ("1:0");
4393         }
4394       break;
4395 
4396     case VARIABLE_TYPE_PERCENTAGE:
4397       return g_strdup_printf ("%d%%", SIGNED_ROUND (100.0 * value));
4398 
4399     case VARIABLE_TYPE_DURATION:
4400       return g_strdup_printf ("%02d:%02d:%04.1f",
4401                               (gint) floor (value / 3600.0),
4402                               (gint) floor (fmod (value / 60.0, 60.0)),
4403                               floor (fmod (value, 60.0) * 10.0) / 10.0);
4404 
4405     case VARIABLE_TYPE_RATE_OF_CHANGE:
4406       {
4407         gchar buf[64];
4408 
4409         g_snprintf (buf, sizeof (buf), "%g", value);
4410 
4411         return gimp_dashboard_format_rate_of_change (buf);
4412       }
4413     }
4414 
4415   return g_strdup (_("N/A"));
4416 }
4417 
4418 static void
gimp_dashboard_label_set_text(GtkLabel * label,const gchar * text)4419 gimp_dashboard_label_set_text (GtkLabel    *label,
4420                                const gchar *text)
4421 {
4422   /* the strcmp() reduces the overhead of gtk_label_set_text() when the
4423    * text hasn't changed
4424    */
4425   if (g_strcmp0 (gtk_label_get_text (label), text))
4426     gtk_label_set_text (label, text);
4427 }
4428 
4429 
4430 /*  public functions  */
4431 
4432 
4433 GtkWidget *
gimp_dashboard_new(Gimp * gimp,GimpMenuFactory * menu_factory)4434 gimp_dashboard_new (Gimp            *gimp,
4435                     GimpMenuFactory *menu_factory)
4436 {
4437   GimpDashboard *dashboard;
4438 
4439   dashboard = g_object_new (GIMP_TYPE_DASHBOARD,
4440                             "menu-factory",    menu_factory,
4441                             "menu-identifier", "<Dashboard>",
4442                             "ui-path",         "/dashboard-popup",
4443                             NULL);
4444 
4445   dashboard->priv->gimp = gimp;
4446 
4447   return GTK_WIDGET (dashboard);
4448 }
4449 
4450 gboolean
gimp_dashboard_log_start_recording(GimpDashboard * dashboard,GFile * file,const GimpDashboardLogParams * params,GError ** error)4451 gimp_dashboard_log_start_recording (GimpDashboard                 *dashboard,
4452                                     GFile                         *file,
4453                                     const GimpDashboardLogParams  *params,
4454                                     GError                       **error)
4455 {
4456   GimpDashboardPrivate  *priv;
4457   GimpUIManager         *ui_manager;
4458   GimpActionGroup       *action_group;
4459   gchar                 *version;
4460   gchar                **envp;
4461   gchar                **env;
4462   GParamSpec           **pspecs;
4463   guint                  n_pspecs;
4464   gboolean               has_backtrace;
4465   Variable               variable;
4466   guint                  i;
4467 
4468   g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
4469   g_return_val_if_fail (G_IS_FILE (file), FALSE);
4470   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
4471 
4472   priv = dashboard->priv;
4473 
4474   g_return_val_if_fail (! gimp_dashboard_log_is_recording (dashboard), FALSE);
4475 
4476   if (! params)
4477     params = gimp_dashboard_log_get_default_params (dashboard);
4478 
4479   priv->log_params = *params;
4480 
4481   if (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY"))
4482     {
4483       priv->log_params.sample_frequency =
4484         atoi (g_getenv ("GIMP_PERFORMANCE_LOG_SAMPLE_FREQUENCY"));
4485     }
4486 
4487   if (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE"))
4488     {
4489       priv->log_params.backtrace =
4490         atoi (g_getenv ("GIMP_PERFORMANCE_LOG_BACKTRACE")) ? 1 : 0;
4491     }
4492 
4493   if (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES"))
4494     {
4495       priv->log_params.messages =
4496         atoi (g_getenv ("GIMP_PERFORMANCE_LOG_MESSAGES")) ? 1 : 0;
4497     }
4498 
4499   if (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE"))
4500     {
4501       priv->log_params.progressive =
4502         atoi (g_getenv ("GIMP_PERFORMANCE_LOG_PROGRESSIVE")) ? 1 : 0;
4503     }
4504 
4505   priv->log_params.sample_frequency = CLAMP (priv->log_params.sample_frequency,
4506                                              LOG_SAMPLE_FREQUENCY_MIN,
4507                                              LOG_SAMPLE_FREQUENCY_MAX);
4508 
4509   g_mutex_lock (&priv->mutex);
4510 
4511   if (priv->log_params.progressive     &&
4512       g_file_query_exists (file, NULL) &&
4513       ! g_file_delete (file, NULL, error))
4514     {
4515       g_mutex_unlock (&priv->mutex);
4516 
4517       return FALSE;
4518     }
4519 
4520   priv->log_output = G_OUTPUT_STREAM (g_file_replace (file,
4521                                       NULL, FALSE, G_FILE_CREATE_NONE, NULL,
4522                                       error));
4523 
4524   if (! priv->log_output)
4525     {
4526       g_mutex_unlock (&priv->mutex);
4527 
4528       return FALSE;
4529     }
4530 
4531   priv->log_error      = NULL;
4532   priv->log_start_time = g_get_monotonic_time ();
4533   priv->log_n_samples  = 0;
4534   priv->log_n_markers  = 0;
4535   priv->log_backtrace  = NULL;
4536   priv->log_addresses  = g_hash_table_new (NULL, NULL);
4537 
4538   if (priv->log_params.backtrace)
4539     has_backtrace = gimp_backtrace_start ();
4540   else
4541     has_backtrace = FALSE;
4542 
4543   gimp_dashboard_log_printf (dashboard,
4544                              "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
4545                              "<gimp-performance-log version=\"%d\">\n",
4546                              LOG_VERSION);
4547 
4548   gimp_dashboard_log_printf (dashboard,
4549                              "\n"
4550                              "<params>\n"
4551                              "<sample-frequency>%d</sample-frequency>\n"
4552                              "<backtrace>%d</backtrace>\n"
4553                              "<messages>%d</messages>\n"
4554                              "<progressive>%d</progressive>\n"
4555                              "</params>\n",
4556                              priv->log_params.sample_frequency,
4557                              has_backtrace,
4558                              priv->log_params.messages,
4559                              priv->log_params.progressive);
4560 
4561   gimp_dashboard_log_printf (dashboard,
4562                              "\n"
4563                              "<info>\n");
4564 
4565   version = gimp_version (TRUE, FALSE);
4566 
4567   gimp_dashboard_log_printf (dashboard,
4568                              "\n"
4569                              "<gimp-version>\n");
4570   gimp_dashboard_log_print_escaped (dashboard, version);
4571   gimp_dashboard_log_printf (dashboard,
4572                              "</gimp-version>\n");
4573 
4574   g_free (version);
4575 
4576   gimp_dashboard_log_printf (dashboard,
4577                              "\n"
4578                              "<env>\n");
4579 
4580   envp = g_get_environ ();
4581 
4582   for (env = envp; *env; env++)
4583     {
4584       if (g_str_has_prefix (*env, "BABL_") ||
4585           g_str_has_prefix (*env, "GEGL_") ||
4586           g_str_has_prefix (*env, "GIMP_"))
4587         {
4588           gchar       *delim = strchr (*env, '=');
4589           const gchar *s;
4590 
4591           if (! delim)
4592             continue;
4593 
4594           for (s = *env;
4595                s != delim && (g_ascii_isalnum (*s) || *s == '_' || *s == '-');
4596                s++);
4597 
4598           if (s != delim)
4599             continue;
4600 
4601           *delim = '\0';
4602 
4603           gimp_dashboard_log_printf (dashboard,
4604                                      "<%s>",
4605                                      *env);
4606           gimp_dashboard_log_print_escaped (dashboard, delim + 1);
4607           gimp_dashboard_log_printf (dashboard,
4608                                      "</%s>\n",
4609                                      *env);
4610         }
4611     }
4612 
4613   g_strfreev (envp);
4614 
4615   gimp_dashboard_log_printf (dashboard,
4616                              "</env>\n");
4617 
4618   gimp_dashboard_log_printf (dashboard,
4619                              "\n"
4620                              "<gegl-config>\n");
4621 
4622   pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (gegl_config ()),
4623                                            &n_pspecs);
4624 
4625   for (i = 0; i < n_pspecs; i++)
4626     {
4627       const GParamSpec *pspec     = pspecs[i];
4628       GValue            value     = {};
4629       GValue            str_value = {};
4630 
4631       g_value_init (&value,     pspec->value_type);
4632       g_value_init (&str_value, G_TYPE_STRING);
4633 
4634       g_object_get_property (G_OBJECT (gegl_config ()), pspec->name, &value);
4635 
4636       if (g_value_transform (&value, &str_value))
4637         {
4638           gimp_dashboard_log_printf (dashboard,
4639                                      "<%s>",
4640                                      pspec->name);
4641           gimp_dashboard_log_print_escaped (dashboard,
4642                                             g_value_get_string (&str_value));
4643           gimp_dashboard_log_printf (dashboard,
4644                                      "</%s>\n",
4645                                      pspec->name);
4646         }
4647 
4648       g_value_unset (&str_value);
4649       g_value_unset (&value);
4650     }
4651 
4652   g_free (pspecs);
4653 
4654   gimp_dashboard_log_printf (dashboard,
4655                              "</gegl-config>\n");
4656 
4657   gimp_dashboard_log_printf (dashboard,
4658                              "\n"
4659                              "</info>\n");
4660 
4661   gimp_dashboard_log_printf (dashboard,
4662                              "\n"
4663                              "<var-defs>\n");
4664 
4665   for (variable = FIRST_VARIABLE; variable < N_VARIABLES; variable++)
4666     {
4667       const VariableInfo *variable_info = &variables[variable];
4668       const gchar        *type          = "";
4669 
4670       if (variable_info->exclude_from_log)
4671         continue;
4672 
4673       switch (variable_info->type)
4674         {
4675         case VARIABLE_TYPE_BOOLEAN:        type = "boolean";        break;
4676         case VARIABLE_TYPE_INTEGER:        type = "integer";        break;
4677         case VARIABLE_TYPE_SIZE:           type = "size";           break;
4678         case VARIABLE_TYPE_SIZE_RATIO:     type = "size-ratio";     break;
4679         case VARIABLE_TYPE_INT_RATIO:      type = "int-ratio";      break;
4680         case VARIABLE_TYPE_PERCENTAGE:     type = "percentage";     break;
4681         case VARIABLE_TYPE_DURATION:       type = "duration";       break;
4682         case VARIABLE_TYPE_RATE_OF_CHANGE: type = "rate-of-change"; break;
4683         }
4684 
4685       gimp_dashboard_log_printf (dashboard,
4686                                  "<var name=\"%s\" type=\"%s\" desc=\"",
4687                                  variable_info->name,
4688                                  type);
4689       gimp_dashboard_log_print_escaped (dashboard,
4690                                         /* intentionally untranslated */
4691                                         variable_info->description);
4692       gimp_dashboard_log_printf (dashboard,
4693                                  "\" />\n");
4694     }
4695 
4696   gimp_dashboard_log_printf (dashboard,
4697                              "</var-defs>\n");
4698 
4699   gimp_dashboard_log_printf (dashboard,
4700                              "\n"
4701                              "<samples>\n");
4702 
4703   if (priv->log_error)
4704     {
4705       GCancellable *cancellable = g_cancellable_new ();
4706 
4707       gimp_backtrace_stop ();
4708 
4709       /* Cancel the overwrite initiated by g_file_replace(). */
4710       g_cancellable_cancel (cancellable);
4711       g_output_stream_close (priv->log_output, cancellable, NULL);
4712       g_object_unref (cancellable);
4713 
4714       g_clear_object (&priv->log_output);
4715 
4716       g_propagate_error (error, priv->log_error);
4717       priv->log_error = NULL;
4718 
4719       g_mutex_unlock (&priv->mutex);
4720 
4721       return FALSE;
4722     }
4723 
4724   gimp_dashboard_reset_unlocked (dashboard);
4725 
4726   if (priv->log_params.messages)
4727     {
4728       priv->log_log_handler = gimp_log_set_handler (
4729         TRUE,
4730         G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
4731         (GLogFunc) gimp_dashboard_log_log_func,
4732         dashboard);
4733     }
4734 
4735   priv->update_now = TRUE;
4736   g_cond_signal (&priv->cond);
4737 
4738   g_mutex_unlock (&priv->mutex);
4739 
4740   gimp_dashboard_log_update_n_markers (dashboard);
4741 
4742   ui_manager   = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
4743   action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
4744 
4745   gimp_action_group_update (action_group, dashboard);
4746 
4747   gimp_dashboard_log_update_highlight (dashboard);
4748 
4749   return TRUE;
4750 }
4751 
4752 gboolean
gimp_dashboard_log_stop_recording(GimpDashboard * dashboard,GError ** error)4753 gimp_dashboard_log_stop_recording (GimpDashboard  *dashboard,
4754                                    GError        **error)
4755 {
4756   GimpDashboardPrivate *priv;
4757   GimpUIManager        *ui_manager;
4758   GimpActionGroup      *action_group;
4759   gboolean              result = TRUE;
4760 
4761   g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
4762   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
4763 
4764   priv = dashboard->priv;
4765 
4766   if (! gimp_dashboard_log_is_recording (dashboard))
4767     return TRUE;
4768 
4769   g_mutex_lock (&priv->mutex);
4770 
4771   if (priv->log_log_handler)
4772     {
4773       gimp_log_remove_handler (priv->log_log_handler);
4774 
4775       priv->log_log_handler = 0;
4776     }
4777 
4778   gimp_dashboard_log_printf (dashboard,
4779                              "\n"
4780                              "</samples>\n");
4781 
4782 
4783   if (! priv->log_params.progressive &&
4784       g_hash_table_size (priv->log_addresses) > 0)
4785     {
4786       GimpAsync *async;
4787 
4788       async = gimp_parallel_run_async_independent (
4789         (GimpRunAsyncFunc) gimp_dashboard_log_write_global_address_map,
4790         dashboard);
4791 
4792       gimp_wait (priv->gimp, GIMP_WAITABLE (async),
4793                  _("Resolving symbol information..."));
4794 
4795       g_object_unref (async);
4796     }
4797 
4798   gimp_dashboard_log_printf (dashboard,
4799                              "\n"
4800                              "</gimp-performance-log>\n");
4801 
4802   if (priv->log_params.backtrace)
4803     gimp_backtrace_stop ();
4804 
4805   if (! priv->log_error)
4806     {
4807       g_output_stream_close (priv->log_output, NULL, &priv->log_error);
4808     }
4809   else
4810     {
4811       GCancellable *cancellable = g_cancellable_new ();
4812 
4813       /* Cancel the overwrite initiated by g_file_replace(). */
4814       g_cancellable_cancel (cancellable);
4815       g_output_stream_close (priv->log_output, cancellable, NULL);
4816       g_object_unref (cancellable);
4817     }
4818 
4819   g_clear_object (&priv->log_output);
4820 
4821   if (priv->log_error)
4822     {
4823       g_propagate_error (error, priv->log_error);
4824       priv->log_error = NULL;
4825 
4826       result = FALSE;
4827     }
4828 
4829   g_clear_pointer (&priv->log_backtrace, gimp_backtrace_free);
4830   g_clear_pointer (&priv->log_addresses, g_hash_table_unref);
4831 
4832   g_mutex_unlock (&priv->mutex);
4833 
4834   ui_manager   = gimp_editor_get_ui_manager (GIMP_EDITOR (dashboard));
4835   action_group = gimp_ui_manager_get_action_group (ui_manager, "dashboard");
4836 
4837   gimp_action_group_update (action_group, dashboard);
4838 
4839   gimp_dashboard_log_update_highlight (dashboard);
4840 
4841   return result;
4842 }
4843 
4844 gboolean
gimp_dashboard_log_is_recording(GimpDashboard * dashboard)4845 gimp_dashboard_log_is_recording (GimpDashboard *dashboard)
4846 {
4847   GimpDashboardPrivate *priv;
4848 
4849   g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), FALSE);
4850 
4851   priv = dashboard->priv;
4852 
4853   return priv->log_output != NULL;
4854 }
4855 
4856 const GimpDashboardLogParams *
gimp_dashboard_log_get_default_params(GimpDashboard * dashboard)4857 gimp_dashboard_log_get_default_params (GimpDashboard *dashboard)
4858 {
4859   static const GimpDashboardLogParams default_params =
4860   {
4861     .sample_frequency = LOG_DEFAULT_SAMPLE_FREQUENCY,
4862     .backtrace        = LOG_DEFAULT_BACKTRACE,
4863     .messages         = LOG_DEFAULT_MESSAGES,
4864     .progressive      = LOG_DEFAULT_PROGRESSIVE
4865   };
4866 
4867   g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), NULL);
4868 
4869   return &default_params;
4870 }
4871 
4872 void
gimp_dashboard_log_add_marker(GimpDashboard * dashboard,const gchar * description)4873 gimp_dashboard_log_add_marker (GimpDashboard *dashboard,
4874                                const gchar   *description)
4875 {
4876   GimpDashboardPrivate *priv;
4877 
4878   g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
4879   g_return_if_fail (gimp_dashboard_log_is_recording (dashboard));
4880 
4881   priv = dashboard->priv;
4882 
4883   g_mutex_lock (&priv->mutex);
4884 
4885   gimp_dashboard_log_add_marker_unlocked (dashboard, description);
4886 
4887   g_mutex_unlock (&priv->mutex);
4888 }
4889 
4890 void
gimp_dashboard_reset(GimpDashboard * dashboard)4891 gimp_dashboard_reset (GimpDashboard *dashboard)
4892 {
4893   GimpDashboardPrivate *priv;
4894 
4895   g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
4896 
4897   priv = dashboard->priv;
4898 
4899   g_mutex_lock (&priv->mutex);
4900 
4901   gimp_dashboard_reset_unlocked (dashboard);
4902 
4903   priv->update_now = TRUE;
4904   g_cond_signal (&priv->cond);
4905 
4906   g_mutex_unlock (&priv->mutex);
4907 }
4908 
4909 void
gimp_dashboard_set_update_interval(GimpDashboard * dashboard,GimpDashboardUpdateInteval update_interval)4910 gimp_dashboard_set_update_interval (GimpDashboard              *dashboard,
4911                                     GimpDashboardUpdateInteval  update_interval)
4912 {
4913   GimpDashboardPrivate *priv;
4914 
4915   g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
4916 
4917   priv = dashboard->priv;
4918 
4919   if (update_interval != priv->update_interval)
4920     {
4921       Group group;
4922 
4923       g_mutex_lock (&priv->mutex);
4924 
4925       priv->update_interval = update_interval;
4926 
4927       for (group = FIRST_GROUP; group < N_GROUPS; group++)
4928         {
4929           GroupData *group_data = &priv->groups[group];
4930 
4931           if (group_data->meter)
4932             {
4933               gimp_meter_set_history_resolution (group_data->meter,
4934                                                  update_interval / 1000.0);
4935             }
4936         }
4937 
4938       priv->update_now = TRUE;
4939       g_cond_signal (&priv->cond);
4940 
4941       g_mutex_unlock (&priv->mutex);
4942     }
4943 }
4944 
4945 GimpDashboardUpdateInteval
gimp_dashboard_get_update_interval(GimpDashboard * dashboard)4946 gimp_dashboard_get_update_interval (GimpDashboard *dashboard)
4947 {
4948   g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_UPDATE_INTERVAL);
4949 
4950   return dashboard->priv->update_interval;
4951 }
4952 
4953 void
gimp_dashboard_set_history_duration(GimpDashboard * dashboard,GimpDashboardHistoryDuration history_duration)4954 gimp_dashboard_set_history_duration (GimpDashboard                *dashboard,
4955                                      GimpDashboardHistoryDuration  history_duration)
4956 {
4957   GimpDashboardPrivate *priv;
4958 
4959   g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
4960 
4961   priv = dashboard->priv;
4962 
4963   if (history_duration != priv->history_duration)
4964     {
4965       Group group;
4966 
4967       g_mutex_lock (&priv->mutex);
4968 
4969       priv->history_duration = history_duration;
4970 
4971       for (group = FIRST_GROUP; group < N_GROUPS; group++)
4972         {
4973           GroupData *group_data = &priv->groups[group];
4974 
4975           if (group_data->meter)
4976             {
4977               gimp_meter_set_history_duration (group_data->meter,
4978                                                history_duration / 1000.0);
4979             }
4980         }
4981 
4982       priv->update_now = TRUE;
4983       g_cond_signal (&priv->cond);
4984 
4985       g_mutex_unlock (&priv->mutex);
4986     }
4987 }
4988 
4989 GimpDashboardHistoryDuration
gimp_dashboard_get_history_duration(GimpDashboard * dashboard)4990 gimp_dashboard_get_history_duration (GimpDashboard *dashboard)
4991 {
4992   g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_HISTORY_DURATION);
4993 
4994   return dashboard->priv->history_duration;
4995 }
4996 
4997 void
gimp_dashboard_set_low_swap_space_warning(GimpDashboard * dashboard,gboolean low_swap_space_warning)4998 gimp_dashboard_set_low_swap_space_warning (GimpDashboard *dashboard,
4999                                            gboolean       low_swap_space_warning)
5000 {
5001   GimpDashboardPrivate *priv;
5002 
5003   g_return_if_fail (GIMP_IS_DASHBOARD (dashboard));
5004 
5005   priv = dashboard->priv;
5006 
5007   if (low_swap_space_warning != priv->low_swap_space_warning)
5008     {
5009       g_mutex_lock (&priv->mutex);
5010 
5011       priv->low_swap_space_warning = low_swap_space_warning;
5012 
5013       g_mutex_unlock (&priv->mutex);
5014     }
5015 }
5016 
5017 gboolean
gimp_dashboard_get_low_swap_space_warning(GimpDashboard * dashboard)5018 gimp_dashboard_get_low_swap_space_warning (GimpDashboard *dashboard)
5019 {
5020   g_return_val_if_fail (GIMP_IS_DASHBOARD (dashboard), DEFAULT_LOW_SWAP_SPACE_WARNING);
5021 
5022   return dashboard->priv->low_swap_space_warning;
5023 }
5024 
5025 void
gimp_dashboard_menu_setup(GimpUIManager * manager,const gchar * ui_path)5026 gimp_dashboard_menu_setup (GimpUIManager *manager,
5027                            const gchar   *ui_path)
5028 {
5029   guint merge_id;
5030   Group group;
5031 
5032   g_return_if_fail (GIMP_IS_UI_MANAGER (manager));
5033   g_return_if_fail (ui_path != NULL);
5034 
5035   merge_id = gimp_ui_manager_new_merge_id (manager);
5036 
5037   for (group = FIRST_GROUP; group < N_GROUPS; group++)
5038     {
5039       const GroupInfo *group_info = &groups[group];
5040       gchar           *action_name;
5041       gchar           *action_path;
5042 
5043       action_name = g_strdup_printf ("dashboard-group-%s", group_info->name);
5044       action_path = g_strdup_printf ("%s/Groups/Groups", ui_path);
5045 
5046       gimp_ui_manager_add_ui (manager, merge_id,
5047                               action_path, action_name, action_name,
5048                               GTK_UI_MANAGER_MENUITEM,
5049                               FALSE);
5050 
5051       g_free (action_name);
5052       g_free (action_path);
5053     }
5054 }
5055