37#include <sys/sysctl.h>
40#define POSTHOG_API_KEY "phc_uLtshRLGnot4cMieYFebh4gxkszztKLcfHgEYSZF3Cu6"
43#define POSTHOG_HOST "https://eu.i.posthog.com"
48#define DT_TELEMETRY_ENABLED_KEY "telemetry/enabled"
49#define DT_TELEMETRY_ASKED_KEY "telemetry/consent_asked"
50#define DT_TELEMETRY_INSTALL_ID_KEY "telemetry/install_id"
52static gboolean _running =
FALSE;
53static GThread *_worker = NULL;
54static GAsyncQueue *_queue = NULL;
55static char *_distinct_id = NULL;
56static char _stop_sentinel;
61static GMutex _stats_lock;
62static GHashTable *_module_usage = NULL;
63static GHashTable *_file_types = NULL;
64static int _raw_images = 0;
65static int _nonraw_images = 0;
66static int _mosaiced_images = 0;
67static int _processed_images = 0;
69static int32_t _last_imgid = -1;
70static char _last_pipeline[32] = { 0 };
73static size_t _discard_cb(
char *ptr,
size_t size,
size_t nmemb,
void *userdata)
79static gpointer _telemetry_worker(gpointer data)
81 CURL *curl = curl_easy_init();
82 struct curl_slist *headers = curl_slist_append(NULL,
"Content-Type: application/json");
84 snprintf(url,
sizeof(url),
"%s/capture/", POSTHOG_HOST);
88 char *body = (
char *)g_async_queue_pop(_queue);
89 if(body == &_stop_sentinel)
break;
93 curl_easy_setopt(curl, CURLOPT_URL, url);
94 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
95 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
96 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (
long)strlen(body));
97 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
98 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _discard_cb);
99#if defined(_WIN32) && defined(CURLSSLOPT_NATIVE_CA)
104 curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (
long)CURLSSLOPT_NATIVE_CA);
106 const CURLcode res = curl_easy_perform(curl);
113 if(headers) curl_slist_free_all(headers);
114 if(curl) curl_easy_cleanup(curl);
120 if(!_running || !event)
122 if(properties) json_object_unref(properties);
126 JsonObject *root = json_object_new();
127 json_object_set_string_member(root,
"api_key", POSTHOG_API_KEY);
128 json_object_set_string_member(root,
"event", event);
129 json_object_set_string_member(root,
"distinct_id", _distinct_id ? _distinct_id :
"unknown");
131 GDateTime *now = g_date_time_new_now_utc();
132 gchar *ts = g_date_time_format_iso8601(now);
133 if(ts) json_object_set_string_member(root,
"timestamp", ts);
135 g_date_time_unref(now);
138 json_object_set_object_member(root,
"properties", properties ? properties : json_object_new());
140 JsonNode *node = json_node_new(JSON_NODE_OBJECT);
141 json_node_take_object(node, root);
142 JsonGenerator *gen = json_generator_new();
143 json_generator_set_root(gen, node);
144 gchar *body = json_generator_to_data(gen, NULL);
146 json_node_free(node);
148 if(body) g_async_queue_push(_queue, body);
153 if(!_running || !category || !
name || !*
name)
return;
155 char *
key = g_strdup_printf(
"%s/%s", category,
name);
157 g_mutex_lock(&_stats_lock);
159 _module_usage = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
160 const int count = GPOINTER_TO_INT(g_hash_table_lookup(_module_usage,
key)) + 1;
162 g_hash_table_insert(_module_usage,
key, GINT_TO_POINTER(count));
163 const gboolean first_use = (count == 1);
164 g_mutex_unlock(&_stats_lock);
173 JsonObject *props = json_object_new();
174 json_object_set_string_member(props,
"category", category);
175 json_object_set_string_member(props,
"name",
name);
182 if(!_running || !img)
return;
183 const char *pl = pipeline ? pipeline :
"";
185 g_mutex_lock(&_stats_lock);
187 if(img->
id == _last_imgid && !strcmp(pl, _last_pipeline))
189 g_mutex_unlock(&_stats_lock);
192 _last_imgid = img->
id;
193 g_strlcpy(_last_pipeline, pl,
sizeof(_last_pipeline));
196 const char *dot = strrchr(img->
filename,
'.');
197 char *ext = g_ascii_strdown(dot ? dot + 1 :
"none", -1);
200 _file_types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
201 const int count = GPOINTER_TO_INT(g_hash_table_lookup(_file_types, ext)) + 1;
203 g_hash_table_insert(_file_types, ext, GINT_TO_POINTER(count));
204 const gboolean first_ext = (count == 1);
210 const gboolean needs_demosaic = (img->
dsc.
filters != 0);
212 if(is_raw) _raw_images++;
else _nonraw_images++;
213 if(needs_demosaic) _mosaiced_images++;
215 g_mutex_unlock(&_stats_lock);
222 gchar *ext_lc = g_ascii_strdown(dot ? dot + 1 :
"none", -1);
223 JsonObject *props = json_object_new();
224 json_object_set_string_member(props,
"extension", ext_lc);
226 json_object_set_boolean_member(props,
"raw", is_raw);
227 json_object_set_boolean_member(props,
"ldr", is_ldr);
228 json_object_set_boolean_member(props,
"hdr", is_hdr);
229 json_object_set_boolean_member(props,
"monochrome", is_mono);
230 json_object_set_boolean_member(props,
"needs_demosaic", needs_demosaic);
231 json_object_set_string_member(props,
"pipeline", pl);
241static void _flatten_counts(JsonObject *
p,
const char *prefix, GHashTable *table)
247 g_hash_table_iter_init(&it, table);
248 while(g_hash_table_iter_next(&it, &
k, &
v))
253 gchar *safe = g_strdup_printf(
"%s%s", prefix, (
const char *)
k);
254 for(
char *c = safe; *
c;
c++)
255 if(!g_ascii_isalnum(*c) && *c !=
'_') *
c =
'_';
256 json_object_set_int_member(
p, safe, GPOINTER_TO_INT(
v));
264static JsonObject *_telemetry_session_end_properties(
void)
266 JsonObject *
p = json_object_new();
269 json_object_set_double_member(
p,
"session_seconds", (dur > 0.0) ? dur : 0.0);
280 g_mutex_lock(&_stats_lock);
283 _flatten_counts(
p,
"mod_", _module_usage);
284 _flatten_counts(
p,
"ext_", _file_types);
285 json_object_set_int_member(
p,
"images_processed", _processed_images);
286 json_object_set_int_member(
p,
"raw_images", _raw_images);
287 json_object_set_int_member(
p,
"nonraw_images", _nonraw_images);
288 json_object_set_int_member(
p,
"mosaiced_images", _mosaiced_images);
289 g_mutex_unlock(&_stats_lock);
295static JsonObject *_telemetry_system_properties(
void)
297 JsonObject *
p = json_object_new();
304 json_object_set_boolean_member(
p,
"$geoip_disable",
TRUE);
305 json_object_set_string_member(
p,
"$ip",
"0.0.0.0");
319 gchar *os = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
325 char ver[256] = { 0 };
326 size_t len =
sizeof(ver);
327 if(sysctlbyname(
"kern.osproductversion", ver, &len, NULL, 0) == 0 && ver[0])
328 os = g_strdup_printf(
"macOS %s", ver);
330 os = g_strdup(
"macOS");
335 json_object_set_string_member(
p,
"os", os);
339 json_object_set_int_member(
p,
"cpu_cores", g_get_num_processors());
341 json_object_set_double_member(
p,
"ram_gb",
345 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);
420 g_thread_join(_worker);
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);
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
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[]
int dt_conf_get_bool(const char *name)
const char * dt_install_id(void)
const char * dt_session_id(void)
void dt_print(dt_debug_thread_t thread, const char *msg,...)
static double dt_get_wtime(void)
float *const restrict const size_t k
int dt_opencl_is_enabled(void)
static const char *const mon[12]
struct dt_gui_gtk_t * gui
struct dt_sys_resources_t dtresources
struct dt_opencl_t * opencl
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)