Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
sentry.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#ifdef HAVE_CONFIG_H
20#include "config.h"
21#endif
22
23#include "common/sentry.h"
24#include "common/darktable.h"
25
26#ifdef HAVE_SENTRY
27
29#include "common/image.h"
30#include "common/opencl.h"
31#include "control/conf.h"
32#include "gui/gtk.h"
33
34#include <sentry.h>
35
36#include <signal.h> // for sig_atomic_t
37#include <string.h> // for strrchr, strcmp
38
39#if defined(__linux__)
40#include <fcntl.h> // for open flags
41#include <sys/prctl.h> // for PR_SET_PTRACER
42#include <sys/wait.h> // for waitpid
43#include <unistd.h> // for fork, getpid
44#endif
45
46#if defined(_WIN32)
47#ifndef WIN32_LEAN_AND_MEAN
48#define WIN32_LEAN_AND_MEAN // limit windows.h macro pollution next to GLib/GTK
49#endif
50#include <windows.h>
51#include <dbghelp.h> // for Sym*, locally symbolicated backtrace
52#endif
53
54#ifndef SENTRY_DSN
55#define SENTRY_DSN ""
56#endif
57
58// Conf keys. "sentry/enabled" is a confgen key (shown in Preferences); the two
59// below are intentionally NOT in confgen so dt_conf_key_exists() reflects whether
60// the user has actually decided / how many clean sessions were recorded.
61#define DT_SENTRY_ENABLED_KEY "sentry/enabled"
62#define DT_SENTRY_ASKED_KEY "sentry/consent_asked"
63#define DT_SENTRY_CLEAN_SESSIONS_KEY "sentry/clean_sessions"
64#define DT_SENTRY_LAST_SESSION_KEY "sentry/last_session_seconds"
65#define DT_SENTRY_TOTAL_SESSION_KEY "sentry/total_session_seconds"
66
67static gboolean _sentry_inited = FALSE;
68
69// Set once sentry's on_crash hook has captured a gdb backtrace, so the local
70// signal handler can skip running gdb a second time for the same crash.
71static volatile sig_atomic_t _sentry_backtrace_captured = 0;
72
73// Per-session module usage counts ("category/name" -> count). Mutated only from
74// the GUI thread; mirrored into the sentry scope on each change so the crash
75// handler never has to read this table (which would be unsafe in a signal context).
76static GHashTable *_module_usage = NULL;
77
78// Dedup state for the currently-processed image. Pipelines run on worker threads
79// (darkroom full/preview, export), so this is guarded by a mutex.
80static GMutex _processed_image_lock;
81static int32_t _processed_imgid = -1;
82static char _processed_pipeline[32] = { 0 };
83// Count of distinct image+pipeline runs this session, stamped on crash events so
84// the website can report "images processed before a crash".
85static volatile int _processed_image_count = 0;
86
87// Length of the running session, in seconds. darktable.start_wtime is stamped at
88// the very start of dt_init().
89static double _sentry_session_seconds(void)
90{
91 const double dur = dt_get_wtime() - darktable.start_wtime;
92 return (dur > 0.0) ? dur : 0.0;
93}
94
95// Stamp the event with the current session length, in seconds. For crashes this
96// runs inside the crashing process, so the value is the exact time-to-crash.
97static void _sentry_stamp_session_length(sentry_value_t event)
98{
99 const double dur = _sentry_session_seconds();
100
101 // Numeric values under "extra" for inspection.
102 sentry_value_t extra = sentry_value_get_by_key(event, "extra");
103 if(sentry_value_is_null(extra))
104 {
105 extra = sentry_value_new_object();
106 sentry_value_set_by_key(event, "extra", extra);
107 }
108 sentry_value_set_by_key(extra, "session_seconds", sentry_value_new_double(dur));
109 sentry_value_set_by_key(extra, "images_processed", sentry_value_new_int32(_processed_image_count));
110
111 // String tags so events are searchable/groupable by session length and by how
112 // many images had been processed when the crash happened.
113 char buf[32];
114 snprintf(buf, sizeof(buf), "%.0f", dur);
115 char ibuf[32];
116 snprintf(ibuf, sizeof(ibuf), "%d", _processed_image_count);
117 sentry_value_t tags = sentry_value_get_by_key(event, "tags");
118 if(sentry_value_is_null(tags))
119 {
120 tags = sentry_value_new_object();
121 sentry_value_set_by_key(event, "tags", tags);
122 }
123 sentry_value_set_by_key(tags, "session_seconds", sentry_value_new_string(buf));
124 sentry_value_set_by_key(tags, "images_processed", sentry_value_new_string(ibuf));
125}
126
127// before_send handles NON-crash events (on_crash takes over for crashes). Stamp
128// the session length so every event carries it.
129static sentry_value_t _sentry_before_send(sentry_value_t event, void *hint, void *user_data)
130{
131 _sentry_stamp_session_length(event);
132 return event;
133}
134
135#if defined(__linux__)
136// Run gdb against the crashing process and capture its backtrace into a freshly
137// allocated buffer (NUL-terminated; *len excludes the terminator). Returns NULL
138// on failure. This mirrors the local gdb fallback in system_signal_handling.c but
139// returns the text so it can be attached to the Sentry crash report.
140static char *_sentry_capture_gdb_backtrace(gsize *len)
141{
142 gchar *name = NULL;
143 const int fd = g_file_open_tmp("ansel_sentry_bt_XXXXXX.txt", &name, NULL);
144 if(fd == -1) return NULL;
145 close(fd);
146
147 gchar *pid_arg = g_strdup_printf("%d", (int)getpid());
148 gchar *exe_arg = g_strdup_printf("/proc/%s/exe", pid_arg);
149 gchar *log_file_arg = g_strdup_printf("set logging file %s", name);
150
151 char *contents = NULL;
152 const pid_t pid = fork();
153 if(pid == 0)
154 {
155 // child: gdb attaches to the parent and dumps all threads' backtraces
156 execlp("gdb", "gdb", exe_arg, pid_arg, "-batch",
157 "-ex", "set pagination off",
158 "-ex", "set confirm off",
159 "-ex", log_file_arg,
160 "-ex", "set logging overwrite on",
161 "-ex", "set logging redirect on",
162 "-ex", "set logging enabled on",
163 "-ex", "thread apply all bt full",
164 NULL);
165 _exit(127); // execlp only returns on failure
166 }
167 else if(pid > 0)
168 {
169 prctl(PR_SET_PTRACER, pid, 0, 0, 0); // let the child ptrace us (Yama)
170 waitpid(pid, NULL, 0);
171
172 gsize n = 0;
173 if(g_file_get_contents(name, &contents, &n, NULL))
174 {
175 if(len) *len = n;
176 }
177 else
178 {
179 contents = NULL;
180 }
181 }
182
183 g_unlink(name);
184 g_free(name);
185 g_free(pid_arg);
186 g_free(exe_arg);
187 g_free(log_file_arg);
188 return contents;
189}
190#endif // __linux__
191
192#if defined(_WIN32)
193// Resolve the short module name owning a given address (e.g. "libansel.dll").
194static void _sentry_module_name(DWORD64 addr, char *out, size_t out_len)
195{
196 g_strlcpy(out, "?", out_len);
197 HMODULE mod = NULL;
198 if(GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
199 | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
200 (LPCSTR)(uintptr_t)addr, &mod)
201 && mod)
202 {
203 char path[MAX_PATH];
204 if(GetModuleFileNameA(mod, path, sizeof(path)))
205 {
206 const char *base = strrchr(path, '\\');
207 g_strlcpy(out, base ? base + 1 : path, out_len);
208 }
209 }
210}
211
212// Capture the crashing thread's backtrace and symbolicate it locally with
213// DbgHelp. Mirrors the Linux gdb capture: returns a NUL-terminated buffer
214// (*len excludes the terminator) to attach to the crash report, or NULL on
215// failure. Local symbolication is the whole point on Windows: self-builds carry
216// matching .pdb files on the user's disk but never upload them to Sentry, so
217// without this their crashes arrive as bare addresses (see #129664611).
218static char *_sentry_capture_windows_backtrace(const sentry_ucontext_t *uctx, gsize *len)
219{
220 if(IS_NULL_PTR(uctx)) return NULL;
221
222 void *frames[128];
223 const size_t n = sentry_unwind_stack_from_ucontext(uctx, frames, 128);
224 if(n == 0) return NULL;
225
226 const HANDLE proc = GetCurrentProcess();
227 SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
228 // May fail with ERROR_INVALID_PARAMETER if sentry already initialized the
229 // symbol handler for this process; symbols stay usable either way.
230 SymInitialize(proc, NULL, TRUE);
231
232 GString *out = g_string_new(NULL);
233 g_string_append_printf(out, "this is %s reporting a crash (local DbgHelp backtrace, crashing thread):\n\n",
235
236 // SYMBOL_INFO with room for the (undecorated) symbol name right after it.
237 char symbuf[sizeof(SYMBOL_INFO) + 512];
238 SYMBOL_INFO *sym = (SYMBOL_INFO *)symbuf;
239 memset(symbuf, 0, sizeof(symbuf));
240 sym->SizeOfStruct = sizeof(SYMBOL_INFO);
241 sym->MaxNameLen = 512;
242
243 for(size_t i = 0; i < n; i++)
244 {
245 const DWORD64 addr = (DWORD64)(uintptr_t)frames[i];
246
247 char modname[MAX_PATH];
248 _sentry_module_name(addr, modname, sizeof(modname));
249
250 const unsigned long long fno = (unsigned long long)i;
251 DWORD64 disp = 0;
252 if(SymFromAddr(proc, addr, &disp, sym))
253 {
254 DWORD line_disp = 0;
255 IMAGEHLP_LINE64 line;
256 memset(&line, 0, sizeof(line));
257 line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
258 if(SymGetLineFromAddr64(proc, addr, &line_disp, &line))
259 g_string_append_printf(out, "#%-2llu 0x%016llx %s+0x%llx (%s:%lu) [%s]\n", fno,
260 (unsigned long long)addr, sym->Name, (unsigned long long)disp,
261 line.FileName, (unsigned long)line.LineNumber, modname);
262 else
263 g_string_append_printf(out, "#%-2llu 0x%016llx %s+0x%llx [%s]\n", fno, (unsigned long long)addr,
264 sym->Name, (unsigned long long)disp, modname);
265 }
266 else
267 {
268 g_string_append_printf(out, "#%-2llu 0x%016llx ? [%s]\n", fno, (unsigned long long)addr, modname);
269 }
270 }
271
272 if(len) *len = out->len;
273 return g_string_free(out, FALSE);
274}
275#endif // _WIN32
276
277// on_crash replaces before_send for crash events (inproc). It runs before the
278// crash event/attachments are serialized, so this is where we both stamp the
279// session length and attach a full gdb backtrace to the report.
280static sentry_value_t _sentry_on_crash(const sentry_ucontext_t *uctx, sentry_value_t event, void *user_data)
281{
282 _sentry_stamp_session_length(event);
283
284#if defined(__linux__)
285 gsize bt_len = 0;
286 char *bt = _sentry_capture_gdb_backtrace(&bt_len);
287 if(bt && bt_len > 0)
288 {
289 // Registered on the scope, this is picked up when the crash envelope is
290 // assembled (right after this hook returns). sentry copies the bytes.
291 sentry_attach_bytes(bt, bt_len, "gdb-backtrace.txt");
292
293 // Tell the local signal handler (which runs next in the chain) not to run
294 // gdb again for this same crash.
295 _sentry_backtrace_captured = 1;
296 }
297 g_free(bt);
298#elif defined(_WIN32)
299 gsize bt_len = 0;
300 char *bt = _sentry_capture_windows_backtrace(uctx, &bt_len);
301 if(bt && bt_len > 0)
302 {
303 sentry_attach_bytes(bt, bt_len, "windows-backtrace.txt");
304 // Tell the local exception filter (which runs next in the chain) not to
305 // pop its own backtrace dialog for this same crash.
306 _sentry_backtrace_captured = 1;
307 }
308 g_free(bt);
309#endif
310
311 return event;
312}
313
314gboolean dt_sentry_backtrace_captured(void)
315{
316 return _sentry_backtrace_captured != 0;
317}
318
319void dt_sentry_record_module_usage(const char *category, const char *name)
320{
321 if(!_sentry_inited || !category || !name || !*name) return;
322
323 if(!_module_usage)
324 _module_usage = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
325
326 // g_hash_table_insert frees the duplicate key when the entry already exists,
327 // so the table keeps a single owned copy of each key.
328 char *key = g_strdup_printf("%s/%s", category, name);
329 const int count = GPOINTER_TO_INT(g_hash_table_lookup(_module_usage, key)) + 1;
330 g_hash_table_insert(_module_usage, key, GINT_TO_POINTER(count));
331
332 // Push the whole map into the scope as the "module_usage" context. Cheap at
333 // human interaction rates, and keeps the crash path free of table iteration.
334 sentry_value_t obj = sentry_value_new_object();
335 GHashTableIter iter;
336 gpointer k, v;
337 g_hash_table_iter_init(&iter, _module_usage);
338 while(g_hash_table_iter_next(&iter, &k, &v))
339 sentry_value_set_by_key(obj, (const char *)k, sentry_value_new_int32(GPOINTER_TO_INT(v)));
340 sentry_set_context("module_usage", obj);
341}
342
343void dt_sentry_set_processed_image(const struct dt_image_t *img, const char *pipeline)
344{
345 if(!_sentry_inited || !img) return;
346 const char *pl = pipeline ? pipeline : "";
347
348 // Skip the (frequent) case where the same image keeps being reprocessed by the
349 // same pipeline; only push a new context when something actually changed.
350 g_mutex_lock(&_processed_image_lock);
351 if(img->id == _processed_imgid && !strcmp(pl, _processed_pipeline))
352 {
353 g_mutex_unlock(&_processed_image_lock);
354 return;
355 }
356 _processed_imgid = img->id;
357 g_strlcpy(_processed_pipeline, pl, sizeof(_processed_pipeline));
358 _processed_image_count++;
359 g_mutex_unlock(&_processed_image_lock);
360
361 // Extension and type flags only - never the file name or path.
362 const char *dot = strrchr(img->filename, '.');
363
364 sentry_value_t o = sentry_value_new_object();
365 sentry_value_set_by_key(o, "extension", sentry_value_new_string(dot ? dot + 1 : ""));
366 sentry_value_set_by_key(o, "pipeline", sentry_value_new_string(pl));
367 sentry_value_set_by_key(o, "raw", sentry_value_new_bool(dt_image_is_raw(img)));
368 sentry_value_set_by_key(o, "ldr", sentry_value_new_bool(dt_image_is_ldr(img)));
369 sentry_value_set_by_key(o, "hdr", sentry_value_new_bool(dt_image_is_hdr(img)));
370 sentry_value_set_by_key(o, "monochrome", sentry_value_new_bool(dt_image_is_monochrome(img)));
371 // dsc.filters != 0 means the buffer still carries a CFA mosaic, i.e. it has not
372 // been demosaiced yet.
373 sentry_value_set_by_key(o, "needs_demosaic", sentry_value_new_bool(img->dsc.filters != 0));
374 sentry_value_set_by_key(o, "width", sentry_value_new_int32(img->width));
375 sentry_value_set_by_key(o, "height", sentry_value_new_int32(img->height));
376 sentry_set_context("processed_image", o);
377}
378
379// Attach OS / hardware context so reports are actionable. No images, files or
380// personal data: only the runtime environment.
381static void _sentry_set_context(void)
382{
383 // Hardware / device context
384 sentry_value_t device = sentry_value_new_object();
385 sentry_value_set_by_key(device, "cpu_logical_cores", sentry_value_new_int32(g_get_num_processors()));
386 sentry_value_set_by_key(device, "openmp_threads", sentry_value_new_int32(darktable.num_openmp_threads));
387
389 {
390 const double mem_gb = (double)darktable.dtresources.total_memory / (1024.0 * 1024.0 * 1024.0);
391 sentry_value_set_by_key(device, "memory_gb", sentry_value_new_double(mem_gb));
392 }
393
394 const gboolean cl_enabled = dt_opencl_is_enabled();
395 sentry_value_set_by_key(device, "opencl_enabled", sentry_value_new_bool(cl_enabled));
396
397#ifdef HAVE_OPENCL
398 // Device enumeration fields (num_devs/dev) only exist in HAVE_OPENCL builds.
400 {
401 sentry_value_t gpus = sentry_value_new_list();
402 for(int i = 0; i < darktable.opencl->num_devs; i++)
403 {
404 const char *name = darktable.opencl->dev[i].name;
405 if(name) sentry_value_append(gpus, sentry_value_new_string(name));
406 }
407 sentry_value_set_by_key(device, "opencl_devices", gpus);
408
409 // Tag with the first device so events are filterable by GPU.
410 if(darktable.opencl->dev[0].name) sentry_set_tag("opencl_device", darktable.opencl->dev[0].name);
411 }
412#endif
413 sentry_set_context("device", device);
414
415#if !defined(_WIN32) && !defined(__APPLE__)
416 // Linux/BSD: display server (X11 vs Wayland) and desktop environment, as
417 // searchable tags. Useful since many GUI bugs are backend/DE specific.
418 const char *session_type = g_getenv("XDG_SESSION_TYPE");
419 if(!session_type || !*session_type)
420 {
421 // Fall back to the well-known display sockets if the session type is unset.
422 if(g_getenv("WAYLAND_DISPLAY"))
423 session_type = "wayland";
424 else if(g_getenv("DISPLAY"))
425 session_type = "x11";
426 }
427 if(session_type && *session_type) sentry_set_tag("display_server", session_type);
428
429 const char *desktop = g_getenv("XDG_CURRENT_DESKTOP");
430 if(!desktop || !*desktop) desktop = g_getenv("DESKTOP_SESSION");
431 if(desktop && *desktop) sentry_set_tag("desktop_environment", desktop);
432
433 // What GTK actually renders on (may differ from the session, e.g. an X11 app
434 // under XWayland). The GObject type name ("GdkWaylandDisplay" / "GdkX11Display")
435 // gives this without pulling in the gdkwayland/gdkx backend headers.
436 GdkDisplay *display = gdk_display_get_default();
437 if(display) sentry_set_tag("gdk_backend", G_OBJECT_TYPE_NAME(display));
438#endif
439
440 // Display scaling and main window geometry (GUI sessions only). DPI/PPD come
441 // from the GUI, already computed during dt_gui_gtk_init(). The window size is
442 // read from conf, which holds the restored/last geometry and is kept up to date
443 // live on every resize - more reliable than the not-yet-mapped window here.
444 if(darktable.gui)
445 {
446 sentry_value_t scr = sentry_value_new_object();
447 sentry_value_set_by_key(scr, "dpi", sentry_value_new_double(darktable.gui->dpi));
448 sentry_value_set_by_key(scr, "dpi_factor", sentry_value_new_double(darktable.gui->dpi_factor));
449 sentry_value_set_by_key(scr, "ppd", sentry_value_new_double(darktable.gui->ppd));
450
451 const int win_w = dt_conf_get_int("ui_last/window_width");
452 const int win_h = dt_conf_get_int("ui_last/window_height");
453 if(win_w > 0 && win_h > 0)
454 {
455 sentry_value_set_by_key(scr, "window_width", sentry_value_new_int32(win_w));
456 sentry_value_set_by_key(scr, "window_height", sentry_value_new_int32(win_h));
457
458 // Searchable tag so issues can be filtered/grouped by window size.
459 char wbuf[32];
460 snprintf(wbuf, sizeof(wbuf), "%dx%d", win_w, win_h);
461 sentry_set_tag("window_size", wbuf);
462 }
463
464 // Monitor resolution (logical pixels) of the primary monitor.
465 GdkDisplay *gdkdisp = gdk_display_get_default();
466 GdkMonitor *mon = gdkdisp ? gdk_display_get_primary_monitor(gdkdisp) : NULL;
467 if(!mon && gdkdisp && gdk_display_get_n_monitors(gdkdisp) > 0)
468 mon = gdk_display_get_monitor(gdkdisp, 0);
469 if(mon)
470 {
471 GdkRectangle geo;
472 gdk_monitor_get_geometry(mon, &geo);
473 sentry_value_set_by_key(scr, "screen_width", sentry_value_new_int32(geo.width));
474 sentry_value_set_by_key(scr, "screen_height", sentry_value_new_int32(geo.height));
475 sentry_value_set_by_key(scr, "monitor_scale_factor",
476 sentry_value_new_int32(gdk_monitor_get_scale_factor(mon)));
477
478 char sbuf[32];
479 snprintf(sbuf, sizeof(sbuf), "%dx%d", geo.width, geo.height);
480 sentry_set_tag("screen_size", sbuf);
481 }
482 sentry_set_context("display", scr);
483 }
484
485 // Build info as searchable tags so crash events can be filtered/grouped immediately.
486 // build_type is the CMake build type (Debug/Release/RelWithDebInfo); in Debug asserts
487 // are active, in Release/RelWithDebInfo NDEBUG is defined and assert() is compiled out.
488 sentry_set_tag("build_type", DT_BUILD_TYPE);
489#ifdef NDEBUG
490 sentry_set_tag("asserts", "disabled");
491#else
492 sentry_set_tag("asserts", "enabled");
493#endif
494 // Full C compiler flags baked in at configure time (includes -DNDEBUG, -O3, -g, etc.)
495 // so it is unambiguous which compilation mode produced the crashing binary.
496 sentry_set_extra("build_cflags", sentry_value_new_string(DT_BUILD_C_FLAGS));
497 sentry_set_tag("opencl", cl_enabled ? "yes" : "no");
498
499 // Distribution channel as a searchable tag (also set as the Sentry environment in
500 // dt_sentry_init), so official nightly builds can be told apart from self-builds.
501 sentry_set_tag("build_channel", DT_BUILD_CHANNEL);
502
503 // Stable per-run id, shared with usage analytics (PostHog) so the same session
504 // can be correlated across both systems without double-counting.
505 sentry_set_tag("session_id", dt_session_id());
506
507 // Use the same anonymous per-installation id as PostHog's distinct_id, so the
508 // "users" counted by Sentry de-duplicate against usage analytics.
509 sentry_value_t user = sentry_value_new_object();
510 sentry_value_set_by_key(user, "id", sentry_value_new_string(dt_install_id()));
511 sentry_set_user(user);
512
513 // Human-readable version string (the release itself is the commit SHA). May be a
514 // full "0.0.0+3848~ghash" or, on a shallow clone, just the abbreviated hash.
515 sentry_set_tag("version", darktable_package_version);
516
517 // Surface how many crash-free sessions preceded this one (local mirror of the
518 // server-side release-health metric, useful directly on the event).
519 sentry_set_extra("clean_sessions_local",
520 sentry_value_new_int32(dt_conf_get_int(DT_SENTRY_CLEAN_SESSIONS_KEY)));
521
522 // Length of the previous clean session and cumulative usage time, so events
523 // (e.g. crashes) carry the user's recent/total session history. The current
524 // session's own length is stamped per-event by _sentry_before_send().
525 sentry_set_extra("previous_session_seconds",
526 sentry_value_new_int32(dt_conf_get_int(DT_SENTRY_LAST_SESSION_KEY)));
527 sentry_set_extra("total_session_seconds",
528 sentry_value_new_int64(dt_conf_get_int64(DT_SENTRY_TOTAL_SESSION_KEY)));
529}
530
531void dt_sentry_init(const gboolean have_gui)
532{
533 // Consent is gathered once at startup by dt_privacy_ask_consent() (a single
534 // dialog shared with usage analytics). Here we only honor the resulting toggle.
535 if(!dt_conf_get_bool(DT_SENTRY_ENABLED_KEY))
536 return;
537
538 if(SENTRY_DSN[0] == '\0')
539 {
540 dt_print(DT_DEBUG_CONTROL, "[sentry] no DSN configured, crash reporting disabled\n");
541 return;
542 }
543
544 sentry_options_t *options = sentry_options_new();
545 sentry_options_set_dsn(options, SENTRY_DSN);
546
547 // Keep the crash database next to our other runtime caches.
548 char cachedir[PATH_MAX] = { 0 };
549 dt_loc_get_user_cache_dir(cachedir, sizeof(cachedir));
550 char *db_path = g_build_filename(cachedir, "sentry-native", NULL);
551 sentry_options_set_database_path(options, db_path);
552 g_free(db_path);
553
554 // Release is keyed on the full commit SHA: it is the only build identifier that
555 // is identical across shallow and full clones, so events from official builds and
556 // self-builds of the same commit group into one release (the version string's
557 // abbreviated hash / commit count is not consistent across clone types). The
558 // human-readable version is attached as a tag in _sentry_set_context().
559 char *release = g_strdup_printf("ansel@%s", darktable_commit_hash);
560 sentry_options_set_release(options, release);
561 g_free(release);
562
563 // Environment separates official nightly builds from local/self-builds in the
564 // dashboard and release-health metrics. The compiler/optimization build type is
565 // kept separately as the "build_type" extra.
566 sentry_options_set_environment(options, DT_BUILD_CHANNEL);
567 sentry_options_set_debug(options, (darktable.unmuted & DT_DEBUG_CONTROL) ? 1 : 0);
568
569 // Stamp non-crash events with the session length...
570 sentry_options_set_before_send(options, _sentry_before_send, NULL);
571 // ...and for crashes, stamp the session length and attach a full gdb backtrace
572 // (on_crash replaces before_send for crash events).
573 sentry_options_set_on_crash(options, _sentry_on_crash, NULL);
574
575 // Release health: starts a session now, ended healthy on dt_sentry_shutdown()
576 // or marked crashed by the in-process handler. This is what produces the
577 // "sessions that ended with no error" / crash-free rate metric.
578 sentry_options_set_auto_session_tracking(options, 1);
579
580 if(sentry_init(options) == 0)
581 {
582 _sentry_inited = TRUE;
583 _sentry_set_context();
584 dt_print(DT_DEBUG_CONTROL, "[sentry] crash reporting initialized\n");
585 }
586 else
587 {
588 dt_print(DT_DEBUG_ALWAYS, "[sentry] initialization failed\n");
589 }
590}
591
592void dt_sentry_shutdown(void)
593{
594 if(!_sentry_inited) return;
595
596 // This session ended without a crash: bump the local counter before closing
597 // so the next run (and any future crash) sees the updated count.
598 dt_conf_set_int(DT_SENTRY_CLEAN_SESSIONS_KEY, dt_conf_get_int(DT_SENTRY_CLEAN_SESSIONS_KEY) + 1);
599
600 // Record this healthy session's length (sentry's release health already tracks
601 // the per-session duration server-side; this keeps a local record and feeds the
602 // "previous/total session seconds" context attached on the next run).
603 const int dur = (int)_sentry_session_seconds();
604 dt_conf_set_int(DT_SENTRY_LAST_SESSION_KEY, dur);
605 dt_conf_set_int64(DT_SENTRY_TOTAL_SESSION_KEY,
606 dt_conf_get_int64(DT_SENTRY_TOTAL_SESSION_KEY) + dur);
607
608 sentry_close();
609 _sentry_inited = FALSE;
610
611 if(_module_usage)
612 {
613 g_hash_table_destroy(_module_usage);
614 _module_usage = NULL;
615 }
616}
617
618#else // !HAVE_SENTRY
619
620void dt_sentry_init(const gboolean have_gui)
621{
622}
623
625{
626}
627
629{
630 return FALSE;
631}
632
633void dt_sentry_record_module_usage(const char *category, const char *name)
634{
635}
636
637void dt_sentry_set_processed_image(const struct dt_image_t *img, const char *pipeline)
638{
639}
640
641#endif // HAVE_SENTRY
642
643// clang-format off
644// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
645// vim: shiftwidth=2 expandtab tabstop=2 cindent
646// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
647// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
const dt_colormatrix_t dt_aligned_pixel_t out
gboolean dt_image_is_raw(const dt_image_t *img)
gboolean dt_image_is_hdr(const dt_image_t *img)
gboolean dt_image_is_monochrome(const dt_image_t *img)
gboolean dt_image_is_ldr(const dt_image_t *img)
char * key
char * name
const char darktable_commit_hash[]
const char darktable_package_string[]
const char darktable_package_version[]
#define DT_BUILD_CHANNEL
#define DT_BUILD_TYPE
#define DT_BUILD_C_FLAGS
int dt_conf_get_bool(const char *name)
void dt_conf_set_int(const char *name, int val)
void dt_conf_set_int64(const char *name, int64_t val)
int dt_conf_get_int(const char *name)
int64_t dt_conf_get_int64(const char *name)
const char * dt_install_id(void)
Definition darktable.c:328
darktable_t darktable
Definition darktable.c:183
const char * dt_session_id(void)
Definition darktable.c:314
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_CONTROL
Definition darktable.h:738
@ DT_DEBUG_ALWAYS
Definition darktable.h:735
static double dt_get_wtime(void)
Definition darktable.h:976
#define PATH_MAX
Definition darktable.h:1189
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
void dt_loc_get_user_cache_dir(char *cachedir, size_t bufsize)
const float v
float *const restrict const size_t k
int dt_opencl_is_enabled(void)
Definition opencl.c:2835
gboolean dt_sentry_backtrace_captured(void)
Definition sentry.c:628
void dt_sentry_record_module_usage(const char *category, const char *name)
Definition sentry.c:633
void dt_sentry_shutdown(void)
Definition sentry.c:624
void dt_sentry_init(const gboolean have_gui)
Definition sentry.c:620
void dt_sentry_set_processed_image(const struct dt_image_t *img, const char *pipeline)
Definition sentry.c:637
static const char *const mon[12]
Definition strptime.c:99
int32_t num_openmp_threads
Definition darktable.h:786
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_sys_resources_t dtresources
Definition darktable.h:862
struct dt_opencl_t * opencl
Definition darktable.h:813
int32_t unmuted
Definition darktable.h:788
double start_wtime
Definition darktable.h:856
double dpi
Definition gtk.h:205
double ppd
Definition gtk.h:205
double dpi_factor
Definition gtk.h:205
int32_t height
Definition image.h:315
int32_t width
Definition image.h:315
dt_iop_buffer_dsc_t dsc
Definition image.h:337
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:304
int32_t id
Definition image.h:319
uint32_t filters
Definition format.h:60
const char * name
Definition opencl.h:162
int num_devs
Definition opencl.h:263
dt_opencl_device_t * dev
Definition opencl.h:273
int inited
Definition opencl.h:259
typedef double((*spd)(unsigned long int wavelength, double TempK))