tracers: eliminate var_args
[platform/upstream/gstreamer.git] / plugins / tracers / gstrusage.c
1 /* GStreamer
2  * Copyright (C) 2013 Stefan Sauer <ensonic@users.sf.net>
3  *
4  * gstrusage.c: tracing module that logs resource usage stats
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:gstrusage
23  * @short_description: log resource usage stats
24  *
25  * A tracing module that take rusage() snapshots and logs them. 
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #  include "config.h"
30 #endif
31
32 #include <unistd.h>
33 #include "gstrusage.h"
34
35 #ifdef HAVE_SYS_RESOURCE_H
36 #ifndef __USE_GNU
37 # define __USE_GNU              /* RUSAGE_THREAD */
38 #endif
39 #include <sys/resource.h>
40 #endif
41
42 GST_DEBUG_CATEGORY_STATIC (gst_rusage_debug);
43 #define GST_CAT_DEFAULT gst_rusage_debug
44
45 G_LOCK_DEFINE (_proc);
46
47 #define _do_init \
48     GST_DEBUG_CATEGORY_INIT (gst_rusage_debug, "rusage", 0, "rusage tracer");
49 #define gst_rusage_tracer_parent_class parent_class
50 G_DEFINE_TYPE_WITH_CODE (GstRUsageTracer, gst_rusage_tracer, GST_TYPE_TRACER,
51     _do_init);
52
53 /* remember x measurements per self->window */
54 #define WINDOW_SUBDIV 100
55
56 /* number of cpus to scale cpu-usage in threads */
57 static glong num_cpus = 1;
58
59 typedef struct
60 {
61   /* time spend in this thread */
62   GstClockTime tthread;
63   GstTraceValues *tvs_thread;
64 } GstThreadStats;
65
66 /* data helper */
67
68 static void
69 free_trace_value (gpointer data)
70 {
71   g_slice_free (GstTraceValue, data);
72 }
73
74 static GstTraceValues *
75 make_trace_values (GstClockTime window)
76 {
77   GstTraceValues *self = g_slice_new0 (GstTraceValues);
78   self->window = window;
79   g_queue_init (&self->values);
80   return self;
81 }
82
83 static void
84 free_trace_values (GstTraceValues * self)
85 {
86   g_queue_free_full (&self->values, free_trace_value);
87   g_slice_free (GstTraceValues, self);
88 }
89
90 static gboolean
91 update_trace_value (GstTraceValues * self, GstClockTime nts,
92     GstClockTime nval, GstClockTime * dts, GstClockTime * dval)
93 {
94   GstTraceValue *lv;
95   GstClockTimeDiff dt;
96   GstClockTime window = self->window;
97   GQueue *q = &self->values;
98   GList *node = q->tail;
99   gboolean ret = FALSE;
100
101
102   /* search from the tail of the queue for a good GstTraceValue */
103   while (node) {
104     lv = node->data;
105     dt = GST_CLOCK_DIFF (lv->ts, nts);
106     if (dt < window) {
107       break;
108     } else {
109       node = g_list_previous (node);
110     }
111   }
112
113   if (node) {
114     /* calculate the windowed value */
115     *dts = dt;
116     *dval = GST_CLOCK_DIFF (lv->val, nval);
117
118     /* drop all older measurements */
119     while (q->tail != node) {
120       free_trace_value (g_queue_pop_tail (q));
121     }
122     ret = TRUE;
123   } else {
124     *dts = nts;
125     *dval = nval;
126   }
127
128   /* don't push too many data items */
129   lv = q->head ? q->head->data : NULL;
130   if (!lv || (GST_CLOCK_DIFF (lv->ts, nts) > (window / WINDOW_SUBDIV))) {
131     /* push the new measurement */
132     lv = g_slice_new0 (GstTraceValue);
133     lv->ts = nts;
134     lv->val = nval;
135     g_queue_push_head (q, lv);
136   }
137   return ret;
138 }
139
140
141 static void
142 free_thread_stats (gpointer data)
143 {
144   free_trace_values (((GstThreadStats *) data)->tvs_thread);
145   g_slice_free (GstThreadStats, data);
146 }
147
148 static void
149 do_stats (GstTracer * obj, guint64 ts)
150 {
151   GstRUsageTracer *self = GST_RUSAGE_TRACER_CAST (obj);
152   GstThreadStats *stats;
153   gpointer thread_id = g_thread_self ();
154   guint avg_cpuload, cur_cpuload;
155   struct rusage ru;
156   GstClockTime tproc = G_GUINT64_CONSTANT (0);
157   GstClockTime tthread = G_GUINT64_CONSTANT (0);
158   GstClockTime dts, dtproc;
159
160 #ifdef HAVE_CLOCK_GETTIME
161   {
162     struct timespec now;
163
164     if (!clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &now)) {
165       tproc = GST_TIMESPEC_TO_TIME (now);
166     } else {
167       GST_WARNING_OBJECT (obj,
168           "clock_gettime (CLOCK_PROCESS_CPUTIME_ID,...) failed: %s",
169           g_strerror (errno));
170       getrusage (RUSAGE_SELF, &ru);
171       tproc =
172           GST_TIMEVAL_TO_TIME (ru.ru_utime) + GST_TIMEVAL_TO_TIME (ru.ru_stime);
173     }
174
175     /* cpu time per thread */
176     if (!clock_gettime (CLOCK_THREAD_CPUTIME_ID, &now)) {
177       tthread = GST_TIMESPEC_TO_TIME (now);
178     } else {
179       GST_WARNING_OBJECT (obj,
180           "clock_gettime (CLOCK_THREAD_CPUTIME_ID,...) failed: %s",
181           g_strerror (errno));
182       getrusage (RUSAGE_THREAD, &ru);
183       tthread =
184           GST_TIMEVAL_TO_TIME (ru.ru_utime) + GST_TIMEVAL_TO_TIME (ru.ru_stime);
185     }
186   }
187 #else
188   getrusage (RUSAGE_SELF, &ru);
189   tproc = GST_TIMEVAL_TO_TIME (ru.ru_utime) + GST_TIMEVAL_TO_TIME (ru.ru_stime);
190   getrusage (RUSAGE_THREAD, &ru);
191   tthread =
192       GST_TIMEVAL_TO_TIME (ru.ru_utime) + GST_TIMEVAL_TO_TIME (ru.ru_stime);
193 #endif
194   /* get stats record for current thread */
195   if (!(stats = g_hash_table_lookup (self->threads, thread_id))) {
196     stats = g_slice_new0 (GstThreadStats);
197     stats->tvs_thread = make_trace_values (GST_SECOND);
198     g_hash_table_insert (self->threads, thread_id, stats);
199   }
200   stats->tthread = tthread;
201
202   /* Calibrate ts for the process and main thread. For tthread[main] and tproc
203    * the time is larger than ts, as our base-ts is taken after the process has
204    * started.
205    */
206   if (G_UNLIKELY (thread_id == self->main_thread_id)) {
207     self->main_thread_id = NULL;
208     /* when the registry gets updated, the tproc is less than the debug time ? */
209     /* TODO(ensonic): we still see cases where tproc overtakes ts, especially
210      * when with sync=false, can this be due to multiple cores in use? */
211     if (tproc > ts) {
212       self->tproc_base = tproc - ts;
213       GST_DEBUG ("rusage: calibrating by %" G_GUINT64_FORMAT ", thread: %"
214           G_GUINT64_FORMAT ", proc: %" G_GUINT64_FORMAT,
215           self->tproc_base, stats->tthread, tproc);
216       stats->tthread -= self->tproc_base;
217     }
218   }
219   /* we always need to correct proc time */
220   tproc -= self->tproc_base;
221
222   /* FIXME: how can we take cpu-frequency scaling into account?
223    * - looking at /sys/devices/system/cpu/cpu0/cpufreq/
224    *   scale_factor=scaling_max_freq/scaling_cur_freq
225    * - as a workaround we can switch it via /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
226    *   cpufreq-selector -g performance
227    *   cpufreq-selector -g ondemand
228    */
229   /* *INDENT-OFF* */
230   avg_cpuload = (guint) gst_util_uint64_scale (stats->tthread,
231       G_GINT64_CONSTANT (1000), ts);
232   update_trace_value (stats->tvs_thread, ts, stats->tthread, &dts, &dtproc);
233   cur_cpuload = (guint) gst_util_uint64_scale (dtproc,
234       G_GINT64_CONSTANT (1000), dts);
235   gst_tracer_log_trace (gst_structure_new ("thread-rusage", 
236       "ts", G_TYPE_UINT64, ts, 
237       "thread-id", G_TYPE_UINT, GPOINTER_TO_UINT (thread_id), 
238       "average-cpuload", G_TYPE_UINT, MIN (avg_cpuload, 1000),
239       "current-cpuload", G_TYPE_UINT, MIN (cur_cpuload, 1000),
240       "time", G_TYPE_UINT64, stats->tthread,
241       NULL));
242
243   avg_cpuload = (guint) gst_util_uint64_scale (tproc / num_cpus,
244       G_GINT64_CONSTANT (1000), ts);
245   G_LOCK (_proc);
246   update_trace_value (self->tvs_proc, ts, tproc, &dts, &dtproc);
247   G_UNLOCK (_proc);
248   cur_cpuload = (guint) gst_util_uint64_scale (dtproc / num_cpus,
249       G_GINT64_CONSTANT (1000), dts);
250   gst_tracer_log_trace (gst_structure_new ("proc-rusage", 
251       "ts", G_TYPE_UINT64, ts, 
252       "average-cpuload", G_TYPE_UINT, MIN (avg_cpuload, 1000),
253       "current-cpuload", G_TYPE_UINT, MIN (cur_cpuload, 1000),
254       "time", G_TYPE_UINT64, tproc,
255       NULL));
256   /* *INDENT-ON* */
257 }
258
259 /* tracer class */
260
261 static void
262 gst_rusage_tracer_finalize (GObject * obj)
263 {
264   GstRUsageTracer *self = GST_RUSAGE_TRACER (obj);
265
266   g_hash_table_destroy (self->threads);
267   free_trace_values (self->tvs_proc);
268
269   G_OBJECT_CLASS (parent_class)->finalize (obj);
270 }
271
272 static void
273 gst_rusage_tracer_class_init (GstRUsageTracerClass * klass)
274 {
275   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
276
277   gobject_class->finalize = gst_rusage_tracer_finalize;
278
279   if ((num_cpus = sysconf (_SC_NPROCESSORS_ONLN)) == -1) {
280     GST_WARNING ("failed to get number of cpus online");
281     if ((num_cpus = sysconf (_SC_NPROCESSORS_CONF)) == -1) {
282       GST_WARNING ("failed to get number of cpus, assuming 1");
283       num_cpus = 1;
284     }
285   }
286   GST_DEBUG ("rusage: num_cpus=%ld", num_cpus);
287
288   /* announce trace formats */
289   /* *INDENT-OFF* */
290   gst_tracer_log_trace (gst_structure_new ("thread-rusage.class",
291       "thread-id", GST_TYPE_STRUCTURE, gst_structure_new ("scope",
292           "related-to", G_TYPE_STRING, "thread",  /* TODO: use genum */
293           NULL),
294       "average-cpuload", GST_TYPE_STRUCTURE, gst_structure_new ("value",
295           "type", G_TYPE_GTYPE, G_TYPE_UINT,
296           "description", G_TYPE_STRING, "average cpu usage per thread in ‰",
297           "flags", G_TYPE_STRING, "aggregated",  /* TODO: use gflags */ 
298           "min", G_TYPE_UINT, 0, 
299           "max", G_TYPE_UINT, 1000,
300           NULL),
301       "current-cpuload", GST_TYPE_STRUCTURE, gst_structure_new ("value",
302           "type", G_TYPE_GTYPE, G_TYPE_UINT,
303           "description", G_TYPE_STRING, "current cpu usage per thread in ‰",
304           "flags", G_TYPE_STRING, "windowed",  /* TODO: use gflags */ 
305           "min", G_TYPE_UINT, 0, 
306           "max", G_TYPE_UINT, 1000,
307           NULL),
308       "time", GST_TYPE_STRUCTURE, gst_structure_new ("value",
309           "type", G_TYPE_GTYPE, G_TYPE_UINT64,
310           "description", G_TYPE_STRING, "time spent in thread in ns",
311           "flags", G_TYPE_STRING, "aggregated",  /* TODO: use gflags */ 
312           "min", G_TYPE_UINT64, G_GUINT64_CONSTANT (0),
313           "max", G_TYPE_UINT64, G_MAXUINT64,
314           NULL),
315       NULL));
316   gst_tracer_log_trace (gst_structure_new ("proc-rusage.class",
317       "thread-id", GST_TYPE_STRUCTURE, gst_structure_new ("scope",
318           "related-to", G_TYPE_STRING, "process",  /* TODO: use genum */
319           NULL),
320       "average-cpuload", GST_TYPE_STRUCTURE, gst_structure_new ("value",
321           "type", G_TYPE_GTYPE, G_TYPE_UINT,
322           "description", G_TYPE_STRING, "average cpu usage per process in ‰",
323           "flags", G_TYPE_STRING, "aggregated",  /* TODO: use gflags */ 
324           "min", G_TYPE_UINT, 0, 
325           "max", G_TYPE_UINT, 1000,
326           NULL),
327       "current-cpuload", GST_TYPE_STRUCTURE, gst_structure_new ("value",
328           "type", G_TYPE_GTYPE, G_TYPE_UINT,
329           "description", G_TYPE_STRING, "current cpu usage per process in ‰",
330           "flags", G_TYPE_STRING, "windowed",  /* TODO: use gflags */ 
331           "min", G_TYPE_UINT, 0, 
332           "max", G_TYPE_UINT, 1000,
333           NULL),
334       "time", GST_TYPE_STRUCTURE, gst_structure_new ("value",
335           "type", G_TYPE_GTYPE, G_TYPE_UINT64,
336           "description", G_TYPE_STRING, "time spent in process in ns",
337           "flags", G_TYPE_STRING, "aggregated",  /* TODO: use gflags */ 
338           "min", G_TYPE_UINT64, G_GUINT64_CONSTANT (0),
339           "max", G_TYPE_UINT64, G_MAXUINT64,
340           NULL),
341       NULL));
342   /* *INDENT-ON* */
343 }
344
345 static void
346 gst_rusage_tracer_init (GstRUsageTracer * self)
347 {
348   GstTracer *tracer = GST_TRACER (self);
349
350   gst_tracer_register_hook_id (tracer, 0, G_CALLBACK (do_stats));
351
352   self->threads = g_hash_table_new_full (NULL, NULL, NULL, free_thread_stats);
353   self->tvs_proc = make_trace_values (GST_SECOND);
354   self->main_thread_id = g_thread_self ();
355
356   GST_DEBUG ("rusage: main thread=%p", self->main_thread_id);
357 }