38#include <sys/sysctl.h>
41#define POSTHOG_API_KEY "phc_uLtshRLGnot4cMieYFebh4gxkszztKLcfHgEYSZF3Cu6"
44#define POSTHOG_HOST "https://eu.i.posthog.com"
49#define DT_TELEMETRY_ENABLED_KEY "telemetry/enabled"
50#define DT_TELEMETRY_ASKED_KEY "telemetry/consent_asked"
51#define DT_TELEMETRY_INSTALL_ID_KEY "telemetry/install_id"
53static gboolean _running =
FALSE;
55static GAsyncQueue *
_queue = NULL;
56static char *_distinct_id = NULL;
57static char _stop_sentinel;
62static GMutex _stats_lock;
63static GHashTable *_module_usage = NULL;
64static GHashTable *_file_types = NULL;
65static int _raw_images = 0;
66static int _nonraw_images = 0;
67static int _mosaiced_images = 0;
68static int _processed_images = 0;
70static int32_t _last_imgid = -1;
71static char _last_pipeline[32] = { 0 };
74static size_t _discard_cb(
char *ptr,
size_t size,
size_t nmemb,
void *userdata)
80static gpointer _telemetry_worker(gpointer data)
82 CURL *curl = curl_easy_init();
83 struct curl_slist *
headers = curl_slist_append(NULL,
"Content-Type: application/json");
85 snprintf(url,
sizeof(url),
"%s/capture/", POSTHOG_HOST);
89 char *body = (
char *)g_async_queue_pop(
_queue);
90 if(body == &_stop_sentinel)
break;
94 curl_easy_setopt(curl, CURLOPT_URL, url);
95 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
96 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
97 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (
long)strlen(body));
98 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
99 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _discard_cb);
100#if defined(_WIN32) && defined(CURLSSLOPT_NATIVE_CA)
105 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (
long)CURLSSLOPT_NATIVE_CA);
107 const CURLcode
res = curl_easy_perform(curl);
114 if(headers) curl_slist_free_all(headers);
115 if(curl) curl_easy_cleanup(curl);
121 if(!_running || !event)
123 if(properties) json_object_unref(properties);
127 JsonObject *root = json_object_new();
128 json_object_set_string_member(root,
"api_key", POSTHOG_API_KEY);
129 json_object_set_string_member(root,
"event", event);
130 json_object_set_string_member(root,
"distinct_id", _distinct_id ? _distinct_id :
"unknown");
132 GDateTime *now = g_date_time_new_now_utc();
133 gchar *ts = g_date_time_format_iso8601(now);
134 if(ts) json_object_set_string_member(root,
"timestamp", ts);
136 g_date_time_unref(now);
139 json_object_set_object_member(root,
"properties", properties ? properties : json_object_new());
141 JsonNode *node = json_node_new(JSON_NODE_OBJECT);
142 json_node_take_object(node, root);
143 JsonGenerator *gen = json_generator_new();
144 json_generator_set_root(gen, node);
145 gchar *body = json_generator_to_data(gen, NULL);
147 json_node_free(node);
149 if(body) g_async_queue_push(
_queue, body);
154 if(!_running || !category || !
name || !*
name)
return;
156 char *
key = g_strdup_printf(
"%s/%s", category,
name);
158 g_mutex_lock(&_stats_lock);
160 _module_usage = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
161 const int count = GPOINTER_TO_INT(g_hash_table_lookup(_module_usage,
key)) + 1;
163 g_hash_table_insert(_module_usage,
key, GINT_TO_POINTER(count));
164 const gboolean first_use = (count == 1);
165 g_mutex_unlock(&_stats_lock);
174 JsonObject *props = json_object_new();
175 json_object_set_string_member(props,
"category", category);
176 json_object_set_string_member(props,
"name",
name);
183 if(!_running || !img)
return;
184 const char *pl = pipeline ? pipeline :
"";
186 g_mutex_lock(&_stats_lock);
188 if(img->
id == _last_imgid && !strcmp(pl, _last_pipeline))
190 g_mutex_unlock(&_stats_lock);
193 _last_imgid = img->
id;
194 g_strlcpy(_last_pipeline, pl,
sizeof(_last_pipeline));
197 const char *dot = strrchr(img->
filename,
'.');
198 char *ext = g_ascii_strdown(dot ? dot + 1 :
"none", -1);
201 _file_types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
202 const int count = GPOINTER_TO_INT(g_hash_table_lookup(_file_types, ext)) + 1;
204 g_hash_table_insert(_file_types, ext, GINT_TO_POINTER(count));
205 const gboolean first_ext = (count == 1);
211 const gboolean needs_demosaic = (img->
dsc.
filters != 0);
213 if(is_raw) _raw_images++;
else _nonraw_images++;
214 if(needs_demosaic) _mosaiced_images++;
216 g_mutex_unlock(&_stats_lock);
223 gchar *ext_lc = g_ascii_strdown(dot ? dot + 1 :
"none", -1);
224 JsonObject *props = json_object_new();
225 json_object_set_string_member(props,
"extension", ext_lc);
227 json_object_set_boolean_member(props,
"raw", is_raw);
228 json_object_set_boolean_member(props,
"ldr", is_ldr);
229 json_object_set_boolean_member(props,
"hdr", is_hdr);
230 json_object_set_boolean_member(props,
"monochrome", is_mono);
231 json_object_set_boolean_member(props,
"needs_demosaic", needs_demosaic);
232 json_object_set_string_member(props,
"pipeline", pl);
242static void _flatten_counts(JsonObject *
p,
const char *prefix, GHashTable *table)
248 g_hash_table_iter_init(&it, table);
249 while(g_hash_table_iter_next(&it, &
k, &
v))
254 gchar *safe = g_strdup_printf(
"%s%s", prefix, (
const char *)
k);
255 for(
char *c = safe; *
c;
c++)
256 if(!g_ascii_isalnum(*c) && *c !=
'_') *
c =
'_';
257 json_object_set_int_member(
p, safe, GPOINTER_TO_INT(
v));
265static JsonObject *_telemetry_session_end_properties(
void)
267 JsonObject *
p = json_object_new();
270 json_object_set_double_member(
p,
"session_seconds", (dur > 0.0) ? dur : 0.0);
281 g_mutex_lock(&_stats_lock);
284 _flatten_counts(
p,
"mod_", _module_usage);
285 _flatten_counts(
p,
"ext_", _file_types);
286 json_object_set_int_member(
p,
"images_processed", _processed_images);
287 json_object_set_int_member(
p,
"raw_images", _raw_images);
288 json_object_set_int_member(
p,
"nonraw_images", _nonraw_images);
289 json_object_set_int_member(
p,
"mosaiced_images", _mosaiced_images);
290 g_mutex_unlock(&_stats_lock);
296static JsonObject *_telemetry_system_properties(
void)
298 JsonObject *
p = json_object_new();
305 json_object_set_boolean_member(
p,
"$geoip_disable",
TRUE);
306 json_object_set_string_member(
p,
"$ip",
"0.0.0.0");
320 gchar *os = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
326 char ver[256] = { 0 };
327 size_t len =
sizeof(ver);
328 if(sysctlbyname(
"kern.osproductversion", ver, &len, NULL, 0) == 0 && ver[0])
329 os = g_strdup_printf(
"macOS %s", ver);
331 os = g_strdup(
"macOS");
336 json_object_set_string_member(
p,
"os", os);
340 json_object_set_int_member(
p,
"cpu_cores", g_get_num_processors());
342 json_object_set_double_member(
p,
"ram_gb",
346 json_object_set_boolean_member(
p,
"opencl", cl);
353#if !defined(_WIN32) && !defined(__APPLE__)
354 const char *session_type = g_getenv(
"XDG_SESSION_TYPE");
355 if(session_type && *session_type) json_object_set_string_member(
p,
"display_server", session_type);
356 const char *desktop = g_getenv(
"XDG_CURRENT_DESKTOP");
357 if(desktop && *desktop) json_object_set_string_member(
p,
"desktop_environment", desktop);
364 GdkDisplay *display = gdk_display_get_default();
365 GdkMonitor *
mon = display ? gdk_display_get_primary_monitor(display) : NULL;
366 if(!
mon && display && gdk_display_get_n_monitors(display) > 0)
mon = gdk_display_get_monitor(display, 0);
370 gdk_monitor_get_geometry(
mon, &geo);
371 json_object_set_int_member(
p,
"screen_width", geo.width);
372 json_object_set_int_member(
p,
"screen_height", geo.height);
385 if(POSTHOG_API_KEY[0] ==
'\0')
395 _queue = g_async_queue_new();
396 _worker = g_thread_new(
"telemetry", _telemetry_worker, NULL);
408 if(!_running)
return;
417 g_async_queue_push(
_queue, &_stop_sentinel);
425 g_async_queue_unref(
_queue);
428 g_free(_distinct_id);
431 g_mutex_lock(&_stats_lock);
434 g_hash_table_destroy(_module_usage);
435 _module_usage = NULL;
439 g_hash_table_destroy(_file_types);
442 g_mutex_unlock(&_stats_lock);
457 if(properties) json_object_unref(properties);
const char * dt_install_id(void)
const char * dt_session_id(void)
int dt_conf_get_bool(const char *name)
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)
const char darktable_commit_hash[]
const char darktable_package_version[]
size_t dt_get_total_mem(void)
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
const char * dt_opencl_get_device_name(const int devid)
Human-readable device name, owned by the OpenCL module.
int dt_opencl_is_enabled(void)
int dt_opencl_get_num_devices(void)
Number of usable OpenCL devices; 0 when OpenCL is unavailable.
static dt_pixelpipe_cache_wait_queue_t _queue
double dt_screen_dpi(void)
gboolean dt_screen_metrics_probed(void)
double dt_screen_ppd(void)
static const char *const mon[12]
char filename[DT_MAX_FILENAME_LEN]
void dt_telemetry_record_module_usage(const char *category, const char *name)
void dt_telemetry_shutdown(void)
void dt_telemetry_capture(const char *event, JsonObject *properties)
void dt_telemetry_init(const gboolean have_gui)
void dt_telemetry_record_file_type(const struct dt_image_t *img, const char *pipeline)
double dt_get_start_wtime(void)
static double dt_get_wtime(void)