leaks: Allow passing a GstStructure to configure the tracer
[platform/upstream/gstreamer.git] / plugins / tracers / gstleaks.c
1 /* GStreamer
2  * Copyright (C) 2016 Collabora Ltd. <guillaume.desmottes@collabora.co.uk>
3  *
4  * gstleaks.c: tracing module detecting object leaks
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 /**
22  * SECTION:gstleaks
23  * @short_description: detect GstObject and GstMiniObject leaks
24  *
25  * A tracing module tracking the lifetime of objects by logging those still
26  * alive when program is exiting and raising a warning.
27  * The type of objects tracked can be filtered using the parameters of the
28  * tracer, for example: GST_TRACERS=leaks(filters="GstEvent,GstMessage",print-traces=true)
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #  include "config.h"
33 #endif
34
35 #include "gstleaks.h"
36
37 #ifdef G_OS_UNIX
38 #include <signal.h>
39 #endif /* G_OS_UNIX */
40
41 GST_DEBUG_CATEGORY_STATIC (gst_leaks_debug);
42 #define GST_CAT_DEFAULT gst_leaks_debug
43
44 #define _do_init \
45     GST_DEBUG_CATEGORY_INIT (gst_leaks_debug, "leaks", 0, "leaks tracer");
46 #define gst_leaks_tracer_parent_class parent_class
47 G_DEFINE_TYPE_WITH_CODE (GstLeaksTracer, gst_leaks_tracer,
48     GST_TYPE_TRACER, _do_init);
49
50 static GstTracerRecord *tr_alive;
51 #ifdef G_OS_UNIX
52 static GstTracerRecord *tr_added = NULL;
53 static GstTracerRecord *tr_removed = NULL;
54 #endif /* G_OS_UNIX */
55 static GQueue instances = G_QUEUE_INIT;
56
57 static void
58 set_print_stack_trace (GstLeaksTracer * self, GstStructure * params)
59 {
60   gchar *trace;
61   gboolean check_stack_trace =
62       g_getenv ("GST_LEAKS_TRACER_STACK_TRACE") != NULL;
63
64   if (!check_stack_trace && params)
65     gst_structure_get_boolean (params, "print-traces", &check_stack_trace);
66
67   if (!check_stack_trace)
68     return;
69
70
71   /* Test if we can retrieve backtrace */
72   trace = gst_debug_get_stack_trace (0);
73   if (trace) {
74     self->log_stack_trace = TRUE;
75     g_free (trace);
76   } else {
77     g_warning ("Can't retrieve backtrace on this system");
78   }
79 }
80
81 static void
82 set_filters (GstLeaksTracer * self, const gchar * filters)
83 {
84   guint i;
85   GStrv tmp = g_strsplit (filters, ",", -1);
86
87   self->filter = g_array_sized_new (FALSE, FALSE, sizeof (GType),
88       g_strv_length (tmp));
89   for (i = 0; tmp[i]; i++) {
90     GType type;
91
92     type = g_type_from_name (tmp[i]);
93     if (type == 0) {
94       /* The type may not yet be known by the type system, typically because
95        * the plugin implementing it as not yet be loaded. Save it for now as
96        * it will have another chance to be added to the filter later in
97        * should_handle_object_type() when/if the object type is actually
98        * used. */
99       if (!self->unhandled_filter)
100         self->unhandled_filter = g_hash_table_new (NULL, NULL);
101
102       g_hash_table_add (self->unhandled_filter,
103           GUINT_TO_POINTER (g_quark_from_string (tmp[i])));
104       g_atomic_int_inc (&self->unhandled_filter_count);
105       continue;
106     }
107
108     GST_DEBUG_OBJECT (self, "add filter on %s", tmp[i]);
109
110     g_array_append_val (self->filter, type);
111   }
112
113   g_strfreev (tmp);
114 }
115
116 static void
117 set_params_from_structure (GstLeaksTracer * self, GstStructure * params)
118 {
119   const gchar *filters = gst_structure_get_string (params, "filters");
120
121   if (filters)
122     set_filters (self, filters);
123 }
124
125 static void
126 set_params (GstLeaksTracer * self)
127 {
128   gchar *params, *tmp;
129   GstStructure *params_struct = NULL;
130
131   g_object_get (self, "params", &params, NULL);
132   if (!params)
133     goto set_stacktrace;
134
135   tmp = g_strdup_printf ("leaks,%s", params);
136   params_struct = gst_structure_from_string (tmp, NULL);
137   g_free (tmp);
138
139   if (params_struct)
140     set_params_from_structure (self, params_struct);
141   else
142     set_filters (self, params);
143
144   g_free (params);
145
146 set_stacktrace:
147   set_print_stack_trace (self, params_struct);
148
149   if (params_struct)
150     gst_structure_free (params_struct);
151 }
152
153 static gboolean
154 should_handle_object_type (GstLeaksTracer * self, GType object_type)
155 {
156   guint i, len;
157
158   if (!self->filter)
159     /* No filtering, handle all types */
160     return TRUE;
161
162   if (g_atomic_int_get (&self->unhandled_filter_count)) {
163     GST_OBJECT_LOCK (self);
164     if (self->unhandled_filter) {
165       GQuark q;
166
167       q = g_type_qname (object_type);
168       if (g_hash_table_contains (self->unhandled_filter, GUINT_TO_POINTER (q))) {
169         g_array_append_val (self->filter, object_type);
170         g_hash_table_remove (self->unhandled_filter, GUINT_TO_POINTER (q));
171
172         if (g_atomic_int_dec_and_test (&self->unhandled_filter_count))
173           g_clear_pointer (&self->unhandled_filter, g_hash_table_unref);
174
175         GST_OBJECT_UNLOCK (self);
176         return TRUE;
177       }
178     }
179     GST_OBJECT_UNLOCK (self);
180   }
181
182   len = self->filter->len;
183   for (i = 0; i < len; i++) {
184     GType type = g_array_index (self->filter, GType, i);
185
186     if (g_type_is_a (object_type, type))
187       return TRUE;
188   }
189
190   return FALSE;
191 }
192
193 #ifdef G_OS_UNIX
194 /* The object may be destroyed when we log it using the checkpointing system so
195  * we have to save its type name */
196 typedef struct
197 {
198   gpointer object;
199   const gchar *type_name;
200 } ObjectLog;
201
202 static ObjectLog *
203 object_log_new (gpointer obj)
204 {
205   ObjectLog *o = g_slice_new (ObjectLog);
206
207   o->object = obj;
208
209   if (G_IS_OBJECT (obj))
210     o->type_name = G_OBJECT_TYPE_NAME (obj);
211   else
212     o->type_name = g_type_name (GST_MINI_OBJECT_TYPE (obj));
213
214   return o;
215 }
216
217 static void
218 object_log_free (ObjectLog * obj)
219 {
220   g_slice_free (ObjectLog, obj);
221 }
222 #endif /* G_OS_UNIX */
223
224 static void
225 handle_object_destroyed (GstLeaksTracer * self, gpointer object)
226 {
227   GST_OBJECT_LOCK (self);
228   if (self->done) {
229     g_warning
230         ("object %p destroyed while the leaks tracer was finalizing. Some threads are still running?",
231         object);
232     goto out;
233   }
234
235   g_hash_table_remove (self->objects, object);
236 #ifdef G_OS_UNIX
237   if (self->removed)
238     g_hash_table_add (self->removed, object_log_new (object));
239 #endif /* G_OS_UNIX */
240 out:
241   GST_OBJECT_UNLOCK (self);
242 }
243
244 static void
245 object_weak_cb (gpointer data, GObject * object)
246 {
247   GstLeaksTracer *self = data;
248
249   handle_object_destroyed (self, object);
250 }
251
252 static void
253 mini_object_weak_cb (gpointer data, GstMiniObject * object)
254 {
255   GstLeaksTracer *self = data;
256
257   handle_object_destroyed (self, object);
258 }
259
260 static void
261 handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
262     gboolean gobject)
263 {
264   gchar *trace = NULL;
265
266   if (!should_handle_object_type (self, type))
267     return;
268
269   if (gobject)
270     g_object_weak_ref ((GObject *) object, object_weak_cb, self);
271   else
272     gst_mini_object_weak_ref (GST_MINI_OBJECT_CAST (object),
273         mini_object_weak_cb, self);
274
275   GST_OBJECT_LOCK (self);
276   if (self->log_stack_trace) {
277     trace = gst_debug_get_stack_trace (0);
278   }
279
280   g_hash_table_insert (self->objects, object, trace);
281
282 #ifdef G_OS_UNIX
283   if (self->added)
284     g_hash_table_add (self->added, object_log_new (object));
285 #endif /* G_OS_UNIX */
286   GST_OBJECT_UNLOCK (self);
287 }
288
289 static void
290 mini_object_created_cb (GstTracer * tracer, GstClockTime ts,
291     GstMiniObject * object)
292 {
293   GstLeaksTracer *self = GST_LEAKS_TRACER_CAST (tracer);
294
295   handle_object_created (self, object, GST_MINI_OBJECT_TYPE (object), FALSE);
296 }
297
298 static void
299 object_created_cb (GstTracer * tracer, GstClockTime ts, GstObject * object)
300 {
301   GstLeaksTracer *self = GST_LEAKS_TRACER_CAST (tracer);
302   GType object_type = G_OBJECT_TYPE (object);
303
304   /* Can't track tracers as they may be disposed after the leak tracer itself */
305   if (g_type_is_a (object_type, GST_TYPE_TRACER))
306     return;
307
308   handle_object_created (self, object, object_type, TRUE);
309 }
310
311 static void
312 gst_leaks_tracer_init (GstLeaksTracer * self)
313 {
314   self->objects = g_hash_table_new_full (NULL, NULL, NULL, g_free);
315
316   g_queue_push_tail (&instances, self);
317 }
318
319 static void
320 gst_leaks_tracer_constructed (GObject * object)
321 {
322   GstLeaksTracer *self = GST_LEAKS_TRACER (object);
323   GstTracer *tracer = GST_TRACER (object);
324
325   set_params (self);
326
327   gst_tracing_register_hook (tracer, "mini-object-created",
328       G_CALLBACK (mini_object_created_cb));
329   gst_tracing_register_hook (tracer, "object-created",
330       G_CALLBACK (object_created_cb));
331
332   /* We rely on weak pointers rather than (mini-)object-destroyed hooks so we
333    * are notified of objects being destroyed even during the shuting down of
334    * the tracing system. */
335
336   ((GObjectClass *) gst_leaks_tracer_parent_class)->constructed (object);
337 }
338
339 typedef struct
340 {
341   gpointer obj;
342   const gchar *type_name;
343   guint ref_count;
344   gchar *desc;
345   const gchar *trace;
346 } Leak;
347
348 /* The content of the returned Leak struct is valid until the self->objects
349  * hash table has been modified. */
350 static Leak *
351 leak_new (gpointer obj, GType type, guint ref_count, const gchar * trace)
352 {
353   Leak *leak = g_slice_new (Leak);
354
355   leak->obj = obj;
356   leak->type_name = g_type_name (type);
357   leak->ref_count = ref_count;
358   leak->desc = gst_info_strdup_printf ("%" GST_PTR_FORMAT, obj);
359   leak->trace = trace;
360
361   return leak;
362 }
363
364 static void
365 leak_free (Leak * leak)
366 {
367   g_free (leak->desc);
368   g_slice_free (Leak, leak);
369 }
370
371 static gint
372 sort_leaks (gconstpointer _a, gconstpointer _b)
373 {
374   const Leak *a = _a, *b = _b;
375
376   return g_strcmp0 (a->type_name, b->type_name);
377 }
378
379 static GList *
380 create_leaks_list (GstLeaksTracer * self)
381 {
382   GList *l = NULL;
383   GHashTableIter iter;
384   gpointer obj, trace;
385
386   g_hash_table_iter_init (&iter, self->objects);
387   while (g_hash_table_iter_next (&iter, &obj, &trace)) {
388     GType type;
389     guint ref_count;
390
391     if (GST_IS_OBJECT (obj)) {
392       if (GST_OBJECT_FLAG_IS_SET (obj, GST_OBJECT_FLAG_MAY_BE_LEAKED))
393         continue;
394
395       type = G_OBJECT_TYPE (obj);
396       ref_count = ((GObject *) obj)->ref_count;
397     } else {
398       if (GST_MINI_OBJECT_FLAG_IS_SET (obj, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED))
399         continue;
400
401       type = GST_MINI_OBJECT_TYPE (obj);
402       ref_count = ((GstMiniObject *) obj)->refcount;
403     }
404
405     l = g_list_prepend (l, leak_new (obj, type, ref_count, trace));
406   }
407
408   /* Sort leaks by type name so they are grouped together making the output
409    * easier to read */
410   l = g_list_sort (l, sort_leaks);
411
412   return l;
413 }
414
415 /* Return TRUE if at least one leaked object has been logged */
416 static gboolean
417 log_leaked (GstLeaksTracer * self)
418 {
419   GList *leaks, *l;
420
421   leaks = create_leaks_list (self);
422   if (!leaks)
423     return FALSE;
424
425   for (l = leaks; l != NULL; l = g_list_next (l)) {
426     Leak *leak = l->data;
427
428     gst_tracer_record_log (tr_alive, leak->type_name, leak->obj, leak->desc,
429         leak->ref_count, leak->trace ? leak->trace : "");
430   }
431
432   g_list_free_full (leaks, (GDestroyNotify) leak_free);
433
434   return TRUE;
435 }
436
437 static void
438 gst_leaks_tracer_finalize (GObject * object)
439 {
440   GstLeaksTracer *self = GST_LEAKS_TRACER (object);
441   gboolean leaks;
442   GHashTableIter iter;
443   gpointer obj;
444
445   self->done = TRUE;
446
447   /* Tracers are destroyed as part of gst_deinit() so now is a good time to
448    * report all the objects which are still alive. */
449   leaks = log_leaked (self);
450
451   /* Remove weak references */
452   g_hash_table_iter_init (&iter, self->objects);
453   while (g_hash_table_iter_next (&iter, &obj, NULL)) {
454     if (GST_IS_OBJECT (obj))
455       g_object_weak_unref (obj, object_weak_cb, self);
456     else
457       gst_mini_object_weak_unref (GST_MINI_OBJECT_CAST (obj),
458           mini_object_weak_cb, self);
459   }
460
461   g_clear_pointer (&self->objects, g_hash_table_unref);
462   if (self->filter)
463     g_array_free (self->filter, TRUE);
464   g_clear_pointer (&self->added, g_hash_table_unref);
465   g_clear_pointer (&self->removed, g_hash_table_unref);
466   g_clear_pointer (&self->unhandled_filter, g_hash_table_unref);
467
468   g_queue_remove (&instances, self);
469
470   if (leaks)
471     g_warning ("Leaks detected");
472
473   ((GObjectClass *) gst_leaks_tracer_parent_class)->finalize (object);
474 }
475
476 #define RECORD_FIELD_TYPE_NAME \
477     "type-name", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
478         "type", G_TYPE_GTYPE, G_TYPE_STRING, \
479         "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
480         NULL)
481 #define RECORD_FIELD_ADDRESS \
482     "address", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
483         "type", G_TYPE_GTYPE, G_TYPE_POINTER, \
484         "related-to", GST_TYPE_TRACER_VALUE_SCOPE, \
485         GST_TRACER_VALUE_SCOPE_PROCESS, \
486         NULL)
487 #define RECORD_FIELD_DESC \
488     "description", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
489         "type", G_TYPE_GTYPE, G_TYPE_STRING, \
490         "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
491         NULL)
492 #define RECORD_FIELD_REF_COUNT \
493     "ref-count", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
494         "type", G_TYPE_GTYPE, G_TYPE_UINT, \
495         "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
496         NULL)
497 #define RECORD_FIELD_TRACE \
498     "trace", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
499         "type", G_TYPE_GTYPE, G_TYPE_STRING, \
500         "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
501         NULL)
502
503 #ifdef G_OS_UNIX
504 static void
505 sig_usr1_handler_foreach (gpointer data, gpointer user_data)
506 {
507   GstLeaksTracer *tracer = data;
508
509   GST_OBJECT_LOCK (tracer);
510   GST_TRACE_OBJECT (tracer, "start listing currently alive objects");
511   log_leaked (tracer);
512   GST_TRACE_OBJECT (tracer, "done listing currently alive objects");
513   GST_OBJECT_UNLOCK (tracer);
514 }
515
516 static void
517 sig_usr1_handler (G_GNUC_UNUSED int signal)
518 {
519   g_queue_foreach (&instances, sig_usr1_handler_foreach, NULL);
520 }
521
522 static void
523 log_checkpoint (GHashTable * hash, GstTracerRecord * record)
524 {
525   GHashTableIter iter;
526   gpointer o;
527
528   g_hash_table_iter_init (&iter, hash);
529   while (g_hash_table_iter_next (&iter, &o, NULL)) {
530     ObjectLog *obj = o;
531
532     gst_tracer_record_log (record, obj->type_name, obj->object);
533   }
534 }
535
536 static void
537 do_checkpoint (GstLeaksTracer * self)
538 {
539   GST_TRACE_OBJECT (self, "listing objects created since last checkpoint");
540   log_checkpoint (self->added, tr_added);
541   GST_TRACE_OBJECT (self, "listing objects removed since last checkpoint");
542   log_checkpoint (self->removed, tr_removed);
543
544   g_hash_table_remove_all (self->added);
545   g_hash_table_remove_all (self->removed);
546 }
547
548 static void
549 sig_usr2_handler_foreach (gpointer data, gpointer user_data)
550 {
551   GstLeaksTracer *tracer = data;
552
553   GST_OBJECT_LOCK (tracer);
554
555   if (!tracer->added) {
556     GST_TRACE_OBJECT (tracer, "First checkpoint, start tracking objects");
557
558     tracer->added = g_hash_table_new_full (NULL, NULL,
559         (GDestroyNotify) object_log_free, NULL);
560     tracer->removed = g_hash_table_new_full (NULL, NULL,
561         (GDestroyNotify) object_log_free, NULL);
562   } else {
563     do_checkpoint (tracer);
564   }
565
566   GST_OBJECT_UNLOCK (tracer);
567 }
568
569 static void
570 sig_usr2_handler (G_GNUC_UNUSED int signal)
571 {
572   g_queue_foreach (&instances, sig_usr2_handler_foreach, NULL);
573 }
574
575 static void
576 setup_signals (void)
577 {
578   tr_added = gst_tracer_record_new ("object-added.class",
579       RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS, NULL);
580   GST_OBJECT_FLAG_SET (tr_added, GST_OBJECT_FLAG_MAY_BE_LEAKED);
581
582   tr_removed = gst_tracer_record_new ("object-removed.class",
583       RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS, NULL);
584   GST_OBJECT_FLAG_SET (tr_removed, GST_OBJECT_FLAG_MAY_BE_LEAKED);
585
586   signal (SIGUSR1, sig_usr1_handler);
587   signal (SIGUSR2, sig_usr2_handler);
588 }
589 #endif /* G_OS_UNIX */
590
591 static void
592 gst_leaks_tracer_class_init (GstLeaksTracerClass * klass)
593 {
594   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
595
596   gobject_class->constructed = gst_leaks_tracer_constructed;
597   gobject_class->finalize = gst_leaks_tracer_finalize;
598
599   tr_alive = gst_tracer_record_new ("object-alive.class",
600       RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS, RECORD_FIELD_DESC,
601       RECORD_FIELD_REF_COUNT, RECORD_FIELD_TRACE, NULL);
602   GST_OBJECT_FLAG_SET (tr_alive, GST_OBJECT_FLAG_MAY_BE_LEAKED);
603
604   if (g_getenv ("GST_LEAKS_TRACER_SIG")) {
605 #ifdef G_OS_UNIX
606     setup_signals ();
607 #else
608     g_warning ("System doesn't support POSIX signals");
609 #endif /* G_OS_UNIX */
610   }
611 }