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", ¶ms, 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