36#define POSTHOG_API_KEY "phc_uLtshRLGnot4cMieYFebh4gxkszztKLcfHgEYSZF3Cu6"
39#define POSTHOG_HOST "https://eu.i.posthog.com"
44#define DT_TELEMETRY_ENABLED_KEY "telemetry/enabled"
45#define DT_TELEMETRY_ASKED_KEY "telemetry/consent_asked"
46#define DT_TELEMETRY_INSTALL_ID_KEY "telemetry/install_id"
48static gboolean _running =
FALSE;
49static GThread *_worker = NULL;
50static GAsyncQueue *_queue = NULL;
51static char *_distinct_id = NULL;
52static char _stop_sentinel;
57static GMutex _stats_lock;
58static GHashTable *_module_usage = NULL;
59static GHashTable *_file_types = NULL;
60static int _raw_images = 0;
61static int _nonraw_images = 0;
62static int _mosaiced_images = 0;
63static int _processed_images = 0;
65static int32_t _last_imgid = -1;
66static char _last_pipeline[32] = { 0 };
69static size_t _discard_cb(
char *ptr,
size_t size,
size_t nmemb,
void *userdata)
75static gpointer _telemetry_worker(gpointer data)
77 CURL *curl = curl_easy_init();
78 struct curl_slist *headers = curl_slist_append(NULL,
"Content-Type: application/json");
80 snprintf(url,
sizeof(url),
"%s/capture/", POSTHOG_HOST);
84 char *body = (
char *)g_async_queue_pop(_queue);
85 if(body == &_stop_sentinel)
break;
89 curl_easy_setopt(curl, CURLOPT_URL, url);
90 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
91 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
92 curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (
long)strlen(body));
93 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
94 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _discard_cb);
95 curl_easy_perform(curl);
100 if(headers) curl_slist_free_all(headers);
101 if(curl) curl_easy_cleanup(curl);
107 if(!_running || !event)
109 if(properties) json_object_unref(properties);
113 JsonObject *root = json_object_new();
114 json_object_set_string_member(root,
"api_key", POSTHOG_API_KEY);
115 json_object_set_string_member(root,
"event", event);
116 json_object_set_string_member(root,
"distinct_id", _distinct_id ? _distinct_id :
"unknown");
118 GDateTime *now = g_date_time_new_now_utc();
119 gchar *ts = g_date_time_format_iso8601(now);
120 if(ts) json_object_set_string_member(root,
"timestamp", ts);
122 g_date_time_unref(now);
125 json_object_set_object_member(root,
"properties", properties ? properties : json_object_new());
127 JsonNode *node = json_node_new(JSON_NODE_OBJECT);
128 json_node_take_object(node, root);
129 JsonGenerator *gen = json_generator_new();
130 json_generator_set_root(gen, node);
131 gchar *body = json_generator_to_data(gen, NULL);
133 json_node_free(node);
135 if(body) g_async_queue_push(_queue, body);
140 if(!_running || !category || !
name || !*
name)
return;
142 char *
key = g_strdup_printf(
"%s/%s", category,
name);
144 g_mutex_lock(&_stats_lock);
146 _module_usage = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
147 const int count = GPOINTER_TO_INT(g_hash_table_lookup(_module_usage,
key)) + 1;
149 g_hash_table_insert(_module_usage,
key, GINT_TO_POINTER(count));
150 const gboolean first_use = (count == 1);
151 g_mutex_unlock(&_stats_lock);
160 JsonObject *props = json_object_new();
161 json_object_set_string_member(props,
"category", category);
162 json_object_set_string_member(props,
"name",
name);
169 if(!_running || !img)
return;
170 const char *pl = pipeline ? pipeline :
"";
172 g_mutex_lock(&_stats_lock);
174 if(img->
id == _last_imgid && !strcmp(pl, _last_pipeline))
176 g_mutex_unlock(&_stats_lock);
179 _last_imgid = img->
id;
180 g_strlcpy(_last_pipeline, pl,
sizeof(_last_pipeline));
183 const char *dot = strrchr(img->
filename,
'.');
184 char *ext = g_ascii_strdown(dot ? dot + 1 :
"none", -1);
187 _file_types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
188 const int count = GPOINTER_TO_INT(g_hash_table_lookup(_file_types, ext)) + 1;
190 g_hash_table_insert(_file_types, ext, GINT_TO_POINTER(count));
191 const gboolean first_ext = (count == 1);
197 const gboolean needs_demosaic = (img->
dsc.
filters != 0);
199 if(is_raw) _raw_images++;
else _nonraw_images++;
200 if(needs_demosaic) _mosaiced_images++;
202 g_mutex_unlock(&_stats_lock);
209 gchar *ext_lc = g_ascii_strdown(dot ? dot + 1 :
"none", -1);
210 JsonObject *props = json_object_new();
211 json_object_set_string_member(props,
"extension", ext_lc);
213 json_object_set_boolean_member(props,
"raw", is_raw);
214 json_object_set_boolean_member(props,
"ldr", is_ldr);
215 json_object_set_boolean_member(props,
"hdr", is_hdr);
216 json_object_set_boolean_member(props,
"monochrome", is_mono);
217 json_object_set_boolean_member(props,
"needs_demosaic", needs_demosaic);
218 json_object_set_string_member(props,
"pipeline", pl);
228static void _flatten_counts(JsonObject *
p,
const char *prefix, GHashTable *table)
234 g_hash_table_iter_init(&it, table);
235 while(g_hash_table_iter_next(&it, &
k, &
v))
240 gchar *safe = g_strdup_printf(
"%s%s", prefix, (
const char *)
k);
241 for(
char *c = safe; *
c;
c++)
242 if(!g_ascii_isalnum(*c) && *c !=
'_') *
c =
'_';
243 json_object_set_int_member(
p, safe, GPOINTER_TO_INT(
v));
251static JsonObject *_telemetry_session_end_properties(
void)
253 JsonObject *
p = json_object_new();
256 json_object_set_double_member(
p,
"session_seconds", (dur > 0.0) ? dur : 0.0);
258 g_mutex_lock(&_stats_lock);
261 _flatten_counts(
p,
"mod_", _module_usage);
262 _flatten_counts(
p,
"ext_", _file_types);
263 json_object_set_int_member(
p,
"images_processed", _processed_images);
264 json_object_set_int_member(
p,
"raw_images", _raw_images);
265 json_object_set_int_member(
p,
"nonraw_images", _nonraw_images);
266 json_object_set_int_member(
p,
"mosaiced_images", _mosaiced_images);
267 g_mutex_unlock(&_stats_lock);
273static JsonObject *_telemetry_system_properties(
void)
275 JsonObject *
p = json_object_new();
282 json_object_set_boolean_member(
p,
"$geoip_disable",
TRUE);
283 json_object_set_string_member(
p,
"$ip",
"0.0.0.0");
288 gchar *os = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
291 json_object_set_string_member(
p,
"os", os);
295 json_object_set_int_member(
p,
"cpu_cores", g_get_num_processors());
297 json_object_set_double_member(
p,
"ram_gb",
301 json_object_set_boolean_member(
p,
"opencl", cl);
309#if !defined(_WIN32) && !defined(__APPLE__)
310 const char *session_type = g_getenv(
"XDG_SESSION_TYPE");
311 if(session_type && *session_type) json_object_set_string_member(
p,
"display_server", session_type);
312 const char *desktop = g_getenv(
"XDG_CURRENT_DESKTOP");
313 if(desktop && *desktop) json_object_set_string_member(
p,
"desktop_environment", desktop);
320 GdkDisplay *display = gdk_display_get_default();
321 GdkMonitor *
mon = display ? gdk_display_get_primary_monitor(display) : NULL;
322 if(!
mon && display && gdk_display_get_n_monitors(display) > 0)
mon = gdk_display_get_monitor(display, 0);
326 gdk_monitor_get_geometry(
mon, &geo);
327 json_object_set_int_member(
p,
"screen_width", geo.width);
328 json_object_set_int_member(
p,
"screen_height", geo.height);
341 if(POSTHOG_API_KEY[0] ==
'\0')
352 id = g_uuid_string_random();
357 _queue = g_async_queue_new();
358 _worker = g_thread_new(
"telemetry", _telemetry_worker, NULL);
370 if(!_running)
return;
379 g_async_queue_push(_queue, &_stop_sentinel);
382 g_thread_join(_worker);
387 g_async_queue_unref(_queue);
390 g_free(_distinct_id);
393 g_mutex_lock(&_stats_lock);
396 g_hash_table_destroy(_module_usage);
397 _module_usage = NULL;
401 g_hash_table_destroy(_file_types);
404 g_mutex_unlock(&_stats_lock);
419 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_package_version[]
int dt_conf_get_bool(const char *name)
gchar * dt_conf_get_string(const char *name)
void dt_conf_set_string(const char *name, const char *val)
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)