e78e53a0378d826b2a8456d4dee1c500a2fb91f0
[platform/upstream/gstreamer.git] / gst / gstdebugutils.c
1 /* GStreamer
2  * Copyright (C) 2007 Stefan Kost <ensonic@users.sf.net>
3  *
4  * gstdebugutils.c: debugging and analysis utillities
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 /* TODO:
22  * edge [ constraint=false ];
23  *   this creates strange graphs ("minlen=0" is better)
24  * try puting src/sink ghostpads for each bin into invisible clusters
25  *
26  * for more compact nodes, try
27  * - changing node-shape from box into record
28  * - use labels like : element [ label="{element | <src> src | <sink> sink}"]
29  * - point to record-connectors : element1:src -> element2:sink
30  * - we use head/tail labels for pad-caps right now
31  *   - this does not work well, as dot seems to not look at their size when
32  *     doing the layout
33  *   - we could add the caps to the pad itself, then we should use one line per
34  *     caps (simple caps = one line)
35  */
36
37 #include "gst_private.h"
38 #include "gstdebugutils.h"
39
40 #ifndef GST_DISABLE_GST_DEBUG
41
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <string.h>
45 #include <errno.h>
46
47 #include "gstinfo.h"
48 #include "gstbin.h"
49 #include "gstobject.h"
50 #include "gstghostpad.h"
51 #include "gstpad.h"
52 #include "gstutils.h"
53 #include "gstvalue.h"
54
55 /*** PIPELINE GRAPHS **********************************************************/
56
57 extern const gchar *priv_gst_dump_dot_dir;      /* NULL *//* set from gst.c */
58
59 #define PARAM_MAX_LENGTH 80
60
61 const gchar spaces[] = {
62   "                                "    /* 32 */
63       "                                "        /* 64 */
64       "                                "        /* 96 */
65       "                                "        /* 128 */
66 };
67
68 #define MAKE_INDENT(indent) \
69   &spaces[MAX (sizeof (spaces) - (1 + (indent) * 2), 0)]
70
71 static gchar *
72 debug_dump_make_object_name (GstObject * obj)
73 {
74   return g_strcanon (g_strdup_printf ("%s_%p", GST_OBJECT_NAME (obj), obj),
75       G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "_", '_');
76 }
77
78 static gchar *
79 debug_dump_get_element_state (GstElement * element)
80 {
81   gchar *state_name = NULL;
82   const gchar *state_icons = "~0-=>";
83   GstState state = GST_STATE_VOID_PENDING, pending = GST_STATE_VOID_PENDING;
84
85   gst_element_get_state (element, &state, &pending, 0);
86   if (pending == GST_STATE_VOID_PENDING) {
87     gboolean is_locked = gst_element_is_locked_state (element);
88     state_name = g_strdup_printf ("\\n[%c]%s", state_icons[state],
89         (is_locked ? "(locked)" : ""));
90   } else {
91     state_name = g_strdup_printf ("\\n[%c] -> [%c]", state_icons[state],
92         state_icons[pending]);
93   }
94   return state_name;
95 }
96
97 static gchar *
98 debug_dump_get_object_params (GObject * object,
99     GstDebugGraphDetails details, const char *const *ignored_propnames)
100 {
101   gchar *param_name = NULL;
102   GParamSpec **properties, *property;
103   GValue value = { 0, };
104   guint i, number_of_properties;
105   gchar *tmp, *value_str;
106   const gchar *ellipses;
107
108   /* get paramspecs and show non-default properties */
109   properties =
110       g_object_class_list_properties (G_OBJECT_GET_CLASS (object),
111       &number_of_properties);
112   if (properties) {
113     for (i = 0; i < number_of_properties; i++) {
114       gint j;
115       gboolean ignore = FALSE;
116       property = properties[i];
117
118       /* skip some properties */
119       if (!(property->flags & G_PARAM_READABLE))
120         continue;
121       if (!strcmp (property->name, "name"))
122         continue;
123
124       if (ignored_propnames)
125         for (j = 0; ignored_propnames[j]; j++)
126           if (!g_strcmp0 (ignored_propnames[j], property->name))
127             ignore = TRUE;
128
129       if (ignore)
130         continue;
131
132       g_value_init (&value, property->value_type);
133       g_object_get_property (G_OBJECT (object), property->name, &value);
134       if (!(g_param_value_defaults (property, &value))) {
135         /* we need to serialise enums and flags ourselves to make sure the
136          * enum/flag nick is used and not the enum/flag name, which would be the
137          * C header enum/flag for public enums/flags, but for element-specific
138          * enums/flags we abuse the name field for the property description,
139          * and we don't want to print that in the dot file. The nick will
140          * always work, and it's also shorter. */
141         if (G_VALUE_HOLDS_ENUM (&value)) {
142           GEnumClass *e_class = g_type_class_ref (G_VALUE_TYPE (&value));
143           gint idx, e_val;
144
145           tmp = NULL;
146           e_val = g_value_get_enum (&value);
147           for (idx = 0; idx < e_class->n_values; ++idx) {
148             if (e_class->values[idx].value == e_val) {
149               tmp = g_strdup (e_class->values[idx].value_nick);
150               break;
151             }
152           }
153           if (tmp == NULL)
154             continue;
155         } else if (G_VALUE_HOLDS_FLAGS (&value)) {
156           GFlagsClass *f_class = g_type_class_ref (G_VALUE_TYPE (&value));
157           GFlagsValue *vals = f_class->values;
158           GString *s = NULL;
159           guint idx, flags_left;
160
161           s = g_string_new (NULL);
162
163           /* we assume the values are sorted from lowest to highest value */
164           flags_left = g_value_get_flags (&value);
165           idx = f_class->n_values;
166           while (idx > 0) {
167             --idx;
168             if (vals[idx].value != 0
169                 && (flags_left & vals[idx].value) == vals[idx].value) {
170               if (s->len > 0)
171                 g_string_prepend_c (s, '+');
172               g_string_prepend (s, vals[idx].value_nick);
173               flags_left -= vals[idx].value;
174               if (flags_left == 0)
175                 break;
176             }
177           }
178
179           if (s->len == 0)
180             g_string_assign (s, "(none)");
181
182           tmp = g_string_free (s, FALSE);
183         } else {
184           tmp = g_strdup_value_contents (&value);
185         }
186         value_str = g_strescape (tmp, NULL);
187         g_free (tmp);
188
189         /* too long, ellipsize */
190         if (!(details & GST_DEBUG_GRAPH_SHOW_FULL_PARAMS) &&
191             strlen (value_str) > PARAM_MAX_LENGTH)
192           ellipses = "…";
193         else
194           ellipses = "";
195
196         if (param_name)
197           tmp = param_name;
198         else
199           tmp = (char *) "";
200
201         if (details & GST_DEBUG_GRAPH_SHOW_FULL_PARAMS) {
202           param_name = g_strdup_printf ("%s\\n%s=%s", tmp, property->name,
203               value_str);
204         } else {
205           param_name = g_strdup_printf ("%s\\n%s=%."
206               G_STRINGIFY (PARAM_MAX_LENGTH) "s%s", tmp, property->name,
207               value_str, ellipses);
208         }
209
210         if (tmp[0] != '\0')
211           g_free (tmp);
212
213         g_free (value_str);
214       }
215       g_value_unset (&value);
216     }
217     g_free (properties);
218   }
219   return param_name;
220 }
221
222 static void
223 debug_dump_pad (GstPad * pad, const gchar * color_name,
224     const gchar * element_name, GstDebugGraphDetails details, GString * str,
225     const gint indent)
226 {
227   GstPadTemplate *pad_templ;
228   GstPadPresence presence;
229   gchar *pad_name, *param_name = NULL;
230   const gchar *style_name;
231   static const char *const ignore_propnames[] =
232       { "parent", "direction", "template",
233     "caps", NULL
234   };
235   const gchar *spc = MAKE_INDENT (indent);
236
237   pad_name = debug_dump_make_object_name (GST_OBJECT (pad));
238
239   /* pad availability */
240   style_name = "filled,solid";
241   if ((pad_templ = gst_pad_get_pad_template (pad))) {
242     presence = GST_PAD_TEMPLATE_PRESENCE (pad_templ);
243     gst_object_unref (pad_templ);
244     if (presence == GST_PAD_SOMETIMES) {
245       style_name = "filled,dotted";
246     } else if (presence == GST_PAD_REQUEST) {
247       style_name = "filled,dashed";
248     }
249   }
250
251   param_name =
252       debug_dump_get_object_params (G_OBJECT (pad), details, ignore_propnames);
253   if (details & GST_DEBUG_GRAPH_SHOW_STATES) {
254     gchar pad_flags[4];
255     const gchar *activation_mode = "-><";
256     const gchar *task_mode = "";
257     GstTask *task;
258
259     GST_OBJECT_LOCK (pad);
260     task = GST_PAD_TASK (pad);
261     if (task) {
262       switch (gst_task_get_state (task)) {
263         case GST_TASK_STARTED:
264           task_mode = "[T]";
265           break;
266         case GST_TASK_PAUSED:
267           task_mode = "[t]";
268           break;
269         default:
270           /* Invalid task state, ignoring */
271           break;
272       }
273     }
274     GST_OBJECT_UNLOCK (pad);
275
276     /* check if pad flags */
277     pad_flags[0] =
278         GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLAG_BLOCKED) ? 'B' : 'b';
279     pad_flags[1] =
280         GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLAG_FLUSHING) ? 'F' : 'f';
281     pad_flags[2] =
282         GST_OBJECT_FLAG_IS_SET (pad, GST_PAD_FLAG_BLOCKING) ? 'B' : 'b';
283     pad_flags[3] = '\0';
284
285     g_string_append_printf (str,
286         "%s  %s_%s [color=black, fillcolor=\"%s\", label=\"%s%s\\n[%c][%s]%s\", height=\"0.2\", style=\"%s\"];\n",
287         spc, element_name, pad_name, color_name, GST_OBJECT_NAME (pad),
288         (param_name ? param_name : ""),
289         activation_mode[pad->mode], pad_flags, task_mode, style_name);
290   } else {
291     g_string_append_printf (str,
292         "%s  %s_%s [color=black, fillcolor=\"%s\", label=\"%s%s\", height=\"0.2\", style=\"%s\"];\n",
293         spc, element_name, pad_name, color_name, GST_OBJECT_NAME (pad),
294         (param_name ? param_name : ""), style_name);
295   }
296
297   g_free (param_name);
298   g_free (pad_name);
299 }
300
301 static void
302 debug_dump_element_pad (GstPad * pad, GstElement * element,
303     GstDebugGraphDetails details, GString * str, const gint indent)
304 {
305   GstElement *target_element;
306   GstPad *target_pad, *tmp_pad;
307   GstPadDirection dir;
308   gchar *element_name;
309   gchar *target_element_name;
310   const gchar *color_name;
311
312   dir = gst_pad_get_direction (pad);
313   element_name = debug_dump_make_object_name (GST_OBJECT (element));
314   if (GST_IS_GHOST_PAD (pad)) {
315     color_name =
316         (dir == GST_PAD_SRC) ? "#ffdddd" : ((dir ==
317             GST_PAD_SINK) ? "#ddddff" : "#ffffff");
318     /* output target-pad so that it belongs to this element */
319     if ((tmp_pad = gst_ghost_pad_get_target (GST_GHOST_PAD (pad)))) {
320       if ((target_pad = gst_pad_get_peer (tmp_pad))) {
321         gchar *pad_name, *target_pad_name;
322         const gchar *spc = MAKE_INDENT (indent);
323
324         if ((target_element = gst_pad_get_parent_element (target_pad))) {
325           target_element_name =
326               debug_dump_make_object_name (GST_OBJECT (target_element));
327         } else {
328           target_element_name = g_strdup ("");
329         }
330         debug_dump_pad (target_pad, color_name, target_element_name, details,
331             str, indent);
332         /* src ghostpad relationship */
333         pad_name = debug_dump_make_object_name (GST_OBJECT (pad));
334         target_pad_name = debug_dump_make_object_name (GST_OBJECT (target_pad));
335         if (dir == GST_PAD_SRC) {
336           g_string_append_printf (str,
337               "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n", spc,
338               target_element_name, target_pad_name, element_name, pad_name);
339         } else {
340           g_string_append_printf (str,
341               "%s%s_%s -> %s_%s [style=dashed, minlen=0]\n", spc,
342               element_name, pad_name, target_element_name, target_pad_name);
343         }
344         g_free (target_pad_name);
345         g_free (target_element_name);
346         if (target_element)
347           gst_object_unref (target_element);
348         gst_object_unref (target_pad);
349         g_free (pad_name);
350       }
351       gst_object_unref (tmp_pad);
352     }
353   } else {
354     color_name =
355         (dir == GST_PAD_SRC) ? "#ffaaaa" : ((dir ==
356             GST_PAD_SINK) ? "#aaaaff" : "#cccccc");
357   }
358   /* pads */
359   debug_dump_pad (pad, color_name, element_name, details, str, indent);
360   g_free (element_name);
361 }
362
363 static gboolean
364 string_append_field (GQuark field, const GValue * value, gpointer ptr)
365 {
366   GString *str = (GString *) ptr;
367   gchar *value_str = gst_value_serialize (value);
368   gchar *esc_value_str;
369
370   if (value_str == NULL) {
371     g_string_append_printf (str, "  %18s: NULL\\l", g_quark_to_string (field));
372     return TRUE;
373   }
374
375   /* some enums can become really long */
376   if (strlen (value_str) > 25) {
377     gint pos = 24;
378
379     /* truncate */
380     value_str[25] = '\0';
381
382     /* mirror any brackets and quotes */
383     if (value_str[0] == '<')
384       value_str[pos--] = '>';
385     if (value_str[0] == '[')
386       value_str[pos--] = ']';
387     if (value_str[0] == '(')
388       value_str[pos--] = ')';
389     if (value_str[0] == '{')
390       value_str[pos--] = '}';
391     if (value_str[0] == '"')
392       value_str[pos--] = '"';
393     if (pos != 24)
394       value_str[pos--] = ' ';
395     /* elippsize */
396     value_str[pos--] = '.';
397     value_str[pos--] = '.';
398     value_str[pos--] = '.';
399   }
400   esc_value_str = g_strescape (value_str, NULL);
401
402   g_string_append_printf (str, "  %18s: %s\\l", g_quark_to_string (field),
403       esc_value_str);
404
405   g_free (value_str);
406   g_free (esc_value_str);
407   return TRUE;
408 }
409
410 static gchar *
411 debug_dump_describe_caps (GstCaps * caps, GstDebugGraphDetails details)
412 {
413   gchar *media = NULL;
414
415   if (details & GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS) {
416
417     if (gst_caps_is_any (caps) || gst_caps_is_empty (caps)) {
418       media = gst_caps_to_string (caps);
419
420     } else {
421       GString *str = NULL;
422       guint i;
423       guint slen = 0;
424
425       for (i = 0; i < gst_caps_get_size (caps); i++) {
426         slen += 25 +
427             STRUCTURE_ESTIMATED_STRING_LEN (gst_caps_get_structure (caps, i));
428       }
429
430       str = g_string_sized_new (slen);
431       for (i = 0; i < gst_caps_get_size (caps); i++) {
432         GstCapsFeatures *features = __gst_caps_get_features_unchecked (caps, i);
433         GstStructure *structure = gst_caps_get_structure (caps, i);
434
435         g_string_append (str, gst_structure_get_name (structure));
436
437         if (features && (gst_caps_features_is_any (features)
438                 || !gst_caps_features_is_equal (features,
439                     GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY))) {
440           g_string_append_c (str, '(');
441           priv_gst_caps_features_append_to_gstring (features, str);
442           g_string_append_c (str, ')');
443         }
444         g_string_append (str, "\\l");
445
446         gst_structure_foreach (structure, string_append_field, (gpointer) str);
447       }
448
449       media = g_string_free (str, FALSE);
450     }
451
452   } else {
453     if (GST_CAPS_IS_SIMPLE (caps))
454       media =
455           g_strdup (gst_structure_get_name (gst_caps_get_structure (caps, 0)));
456     else
457       media = g_strdup ("*");
458   }
459   return media;
460 }
461
462 static void
463 debug_dump_element_pad_link (GstPad * pad, GstElement * element,
464     GstDebugGraphDetails details, GString * str, const gint indent)
465 {
466   GstElement *peer_element;
467   GstPad *peer_pad;
468   GstCaps *caps, *peer_caps;
469   gchar *media = NULL;
470   gchar *media_src = NULL, *media_sink = NULL;
471   gchar *pad_name, *element_name;
472   gchar *peer_pad_name, *peer_element_name;
473   const gchar *spc = MAKE_INDENT (indent);
474
475   if ((peer_pad = gst_pad_get_peer (pad))) {
476     if ((details & GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE) ||
477         (details & GST_DEBUG_GRAPH_SHOW_CAPS_DETAILS)
478         ) {
479       caps = gst_pad_get_current_caps (pad);
480       if (!caps)
481         caps = gst_pad_get_pad_template_caps (pad);
482       peer_caps = gst_pad_get_current_caps (peer_pad);
483       if (!peer_caps)
484         peer_caps = gst_pad_get_pad_template_caps (peer_pad);
485
486       media = debug_dump_describe_caps (caps, details);
487       /* check if peer caps are different */
488       if (peer_caps && !gst_caps_is_equal (caps, peer_caps)) {
489         gchar *tmp;
490
491         tmp = debug_dump_describe_caps (peer_caps, details);
492         if (gst_pad_get_direction (pad) == GST_PAD_SRC) {
493           media_src = media;
494           media_sink = tmp;
495         } else {
496           media_src = tmp;
497           media_sink = media;
498         }
499         media = NULL;
500       }
501       gst_caps_unref (peer_caps);
502       gst_caps_unref (caps);
503     }
504
505     pad_name = debug_dump_make_object_name (GST_OBJECT (pad));
506     if (element) {
507       element_name = debug_dump_make_object_name (GST_OBJECT (element));
508     } else {
509       element_name = g_strdup ("");
510     }
511     peer_pad_name = debug_dump_make_object_name (GST_OBJECT (peer_pad));
512     if ((peer_element = gst_pad_get_parent_element (peer_pad))) {
513       peer_element_name =
514           debug_dump_make_object_name (GST_OBJECT (peer_element));
515     } else {
516       peer_element_name = g_strdup ("");
517     }
518
519     /* pad link */
520     if (media) {
521       g_string_append_printf (str, "%s%s_%s -> %s_%s [label=\"%s\"]\n", spc,
522           element_name, pad_name, peer_element_name, peer_pad_name, media);
523       g_free (media);
524     } else if (media_src && media_sink) {
525       /* dot has some issues with placement of head and taillabels,
526        * we need an empty label to make space */
527       g_string_append_printf (str,
528           "%s%s_%s -> %s_%s [labeldistance=\"10\", labelangle=\"0\", "
529           "label=\"                                                  \", "
530           "taillabel=\"%s\", headlabel=\"%s\"]\n",
531           spc, element_name, pad_name, peer_element_name, peer_pad_name,
532           media_src, media_sink);
533       g_free (media_src);
534       g_free (media_sink);
535     } else {
536       g_string_append_printf (str, "%s%s_%s -> %s_%s\n", spc,
537           element_name, pad_name, peer_element_name, peer_pad_name);
538     }
539
540     g_free (pad_name);
541     g_free (element_name);
542     g_free (peer_pad_name);
543     g_free (peer_element_name);
544     if (peer_element)
545       gst_object_unref (peer_element);
546     gst_object_unref (peer_pad);
547   }
548 }
549
550 static void
551 debug_dump_element_pads (GstIterator * pad_iter, GstPad * pad,
552     GstElement * element, GstDebugGraphDetails details, GString * str,
553     const gint indent, guint * num_pads, gchar * cluster_name,
554     gchar ** first_pad_name)
555 {
556   GValue item = { 0, };
557   gboolean pads_done;
558   const gchar *spc = MAKE_INDENT (indent);
559
560   pads_done = FALSE;
561   while (!pads_done) {
562     switch (gst_iterator_next (pad_iter, &item)) {
563       case GST_ITERATOR_OK:
564         pad = g_value_get_object (&item);
565         if (!*num_pads) {
566           g_string_append_printf (str, "%ssubgraph cluster_%s {\n", spc,
567               cluster_name);
568           g_string_append_printf (str, "%s  label=\"\";\n", spc);
569           g_string_append_printf (str, "%s  style=\"invis\";\n", spc);
570           (*first_pad_name) = debug_dump_make_object_name (GST_OBJECT (pad));
571         }
572         debug_dump_element_pad (pad, element, details, str, indent);
573         (*num_pads)++;
574         g_value_reset (&item);
575         break;
576       case GST_ITERATOR_RESYNC:
577         gst_iterator_resync (pad_iter);
578         break;
579       case GST_ITERATOR_ERROR:
580       case GST_ITERATOR_DONE:
581         pads_done = TRUE;
582         break;
583     }
584   }
585   if (*num_pads) {
586     g_string_append_printf (str, "%s}\n\n", spc);
587   }
588 }
589
590 /*
591  * debug_dump_element:
592  * @bin: the bin that should be analyzed
593  * @out: file to write to
594  * @indent: level of graph indentation
595  *
596  * Helper for gst_debug_bin_to_dot_file() to recursively dump a pipeline.
597  */
598 static void
599 debug_dump_element (GstBin * bin, GstDebugGraphDetails details,
600     GString * str, const gint indent)
601 {
602   GstIterator *element_iter, *pad_iter;
603   gboolean elements_done, pads_done;
604   GValue item = { 0, };
605   GValue item2 = { 0, };
606   GstElement *element;
607   GstPad *pad = NULL;
608   guint src_pads, sink_pads;
609   gchar *src_pad_name = NULL, *sink_pad_name = NULL;
610   gchar *element_name;
611   gchar *state_name = NULL;
612   gchar *param_name = NULL;
613   const gchar *spc = MAKE_INDENT (indent);
614
615   element_iter = gst_bin_iterate_elements (bin);
616   elements_done = FALSE;
617   while (!elements_done) {
618     switch (gst_iterator_next (element_iter, &item)) {
619       case GST_ITERATOR_OK:
620         element = g_value_get_object (&item);
621         element_name = debug_dump_make_object_name (GST_OBJECT (element));
622
623         if (details & GST_DEBUG_GRAPH_SHOW_STATES) {
624           state_name = debug_dump_get_element_state (GST_ELEMENT (element));
625         }
626         if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) {
627           param_name = debug_dump_get_object_params (G_OBJECT (element),
628               details, NULL);
629         }
630         /* elements */
631         g_string_append_printf (str, "%ssubgraph cluster_%s {\n", spc,
632             element_name);
633         g_string_append_printf (str, "%s  fontname=\"Bitstream Vera Sans\";\n",
634             spc);
635         g_string_append_printf (str, "%s  fontsize=\"8\";\n", spc);
636         g_string_append_printf (str, "%s  style=\"filled,rounded\";\n", spc);
637         g_string_append_printf (str, "%s  color=black;\n", spc);
638         g_string_append_printf (str, "%s  label=\"%s\\n%s%s%s\";\n", spc,
639             G_OBJECT_TYPE_NAME (element), GST_OBJECT_NAME (element),
640             (state_name ? state_name : ""), (param_name ? param_name : "")
641             );
642         if (state_name) {
643           g_free (state_name);
644           state_name = NULL;
645         }
646         if (param_name) {
647           g_free (param_name);
648           param_name = NULL;
649         }
650
651         src_pads = sink_pads = 0;
652         if ((pad_iter = gst_element_iterate_sink_pads (element))) {
653           gchar *cluster_name = g_strdup_printf ("%s_sink", element_name);
654           debug_dump_element_pads (pad_iter, pad, element, details, str,
655               indent + 1, &sink_pads, cluster_name, &sink_pad_name);
656           g_free (cluster_name);
657           gst_iterator_free (pad_iter);
658         }
659         if ((pad_iter = gst_element_iterate_src_pads (element))) {
660           gchar *cluster_name = g_strdup_printf ("%s_src", element_name);
661           debug_dump_element_pads (pad_iter, pad, element, details, str,
662               indent + 1, &src_pads, cluster_name, &src_pad_name);
663           g_free (cluster_name);
664           gst_iterator_free (pad_iter);
665         }
666         if (sink_pads && src_pads) {
667           /* add invisible link from first sink to first src pad */
668           g_string_append_printf (str,
669               "%s  %s_%s -> %s_%s [style=\"invis\"];\n",
670               spc, element_name, sink_pad_name, element_name, src_pad_name);
671         }
672         g_free (sink_pad_name);
673         g_free (src_pad_name);
674         g_free (element_name);
675         sink_pad_name = src_pad_name = NULL;
676         if (GST_IS_BIN (element)) {
677           g_string_append_printf (str, "%s  fillcolor=\"#ffffff\";\n", spc);
678           /* recurse */
679           debug_dump_element (GST_BIN (element), details, str, indent + 1);
680         } else {
681           if (src_pads && !sink_pads)
682             g_string_append_printf (str, "%s  fillcolor=\"#ffaaaa\";\n", spc);
683           else if (!src_pads && sink_pads)
684             g_string_append_printf (str, "%s  fillcolor=\"#aaaaff\";\n", spc);
685           else if (src_pads && sink_pads)
686             g_string_append_printf (str, "%s  fillcolor=\"#aaffaa\";\n", spc);
687           else
688             g_string_append_printf (str, "%s  fillcolor=\"#ffffff\";\n", spc);
689         }
690         g_string_append_printf (str, "%s}\n\n", spc);
691         if ((pad_iter = gst_element_iterate_pads (element))) {
692           pads_done = FALSE;
693           while (!pads_done) {
694             switch (gst_iterator_next (pad_iter, &item2)) {
695               case GST_ITERATOR_OK:
696                 pad = g_value_get_object (&item2);
697                 if (gst_pad_is_linked (pad)) {
698                   if (gst_pad_get_direction (pad) == GST_PAD_SRC) {
699                     debug_dump_element_pad_link (pad, element, details, str,
700                         indent);
701                   } else {
702                     GstPad *peer_pad = gst_pad_get_peer (pad);
703
704                     if (peer_pad) {
705                       if (!GST_IS_GHOST_PAD (peer_pad)
706                           && GST_IS_PROXY_PAD (peer_pad)) {
707                         debug_dump_element_pad_link (peer_pad, NULL, details,
708                             str, indent);
709                       }
710                       gst_object_unref (peer_pad);
711                     }
712                   }
713                 }
714                 g_value_reset (&item2);
715                 break;
716               case GST_ITERATOR_RESYNC:
717                 gst_iterator_resync (pad_iter);
718                 break;
719               case GST_ITERATOR_ERROR:
720               case GST_ITERATOR_DONE:
721                 pads_done = TRUE;
722                 break;
723             }
724           }
725           g_value_unset (&item2);
726           gst_iterator_free (pad_iter);
727         }
728         g_value_reset (&item);
729         break;
730       case GST_ITERATOR_RESYNC:
731         gst_iterator_resync (element_iter);
732         break;
733       case GST_ITERATOR_ERROR:
734       case GST_ITERATOR_DONE:
735         elements_done = TRUE;
736         break;
737     }
738   }
739
740   g_value_unset (&item);
741   gst_iterator_free (element_iter);
742 }
743
744 static void
745 debug_dump_header (GstBin * bin, GstDebugGraphDetails details, GString * str)
746 {
747   gchar *state_name = NULL;
748   gchar *param_name = NULL;
749
750   if (details & GST_DEBUG_GRAPH_SHOW_STATES) {
751     state_name = debug_dump_get_element_state (GST_ELEMENT (bin));
752   }
753   if (details & GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS) {
754     param_name = debug_dump_get_object_params (G_OBJECT (bin), details, NULL);
755   }
756
757   /* write header */
758   g_string_append_printf (str,
759       "digraph pipeline {\n"
760       "  rankdir=LR;\n"
761       "  fontname=\"sans\";\n"
762       "  fontsize=\"10\";\n"
763       "  labelloc=t;\n"
764       "  nodesep=.1;\n"
765       "  ranksep=.2;\n"
766       "  label=\"<%s>\\n%s%s%s\";\n"
767       "  node [style=\"filled,rounded\", shape=box, fontsize=\"9\", fontname=\"sans\", margin=\"0.0,0.0\"];\n"
768       "  edge [labelfontsize=\"6\", fontsize=\"9\", fontname=\"monospace\"];\n"
769       "  \n"
770       "  legend [\n"
771       "    pos=\"0,0!\",\n"
772       "    margin=\"0.05,0.05\",\n"
773       "    style=\"filled\",\n"
774       "    label=\"Legend\\lElement-States: [~] void-pending, [0] null, [-] ready, [=] paused, [>] playing\\lPad-Activation: [-] none, [>] push, [<] pull\\lPad-Flags: [b]locked, [f]lushing, [b]locking; upper-case is set\\lPad-Task: [T] has started task, [t] has paused task\\l\",\n"
775       "  ];"
776       "\n", G_OBJECT_TYPE_NAME (bin), GST_OBJECT_NAME (bin),
777       (state_name ? state_name : ""), (param_name ? param_name : "")
778       );
779
780   if (state_name)
781     g_free (state_name);
782   if (param_name)
783     g_free (param_name);
784 }
785
786 static void
787 debug_dump_footer (GString * str)
788 {
789   g_string_append_printf (str, "}\n");
790 }
791
792 /*
793  * gst_debug_bin_to_dot_data:
794  * @bin: the top-level pipeline that should be analyzed
795  *
796  * To aid debugging applications one can use this method to obtain the whole
797  * network of gstreamer elements that form the pipeline into an dot file.
798  * This data can be processed with graphviz to get an image.
799  *
800  * Returns: (transfer full): a string containing the pipeline in graphviz
801  * dot format.
802  */
803 gchar *
804 gst_debug_bin_to_dot_data (GstBin * bin, GstDebugGraphDetails details)
805 {
806   GString *str;
807
808   g_return_val_if_fail (GST_IS_BIN (bin), NULL);
809
810   str = g_string_new (NULL);
811
812   debug_dump_header (bin, details, str);
813   debug_dump_element (bin, details, str, 1);
814   debug_dump_footer (str);
815
816   return g_string_free (str, FALSE);
817 }
818
819 /*
820  * gst_debug_bin_to_dot_file:
821  * @bin: the top-level pipeline that should be analyzed
822  * @file_name: output base filename (e.g. "myplayer")
823  *
824  * To aid debugging applications one can use this method to write out the whole
825  * network of gstreamer elements that form the pipeline into an dot file.
826  * This file can be processed with graphviz to get an image.
827  * <informalexample><programlisting>
828  *  dot -Tpng -oimage.png graph_lowlevel.dot
829  * </programlisting></informalexample>
830  */
831 void
832 gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details,
833     const gchar * file_name)
834 {
835   gchar *full_file_name = NULL;
836   FILE *out;
837
838   g_return_if_fail (GST_IS_BIN (bin));
839
840   if (G_LIKELY (priv_gst_dump_dot_dir == NULL))
841     return;
842
843   if (!file_name) {
844     file_name = g_get_application_name ();
845     if (!file_name)
846       file_name = "unnamed";
847   }
848
849   full_file_name = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s.dot",
850       priv_gst_dump_dot_dir, file_name);
851
852   if ((out = fopen (full_file_name, "wb"))) {
853     gchar *buf;
854
855     buf = gst_debug_bin_to_dot_data (bin, details);
856     fputs (buf, out);
857
858     g_free (buf);
859     fclose (out);
860
861     GST_INFO ("wrote bin graph to : '%s'", full_file_name);
862   } else {
863     GST_WARNING ("Failed to open file '%s' for writing: %s", full_file_name,
864         g_strerror (errno));
865   }
866   g_free (full_file_name);
867 }
868
869 /*
870  * gst_debug_bin_to_dot_file_with_ts:
871  * @bin: the top-level pipeline that should be analyzed
872  * @file_name: output base filename (e.g. "myplayer")
873  *
874  * This works like gst_debug_bin_to_dot_file(), but adds the current timestamp
875  * to the filename, so that it can be used to take multiple snapshots.
876  */
877 void
878 gst_debug_bin_to_dot_file_with_ts (GstBin * bin,
879     GstDebugGraphDetails details, const gchar * file_name)
880 {
881   gchar *ts_file_name = NULL;
882   GstClockTime elapsed;
883
884   g_return_if_fail (GST_IS_BIN (bin));
885
886   if (!file_name) {
887     file_name = g_get_application_name ();
888     if (!file_name)
889       file_name = "unnamed";
890   }
891
892   /* add timestamp */
893   elapsed = GST_CLOCK_DIFF (_priv_gst_start_time, gst_util_get_timestamp ());
894
895   /* we don't use GST_TIME_FORMAT as such filenames would fail on some
896    * filesystems like fat */
897   ts_file_name =
898       g_strdup_printf ("%u.%02u.%02u.%09u-%s", GST_TIME_ARGS (elapsed),
899       file_name);
900
901   gst_debug_bin_to_dot_file (bin, details, ts_file_name);
902   g_free (ts_file_name);
903 }
904 #else /* !GST_DISABLE_GST_DEBUG */
905 #ifndef GST_REMOVE_DISABLED
906 void
907 gst_debug_bin_to_dot_file (GstBin * bin, GstDebugGraphDetails details,
908     const gchar * file_name)
909 {
910 }
911
912 void
913 gst_debug_bin_to_dot_file_with_ts (GstBin * bin, GstDebugGraphDetails details,
914     const gchar * file_name)
915 {
916 }
917 #endif /* GST_REMOVE_DISABLED */
918 #endif /* GST_DISABLE_GST_DEBUG */