• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:tracer-rusage
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 static GstTracerRecord *tr_proc, *tr_thread;
60 
61 typedef struct
62 {
63   /* time spent in this thread */
64   GstClockTime tthread;
65   GstTraceValues *tvs_thread;
66 } GstThreadStats;
67 
68 static void free_thread_stats (gpointer data);
69 
70 static GPrivate thread_stats_key = G_PRIVATE_INIT (free_thread_stats);
71 
72 /* data helper */
73 
74 static void
free_trace_value(gpointer data)75 free_trace_value (gpointer data)
76 {
77   g_slice_free (GstTraceValue, data);
78 }
79 
80 static GstTraceValues *
make_trace_values(GstClockTime window)81 make_trace_values (GstClockTime window)
82 {
83   GstTraceValues *self = g_slice_new0 (GstTraceValues);
84   self->window = window;
85   g_queue_init (&self->values);
86   return self;
87 }
88 
89 static void
free_trace_values(GstTraceValues * self)90 free_trace_values (GstTraceValues * self)
91 {
92   g_queue_foreach (&self->values, (GFunc) free_trace_value, NULL);
93   g_queue_clear (&self->values);
94   g_slice_free (GstTraceValues, self);
95 }
96 
97 static gboolean
update_trace_value(GstTraceValues * self,GstClockTime nts,GstClockTime nval,GstClockTime * dts,GstClockTime * dval)98 update_trace_value (GstTraceValues * self, GstClockTime nts,
99     GstClockTime nval, GstClockTime * dts, GstClockTime * dval)
100 {
101   GstTraceValue *lv;
102   GstClockTimeDiff dt;
103   GstClockTime window = self->window;
104   GQueue *q = &self->values;
105   GList *node = q->tail;
106   gboolean ret = FALSE;
107 
108 
109   /* search from the tail of the queue for a good GstTraceValue */
110   while (node) {
111     lv = node->data;
112     dt = GST_CLOCK_DIFF (lv->ts, nts);
113     if (dt < window) {
114       break;
115     } else {
116       node = g_list_previous (node);
117     }
118   }
119 
120   if (node) {
121     /* calculate the windowed value */
122     *dts = dt;
123     *dval = GST_CLOCK_DIFF (lv->val, nval);
124 
125     /* drop all older measurements */
126     while (q->tail != node) {
127       free_trace_value (g_queue_pop_tail (q));
128     }
129     ret = TRUE;
130   } else {
131     *dts = nts;
132     *dval = nval;
133   }
134 
135   /* don't push too many data items */
136   lv = q->head ? q->head->data : NULL;
137   if (!lv || (GST_CLOCK_DIFF (lv->ts, nts) > (window / WINDOW_SUBDIV))) {
138     /* push the new measurement */
139     lv = g_slice_new0 (GstTraceValue);
140     lv->ts = nts;
141     lv->val = nval;
142     g_queue_push_head (q, lv);
143   }
144   return ret;
145 }
146 
147 static void
free_thread_stats(gpointer data)148 free_thread_stats (gpointer data)
149 {
150   GstThreadStats *stats = data;
151 
152   free_trace_values (stats->tvs_thread);
153   g_free (stats);
154 }
155 
156 static void
do_stats(GstTracer * obj,guint64 ts)157 do_stats (GstTracer * obj, guint64 ts)
158 {
159   GstRUsageTracer *self = GST_RUSAGE_TRACER_CAST (obj);
160   GstThreadStats *stats;
161   gpointer thread_id = g_thread_self ();
162   guint avg_cpuload, cur_cpuload;
163   struct rusage ru;
164   GstClockTime tproc = G_GUINT64_CONSTANT (0);
165   GstClockTime tthread = G_GUINT64_CONSTANT (0);
166   GstClockTime dts, dtproc;
167 
168 #ifdef HAVE_CLOCK_GETTIME
169   {
170     struct timespec now;
171 
172     if (!clock_gettime (CLOCK_PROCESS_CPUTIME_ID, &now)) {
173       tproc = GST_TIMESPEC_TO_TIME (now);
174     } else {
175       GST_WARNING_OBJECT (obj,
176           "clock_gettime (CLOCK_PROCESS_CPUTIME_ID,...) failed: %s",
177           g_strerror (errno));
178       getrusage (RUSAGE_SELF, &ru);
179       tproc =
180           GST_TIMEVAL_TO_TIME (ru.ru_utime) + GST_TIMEVAL_TO_TIME (ru.ru_stime);
181     }
182 
183     /* cpu time per thread */
184     if (!clock_gettime (CLOCK_THREAD_CPUTIME_ID, &now)) {
185       tthread = GST_TIMESPEC_TO_TIME (now);
186     } else {
187       GST_WARNING_OBJECT (obj,
188           "clock_gettime (CLOCK_THREAD_CPUTIME_ID,...) failed: %s",
189           g_strerror (errno));
190 #ifdef RUSAGE_THREAD
191       getrusage (RUSAGE_THREAD, &ru);
192       tthread =
193           GST_TIMEVAL_TO_TIME (ru.ru_utime) + GST_TIMEVAL_TO_TIME (ru.ru_stime);
194 #endif
195     }
196   }
197 #else
198   getrusage (RUSAGE_SELF, &ru);
199   tproc = GST_TIMEVAL_TO_TIME (ru.ru_utime) + GST_TIMEVAL_TO_TIME (ru.ru_stime);
200 #ifdef RUSAGE_THREAD
201   getrusage (RUSAGE_THREAD, &ru);
202   tthread =
203       GST_TIMEVAL_TO_TIME (ru.ru_utime) + GST_TIMEVAL_TO_TIME (ru.ru_stime);
204 #endif
205 #endif
206   /* get stats record for current thread */
207   if (!(stats = g_private_get (&thread_stats_key))) {
208     stats = g_new0 (GstThreadStats, 1);
209     stats->tvs_thread = make_trace_values (GST_SECOND);
210     g_private_set (&thread_stats_key, stats);
211   }
212   stats->tthread = tthread;
213 
214   /* Calibrate ts for the process and main thread. For tthread[main] and tproc
215    * the time is larger than ts, as our base-ts is taken after the process has
216    * started.
217    */
218   if (G_UNLIKELY (thread_id == self->main_thread_id)) {
219     self->main_thread_id = NULL;
220     /* when the registry gets updated, the tproc is less than the debug time ? */
221     /* TODO(ensonic): we still see cases where tproc overtakes ts, especially
222      * when with sync=false, can this be due to multiple cores in use? */
223     if (tproc > ts) {
224       self->tproc_base = tproc - ts;
225       GST_DEBUG ("rusage: calibrating by %" G_GUINT64_FORMAT ", thread: %"
226           G_GUINT64_FORMAT ", proc: %" G_GUINT64_FORMAT,
227           self->tproc_base, stats->tthread, tproc);
228       stats->tthread -= self->tproc_base;
229     }
230   }
231   /* we always need to correct proc time */
232   tproc -= self->tproc_base;
233 
234   /* FIXME: how can we take cpu-frequency scaling into account?
235    * - looking at /sys/devices/system/cpu/cpu0/cpufreq/
236    *   scale_factor=scaling_max_freq/scaling_cur_freq
237    * - as a workaround we can switch it via /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
238    *   cpufreq-selector -g performance
239    *   cpufreq-selector -g ondemand
240    */
241   /* *INDENT-OFF* */
242   avg_cpuload = (guint) gst_util_uint64_scale (stats->tthread,
243       G_GINT64_CONSTANT (1000), ts);
244   update_trace_value (stats->tvs_thread, ts, stats->tthread, &dts, &dtproc);
245   cur_cpuload = (guint) gst_util_uint64_scale (dtproc,
246       G_GINT64_CONSTANT (1000), dts);
247   gst_tracer_record_log (tr_thread, (guint64) (guintptr) thread_id, ts,
248       MIN (avg_cpuload, 1000), MIN (cur_cpuload, 1000), stats->tthread);
249 
250   avg_cpuload = (guint) gst_util_uint64_scale (tproc / num_cpus,
251       G_GINT64_CONSTANT (1000), ts);
252   G_LOCK (_proc);
253   update_trace_value (self->tvs_proc, ts, tproc, &dts, &dtproc);
254   G_UNLOCK (_proc);
255   cur_cpuload = (guint) gst_util_uint64_scale (dtproc / num_cpus,
256       G_GINT64_CONSTANT (1000), dts);
257   gst_tracer_record_log (tr_proc, (guint64) getpid (), ts,
258       MIN (avg_cpuload, 1000), MIN (cur_cpuload, 1000), tproc);
259   /* *INDENT-ON* */
260 }
261 
262 /* tracer class */
263 
264 static void
gst_rusage_tracer_constructed(GObject * object)265 gst_rusage_tracer_constructed (GObject * object)
266 {
267   GstRUsageTracer *self = GST_RUSAGE_TRACER (object);
268   gchar *params, *tmp;
269   const gchar *name;
270   GstStructure *params_struct = NULL;
271 
272   g_object_get (self, "params", &params, NULL);
273 
274   if (!params)
275     return;
276 
277   tmp = g_strdup_printf ("rusage,%s", params);
278   g_free (params);
279   params_struct = gst_structure_from_string (tmp, NULL);
280   g_free (tmp);
281   if (!params_struct)
282     return;
283 
284   /* Set the name if assigned */
285   name = gst_structure_get_string (params_struct, "name");
286   if (name)
287     gst_object_set_name (GST_OBJECT (self), name);
288   gst_structure_free (params_struct);
289 }
290 
291 static void
gst_rusage_tracer_finalize(GObject * obj)292 gst_rusage_tracer_finalize (GObject * obj)
293 {
294   GstRUsageTracer *self = GST_RUSAGE_TRACER (obj);
295 
296   free_trace_values (self->tvs_proc);
297 
298   G_OBJECT_CLASS (parent_class)->finalize (obj);
299 }
300 
301 static void
gst_rusage_tracer_class_init(GstRUsageTracerClass * klass)302 gst_rusage_tracer_class_init (GstRUsageTracerClass * klass)
303 {
304   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
305 
306   gobject_class->constructed = gst_rusage_tracer_constructed;
307   gobject_class->finalize = gst_rusage_tracer_finalize;
308 
309   if ((num_cpus = sysconf (_SC_NPROCESSORS_ONLN)) == -1) {
310     GST_WARNING ("failed to get number of cpus online");
311     if ((num_cpus = sysconf (_SC_NPROCESSORS_CONF)) == -1) {
312       GST_WARNING ("failed to get number of cpus, assuming 1");
313       num_cpus = 1;
314     }
315   }
316   GST_DEBUG ("rusage: num_cpus=%ld", num_cpus);
317 
318   /* announce trace formats */
319   /* *INDENT-OFF* */
320   tr_thread = gst_tracer_record_new ("thread-rusage.class",
321       "thread-id", GST_TYPE_STRUCTURE, gst_structure_new ("scope",
322           "type", G_TYPE_GTYPE, G_TYPE_UINT64,
323           "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_THREAD,
324           NULL),
325       "ts", GST_TYPE_STRUCTURE, gst_structure_new ("value",
326           "type", G_TYPE_GTYPE, G_TYPE_UINT64,
327           "description", G_TYPE_STRING, "event ts",
328           NULL),
329       "average-cpuload", GST_TYPE_STRUCTURE, gst_structure_new ("value",
330           "type", G_TYPE_GTYPE, G_TYPE_UINT,
331           "description", G_TYPE_STRING, "average cpu usage per thread in ‰",
332           "flags", GST_TYPE_TRACER_VALUE_FLAGS, GST_TRACER_VALUE_FLAGS_AGGREGATED,
333           "min", G_TYPE_UINT, 0,
334           "max", G_TYPE_UINT, 1000,
335           NULL),
336       "current-cpuload", GST_TYPE_STRUCTURE, gst_structure_new ("value",
337           "type", G_TYPE_GTYPE, G_TYPE_UINT,
338           "description", G_TYPE_STRING, "current cpu usage per thread in ‰",
339           "min", G_TYPE_UINT, 0,
340           "max", G_TYPE_UINT, 1000,
341           NULL),
342       "time", GST_TYPE_STRUCTURE, gst_structure_new ("value",
343           "type", G_TYPE_GTYPE, G_TYPE_UINT64,
344           "description", G_TYPE_STRING, "time spent in thread in ns",
345           "flags", GST_TYPE_TRACER_VALUE_FLAGS, GST_TRACER_VALUE_FLAGS_AGGREGATED,
346           "min", G_TYPE_UINT64, G_GUINT64_CONSTANT (0),
347           "max", G_TYPE_UINT64, G_MAXUINT64,
348           NULL),
349       NULL);
350   tr_proc = gst_tracer_record_new ("proc-rusage.class",
351       "process-id", GST_TYPE_STRUCTURE, gst_structure_new ("scope",
352           "type", G_TYPE_GTYPE, G_TYPE_UINT64,
353           "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS,
354           NULL),
355       "ts", GST_TYPE_STRUCTURE, gst_structure_new ("value",
356           "type", G_TYPE_GTYPE, G_TYPE_UINT64,
357           "description", G_TYPE_STRING, "event ts",
358           NULL),
359       "average-cpuload", GST_TYPE_STRUCTURE, gst_structure_new ("value",
360           "type", G_TYPE_GTYPE, G_TYPE_UINT,
361           "description", G_TYPE_STRING, "average cpu usage per process in ‰",
362           "flags", GST_TYPE_TRACER_VALUE_FLAGS, GST_TRACER_VALUE_FLAGS_AGGREGATED,
363           "min", G_TYPE_UINT, 0,
364           "max", G_TYPE_UINT, 1000,
365           NULL),
366       "current-cpuload", GST_TYPE_STRUCTURE, gst_structure_new ("value",
367           "type", G_TYPE_GTYPE, G_TYPE_UINT,
368           "description", G_TYPE_STRING, "current cpu usage per process in ‰",
369           "min", G_TYPE_UINT, 0,
370           "max", G_TYPE_UINT, 1000,
371           NULL),
372       "time", GST_TYPE_STRUCTURE, gst_structure_new ("value",
373           "type", G_TYPE_GTYPE, G_TYPE_UINT64,
374           "description", G_TYPE_STRING, "time spent in process in ns",
375           "flags", GST_TYPE_TRACER_VALUE_FLAGS, GST_TRACER_VALUE_FLAGS_AGGREGATED,
376           "min", G_TYPE_UINT64, G_GUINT64_CONSTANT (0),
377           "max", G_TYPE_UINT64, G_MAXUINT64,
378           NULL),
379       NULL);
380   /* *INDENT-ON* */
381 
382   GST_OBJECT_FLAG_SET (tr_thread, GST_OBJECT_FLAG_MAY_BE_LEAKED);
383   GST_OBJECT_FLAG_SET (tr_proc, GST_OBJECT_FLAG_MAY_BE_LEAKED);
384 }
385 
386 static void
gst_rusage_tracer_init(GstRUsageTracer * self)387 gst_rusage_tracer_init (GstRUsageTracer * self)
388 {
389   GstTracer *tracer = GST_TRACER (self);
390   gint i;
391   const gchar *hooks[] = { "pad-push-pre", "pad-push-post", "pad-push-list-pre",
392     "pad-push-list-post", "pad-pull-range-pre", "pad-pull-range-post",
393     "pad-push-event-pre", "pad-push-event-post", "pad-query-pre",
394     "pad-query-post", "element-post-message-pre", "element-post-message-post",
395     "element-query-pre", "element-query-post", "element-new", "element-add-pad",
396     "element-remove-pad", "element-change-state-pre",
397     "element-change-state-post", "bin-add-pre", "bin-add-post",
398     "bin-remove-pre", "bin-remove-post", "pad-link-pre", "pad-link-post",
399     "pad-unlink-pre", "pad-unlink-post"
400   };
401 
402   for (i = 0; i < G_N_ELEMENTS (hooks); i++) {
403     gst_tracing_register_hook (tracer, hooks[i], G_CALLBACK (do_stats));
404   }
405 
406   self->tvs_proc = make_trace_values (GST_SECOND);
407   self->main_thread_id = g_thread_self ();
408 
409   GST_DEBUG ("rusage: main thread=%p", self->main_thread_id);
410 }
411