1 | #include "config.h" |
2 | |
3 | #include "gskprofilerprivate.h" |
4 | |
5 | #define MAX_SAMPLES 32 |
6 | |
7 | typedef struct { |
8 | GQuark id; |
9 | char *description; |
10 | gint64 value; |
11 | gint64 n_samples; |
12 | gboolean can_reset : 1; |
13 | } NamedCounter; |
14 | |
15 | typedef struct { |
16 | GQuark id; |
17 | char *description; |
18 | gint64 value; |
19 | gint64 start_time; |
20 | gint64 min_value; |
21 | gint64 max_value; |
22 | gint64 avg_value; |
23 | gint64 n_samples; |
24 | gboolean in_flight : 1; |
25 | gboolean can_reset : 1; |
26 | gboolean invert : 1; |
27 | } NamedTimer; |
28 | |
29 | typedef struct { |
30 | GQuark id; |
31 | gint64 value; |
32 | } Sample; |
33 | |
34 | struct _GskProfiler |
35 | { |
36 | GObject parent_instance; |
37 | |
38 | GHashTable *counters; |
39 | GHashTable *timers; |
40 | |
41 | Sample timer_samples[MAX_SAMPLES]; |
42 | guint last_sample; |
43 | }; |
44 | |
45 | G_DEFINE_TYPE (GskProfiler, gsk_profiler, G_TYPE_OBJECT) |
46 | |
47 | static void |
48 | named_counter_free (gpointer data) |
49 | { |
50 | NamedCounter *counter = data; |
51 | |
52 | if (data == NULL) |
53 | return; |
54 | |
55 | g_free (mem: counter->description); |
56 | |
57 | g_slice_free (NamedCounter, counter); |
58 | } |
59 | |
60 | static void |
61 | named_timer_free (gpointer data) |
62 | { |
63 | NamedTimer *timer = data; |
64 | |
65 | if (data == NULL) |
66 | return; |
67 | |
68 | g_free (mem: timer->description); |
69 | |
70 | g_slice_free (NamedTimer, timer); |
71 | } |
72 | |
73 | static void |
74 | gsk_profiler_finalize (GObject *gobject) |
75 | { |
76 | GskProfiler *self = GSK_PROFILER (ptr: gobject); |
77 | |
78 | g_clear_pointer (&self->counters, g_hash_table_unref); |
79 | g_clear_pointer (&self->timers, g_hash_table_unref); |
80 | |
81 | G_OBJECT_CLASS (gsk_profiler_parent_class)->finalize (gobject); |
82 | } |
83 | |
84 | static void |
85 | gsk_profiler_class_init (GskProfilerClass *klass) |
86 | { |
87 | G_OBJECT_CLASS (klass)->finalize = gsk_profiler_finalize; |
88 | } |
89 | |
90 | static void |
91 | gsk_profiler_init (GskProfiler *self) |
92 | { |
93 | self->counters = g_hash_table_new_full (hash_func: g_direct_hash, key_equal_func: g_direct_equal, |
94 | NULL, |
95 | value_destroy_func: named_counter_free); |
96 | self->timers = g_hash_table_new_full (hash_func: g_direct_hash, key_equal_func: g_direct_equal, |
97 | NULL, |
98 | value_destroy_func: named_timer_free); |
99 | } |
100 | |
101 | GskProfiler * |
102 | gsk_profiler_new (void) |
103 | { |
104 | return g_object_new (GSK_TYPE_PROFILER, NULL); |
105 | } |
106 | |
107 | static NamedCounter * |
108 | named_counter_new (GQuark id, |
109 | const char *description, |
110 | gboolean can_reset) |
111 | { |
112 | NamedCounter *res = g_slice_new0 (NamedCounter); |
113 | |
114 | res->id = id; |
115 | res->description = g_strdup (str: description); |
116 | res->can_reset = can_reset; |
117 | |
118 | return res; |
119 | } |
120 | |
121 | static NamedCounter * |
122 | gsk_profiler_get_counter (GskProfiler *profiler, |
123 | GQuark id) |
124 | { |
125 | return g_hash_table_lookup (hash_table: profiler->counters, GINT_TO_POINTER (id)); |
126 | } |
127 | |
128 | GQuark |
129 | gsk_profiler_add_counter (GskProfiler *profiler, |
130 | const char *counter_name, |
131 | const char *description, |
132 | gboolean can_reset) |
133 | { |
134 | NamedCounter *counter; |
135 | GQuark id; |
136 | |
137 | g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); |
138 | |
139 | id = g_quark_from_string (string: counter_name); |
140 | counter = gsk_profiler_get_counter (profiler, id); |
141 | if (counter != NULL) |
142 | { |
143 | g_critical ("Cannot add a counter '%s' as one already exists." , counter_name); |
144 | return counter->id; |
145 | } |
146 | |
147 | counter = named_counter_new (id, description, can_reset); |
148 | g_hash_table_insert (hash_table: profiler->counters, GINT_TO_POINTER (id), value: counter); |
149 | |
150 | return counter->id; |
151 | } |
152 | |
153 | static NamedTimer * |
154 | named_timer_new (GQuark id, |
155 | const char *description, |
156 | gboolean invert, |
157 | gboolean can_reset) |
158 | { |
159 | NamedTimer *res = g_slice_new0 (NamedTimer); |
160 | |
161 | res->id = id; |
162 | res->description = g_strdup (str: description); |
163 | res->invert = invert; |
164 | res->can_reset = can_reset; |
165 | |
166 | return res; |
167 | } |
168 | |
169 | static NamedTimer * |
170 | gsk_profiler_get_timer (GskProfiler *profiler, |
171 | GQuark id) |
172 | { |
173 | return g_hash_table_lookup (hash_table: profiler->timers, GINT_TO_POINTER (id)); |
174 | } |
175 | |
176 | GQuark |
177 | gsk_profiler_add_timer (GskProfiler *profiler, |
178 | const char *timer_name, |
179 | const char *description, |
180 | gboolean invert, |
181 | gboolean can_reset) |
182 | { |
183 | NamedTimer *timer; |
184 | GQuark id; |
185 | |
186 | g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); |
187 | |
188 | id = g_quark_from_string (string: timer_name); |
189 | timer = gsk_profiler_get_timer (profiler, id); |
190 | if (timer != NULL) |
191 | { |
192 | g_critical ("Cannot add a timer '%s' as one already exists." , timer_name); |
193 | return timer->id; |
194 | } |
195 | |
196 | timer = named_timer_new (id, description, invert, can_reset); |
197 | g_hash_table_insert (hash_table: profiler->timers, GINT_TO_POINTER (id), value: timer); |
198 | |
199 | return timer->id; |
200 | } |
201 | |
202 | void |
203 | gsk_profiler_counter_inc (GskProfiler *profiler, |
204 | GQuark counter_id) |
205 | { |
206 | gsk_profiler_counter_add (profiler, counter_id, increment: 1); |
207 | } |
208 | |
209 | void |
210 | gsk_profiler_counter_set (GskProfiler *profiler, |
211 | GQuark counter_id, |
212 | gint64 value) |
213 | { |
214 | NamedCounter *counter; |
215 | |
216 | g_return_if_fail (GSK_IS_PROFILER (profiler)); |
217 | |
218 | counter = gsk_profiler_get_counter (profiler, id: counter_id); |
219 | if (counter == NULL) |
220 | { |
221 | g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?" , |
222 | g_quark_to_string (counter_id), counter_id); |
223 | return; |
224 | } |
225 | |
226 | counter->value = value; |
227 | } |
228 | |
229 | void |
230 | gsk_profiler_counter_add (GskProfiler *profiler, |
231 | GQuark counter_id, |
232 | gint64 increment) |
233 | { |
234 | NamedCounter *counter; |
235 | |
236 | g_return_if_fail (GSK_IS_PROFILER (profiler)); |
237 | |
238 | counter = gsk_profiler_get_counter (profiler, id: counter_id); |
239 | if (counter == NULL) |
240 | { |
241 | g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?" , |
242 | g_quark_to_string (counter_id), counter_id); |
243 | return; |
244 | } |
245 | |
246 | counter->value += increment; |
247 | } |
248 | |
249 | void |
250 | gsk_profiler_timer_begin (GskProfiler *profiler, |
251 | GQuark timer_id) |
252 | { |
253 | NamedTimer *timer; |
254 | |
255 | g_return_if_fail (GSK_IS_PROFILER (profiler)); |
256 | |
257 | timer = gsk_profiler_get_timer (profiler, id: timer_id); |
258 | if (timer == NULL) |
259 | return; |
260 | |
261 | if (timer->in_flight) |
262 | return; |
263 | |
264 | timer->in_flight = TRUE; |
265 | timer->start_time = g_get_monotonic_time (); |
266 | } |
267 | |
268 | gint64 |
269 | gsk_profiler_timer_end (GskProfiler *profiler, |
270 | GQuark timer_id) |
271 | { |
272 | NamedTimer *timer; |
273 | gint64 diff; |
274 | |
275 | g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); |
276 | |
277 | timer = gsk_profiler_get_timer (profiler, id: timer_id); |
278 | if (timer == NULL) |
279 | { |
280 | g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?" , |
281 | g_quark_to_string (timer_id), timer_id); |
282 | return 0; |
283 | } |
284 | |
285 | if (!timer->in_flight) |
286 | { |
287 | g_critical ("Timer '%s' (id:%d) is not running; did you forget to call gsk_profiler_timer_begin()?" , |
288 | g_quark_to_string (timer->id), timer->id); |
289 | return 0; |
290 | } |
291 | |
292 | diff = g_get_monotonic_time () - timer->start_time; |
293 | |
294 | timer->in_flight = FALSE; |
295 | timer->value += diff; |
296 | |
297 | return diff; |
298 | } |
299 | |
300 | void |
301 | gsk_profiler_timer_set (GskProfiler *profiler, |
302 | GQuark timer_id, |
303 | gint64 value) |
304 | { |
305 | NamedTimer *timer; |
306 | |
307 | g_return_if_fail (GSK_IS_PROFILER (profiler)); |
308 | |
309 | timer = gsk_profiler_get_timer (profiler, id: timer_id); |
310 | if (timer == NULL) |
311 | { |
312 | g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?" , |
313 | g_quark_to_string (timer_id), timer_id); |
314 | return; |
315 | } |
316 | |
317 | if (timer->in_flight) |
318 | { |
319 | g_critical ("Timer '%s' (id:%d) is running; are you sure you don't want to call " |
320 | "gsk_profiler_timer_end() instead of gsk_profiler_timer_set()?" , |
321 | g_quark_to_string (timer_id), timer_id); |
322 | } |
323 | |
324 | timer->value = value; |
325 | } |
326 | |
327 | gint64 |
328 | gsk_profiler_counter_get (GskProfiler *profiler, |
329 | GQuark counter_id) |
330 | { |
331 | NamedCounter *counter; |
332 | |
333 | g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); |
334 | |
335 | counter = gsk_profiler_get_counter (profiler, id: counter_id); |
336 | if (counter == NULL) |
337 | { |
338 | g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?" , |
339 | g_quark_to_string (counter_id), counter_id); |
340 | return 0; |
341 | } |
342 | |
343 | return counter->value; |
344 | } |
345 | |
346 | gint64 |
347 | gsk_profiler_timer_get (GskProfiler *profiler, |
348 | GQuark timer_id) |
349 | { |
350 | NamedTimer *timer; |
351 | |
352 | g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); |
353 | |
354 | timer = gsk_profiler_get_timer (profiler, id: timer_id); |
355 | if (timer == NULL) |
356 | { |
357 | g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?" , |
358 | g_quark_to_string (timer_id), timer_id); |
359 | return 0; |
360 | } |
361 | |
362 | if (timer->invert) |
363 | return (gint64) (1000000.0 / (double) timer->value); |
364 | |
365 | return timer->value; |
366 | } |
367 | |
368 | gint64 |
369 | gsk_profiler_timer_get_start (GskProfiler *profiler, |
370 | GQuark timer_id) |
371 | { |
372 | NamedTimer *timer; |
373 | |
374 | timer = gsk_profiler_get_timer (profiler, id: timer_id); |
375 | if (timer == NULL) |
376 | return 0; |
377 | |
378 | return timer->start_time; |
379 | } |
380 | |
381 | void |
382 | gsk_profiler_reset (GskProfiler *profiler) |
383 | { |
384 | GHashTableIter iter; |
385 | gpointer value_p = NULL; |
386 | |
387 | g_return_if_fail (GSK_IS_PROFILER (profiler)); |
388 | |
389 | g_hash_table_iter_init (iter: &iter, hash_table: profiler->counters); |
390 | while (g_hash_table_iter_next (iter: &iter, NULL, value: &value_p)) |
391 | { |
392 | NamedCounter *counter = value_p; |
393 | |
394 | if (counter->can_reset) |
395 | counter->value = 0; |
396 | } |
397 | |
398 | g_hash_table_iter_init (iter: &iter, hash_table: profiler->timers); |
399 | while (g_hash_table_iter_next (iter: &iter, NULL, value: &value_p)) |
400 | { |
401 | NamedTimer *timer = value_p; |
402 | |
403 | if (timer->can_reset) |
404 | { |
405 | timer->value = 0; |
406 | timer->min_value = 0; |
407 | timer->max_value = 0; |
408 | timer->avg_value = 0; |
409 | timer->n_samples = 0; |
410 | } |
411 | } |
412 | |
413 | profiler->last_sample = 0; |
414 | } |
415 | |
416 | void |
417 | gsk_profiler_push_samples (GskProfiler *profiler) |
418 | { |
419 | GHashTableIter iter; |
420 | gpointer value_p = NULL; |
421 | guint last_sample; |
422 | |
423 | g_return_if_fail (GSK_IS_PROFILER (profiler)); |
424 | |
425 | g_hash_table_iter_init (iter: &iter, hash_table: profiler->timers); |
426 | while (g_hash_table_iter_next (iter: &iter, NULL, value: &value_p)) |
427 | { |
428 | NamedTimer *timer = value_p; |
429 | Sample *s; |
430 | |
431 | last_sample = profiler->last_sample; |
432 | profiler->last_sample += 1; |
433 | if (profiler->last_sample == MAX_SAMPLES) |
434 | profiler->last_sample = 0; |
435 | |
436 | s = &(profiler->timer_samples[last_sample]); |
437 | s->id = timer->id; |
438 | |
439 | if (timer->invert) |
440 | s->value = (gint64) (1000000.0 / (double) timer->value); |
441 | else |
442 | s->value = timer->value; |
443 | } |
444 | } |
445 | |
446 | void |
447 | gsk_profiler_append_counters (GskProfiler *profiler, |
448 | GString *buffer) |
449 | { |
450 | GHashTableIter iter; |
451 | gpointer value_p = NULL; |
452 | |
453 | g_return_if_fail (GSK_IS_PROFILER (profiler)); |
454 | g_return_if_fail (buffer != NULL); |
455 | |
456 | g_hash_table_iter_init (iter: &iter, hash_table: profiler->counters); |
457 | while (g_hash_table_iter_next (iter: &iter, NULL, value: &value_p)) |
458 | { |
459 | NamedCounter *counter = value_p; |
460 | |
461 | g_string_append_printf (string: buffer, format: "%s: %" G_GINT64_FORMAT "\n" , |
462 | counter->description, |
463 | counter->value); |
464 | } |
465 | } |
466 | |
467 | void |
468 | gsk_profiler_append_timers (GskProfiler *profiler, |
469 | GString *buffer) |
470 | { |
471 | GHashTableIter iter; |
472 | gpointer value_p = NULL; |
473 | int i; |
474 | |
475 | g_return_if_fail (GSK_IS_PROFILER (profiler)); |
476 | g_return_if_fail (buffer != NULL); |
477 | |
478 | g_hash_table_iter_init (iter: &iter, hash_table: profiler->timers); |
479 | while (g_hash_table_iter_next (iter: &iter, NULL, value: &value_p)) |
480 | { |
481 | NamedTimer *timer = value_p; |
482 | |
483 | timer->min_value = G_MAXINT64; |
484 | timer->max_value = G_MININT64; |
485 | timer->avg_value = 0; |
486 | timer->n_samples = 0; |
487 | } |
488 | |
489 | for (i = 0; i < profiler->last_sample; i++) |
490 | { |
491 | Sample *s = &(profiler->timer_samples[i]); |
492 | NamedTimer *timer; |
493 | |
494 | if (s->id == 0) |
495 | continue; |
496 | |
497 | timer = gsk_profiler_get_timer (profiler, id: s->id); |
498 | timer->min_value = MIN (timer->min_value, s->value); |
499 | timer->max_value = MAX (timer->max_value, s->value); |
500 | timer->avg_value += s->value; |
501 | timer->n_samples += 1; |
502 | } |
503 | |
504 | g_hash_table_iter_init (iter: &iter, hash_table: profiler->timers); |
505 | while (g_hash_table_iter_next (iter: &iter, NULL, value: &value_p)) |
506 | { |
507 | NamedTimer *timer = value_p; |
508 | const char *unit = timer->invert ? "" : "usec" ; |
509 | |
510 | g_string_append_printf (string: buffer, format: "%s (%s): %.2f" , |
511 | timer->description, |
512 | unit, |
513 | (double) timer->value); |
514 | |
515 | if (timer->n_samples > 1) |
516 | { |
517 | timer->avg_value = timer->avg_value / timer->n_samples; |
518 | g_string_append_printf (string: buffer, format: " Min: %.2f Avg: %.2f Max: %.2f (%" G_GINT64_FORMAT " samples)" , |
519 | (double) timer->min_value, |
520 | (double) timer->avg_value, |
521 | (double) timer->max_value, |
522 | timer->n_samples); |
523 | } |
524 | |
525 | g_string_append (string: buffer, val: "\n" ); |
526 | } |
527 | } |
528 | |