1 #include <pthread.h>
2 #include <time.h>
3 #include <unistd.h>
4 
5 #include "common.h"
6 #include "global.h"
7 #include "misc/linked_list.h"
8 #include "misc/node.h"
9 #include "msg.h"
10 #include "options/m_option.h"
11 #include "osdep/atomic.h"
12 #include "osdep/timer.h"
13 #include "stats.h"
14 
15 struct stats_base {
16     struct mpv_global *global;
17 
18     atomic_bool active;
19 
20     pthread_mutex_t lock;
21 
22     struct {
23         struct stats_ctx *head, *tail;
24     } list;
25 
26     struct stat_entry **entries;
27     int num_entries;
28 
29     int64_t last_time;
30 };
31 
32 struct stats_ctx {
33     struct stats_base *base;
34     const char *prefix;
35 
36     struct {
37         struct stats_ctx *prev, *next;
38     } list;
39 
40     struct stat_entry **entries;
41     int num_entries;
42 };
43 
44 enum val_type {
45     VAL_UNSET = 0,
46     VAL_STATIC,
47     VAL_STATIC_SIZE,
48     VAL_INC,
49     VAL_TIME,
50     VAL_THREAD_CPU_TIME,
51 };
52 
53 struct stat_entry {
54     char name[32];
55     const char *full_name; // including stats_ctx.prefix
56 
57     enum val_type type;
58     double val_d;
59     int64_t val_rt;
60     int64_t val_th;
61     int64_t time_start_us;
62     int64_t cpu_start_ns;
63     pthread_t thread;
64 };
65 
66 #define IS_ACTIVE(ctx) \
67     (atomic_load_explicit(&(ctx)->base->active, memory_order_relaxed))
68 
69 // Overflows only after I'm dead.
get_thread_cpu_time_ns(pthread_t thread)70 static int64_t get_thread_cpu_time_ns(pthread_t thread)
71 {
72 #if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && defined(_POSIX_THREAD_CPUTIME)
73     clockid_t id;
74     struct timespec tv;
75     if (pthread_getcpuclockid(thread, &id) == 0 &&
76         clock_gettime(id, &tv) == 0)
77     {
78         return tv.tv_sec * (1000LL * 1000LL * 1000LL) + tv.tv_nsec;
79     }
80 #endif
81     return 0;
82 }
83 
stats_destroy(void * p)84 static void stats_destroy(void *p)
85 {
86     struct stats_base *stats = p;
87 
88     // All entries must have been destroyed before this.
89     assert(!stats->list.head);
90 
91     pthread_mutex_destroy(&stats->lock);
92 }
93 
stats_global_init(struct mpv_global * global)94 void stats_global_init(struct mpv_global *global)
95 {
96     assert(!global->stats);
97     struct stats_base *stats = talloc_zero(global, struct stats_base);
98     ta_set_destructor(stats, stats_destroy);
99     pthread_mutex_init(&stats->lock, NULL);
100 
101     global->stats = stats;
102     stats->global = global;
103 }
104 
add_stat(struct mpv_node * list,struct stat_entry * e,const char * suffix,double num_val,char * text)105 static void add_stat(struct mpv_node *list, struct stat_entry *e,
106                      const char *suffix, double num_val, char *text)
107 {
108     struct mpv_node *ne = node_array_add(list, MPV_FORMAT_NODE_MAP);
109 
110     node_map_add_string(ne, "name", suffix ?
111         mp_tprintf(80, "%s/%s", e->full_name, suffix) : e->full_name);
112     node_map_add_double(ne, "value", num_val);
113     if (text)
114         node_map_add_string(ne, "text", text);
115 }
116 
cmp_entry(const void * p1,const void * p2)117 static int cmp_entry(const void *p1, const void *p2)
118 {
119     struct stat_entry **e1 = (void *)p1;
120     struct stat_entry **e2 = (void *)p2;
121     return strcmp((*e1)->full_name, (*e2)->full_name);
122 }
123 
stats_global_query(struct mpv_global * global,struct mpv_node * out)124 void stats_global_query(struct mpv_global *global, struct mpv_node *out)
125 {
126     struct stats_base *stats = global->stats;
127     assert(stats);
128 
129     pthread_mutex_lock(&stats->lock);
130 
131     atomic_store(&stats->active, true);
132 
133     if (!stats->num_entries) {
134         for (struct stats_ctx *ctx = stats->list.head; ctx; ctx = ctx->list.next)
135         {
136             for (int n = 0; n < ctx->num_entries; n++) {
137                 MP_TARRAY_APPEND(stats, stats->entries, stats->num_entries,
138                                  ctx->entries[n]);
139             }
140         }
141         if (stats->num_entries) {
142             qsort(stats->entries, stats->num_entries, sizeof(stats->entries[0]),
143                   cmp_entry);
144         }
145     }
146 
147     node_init(out, MPV_FORMAT_NODE_ARRAY, NULL);
148 
149     int64_t now = mp_time_us();
150     if (stats->last_time) {
151         double t_ms = (now - stats->last_time) / 1e3;
152         struct mpv_node *ne = node_array_add(out, MPV_FORMAT_NODE_MAP);
153         node_map_add_string(ne, "name", "poll-time");
154         node_map_add_double(ne, "value", t_ms);
155         node_map_add_string(ne, "text", mp_tprintf(80, "%.2f ms", t_ms));
156 
157         // Very dirty way to reset everything if the stats.lua page was probably
158         // closed. Not enough energy left for clean solution. Fuck it.
159         if (t_ms > 2000) {
160             for (int n = 0; n < stats->num_entries; n++) {
161                 struct stat_entry *e = stats->entries[n];
162 
163                 e->cpu_start_ns = 0;
164                 e->val_rt = e->val_th = 0;
165                 if (e->type != VAL_THREAD_CPU_TIME)
166                     e->type = 0;
167             }
168         }
169     }
170     stats->last_time = now;
171 
172     for (int n = 0; n < stats->num_entries; n++) {
173         struct stat_entry *e = stats->entries[n];
174 
175         switch (e->type) {
176         case VAL_STATIC:
177             add_stat(out, e, NULL, e->val_d, NULL);
178             break;
179         case VAL_STATIC_SIZE: {
180             char *s = format_file_size(e->val_d);
181             add_stat(out, e, NULL, e->val_d, s);
182             talloc_free(s);
183             break;
184         }
185         case VAL_INC:
186             add_stat(out, e, NULL, e->val_d, NULL);
187             e->val_d = 0;
188             break;
189         case VAL_TIME: {
190             double t_cpu = e->val_th / 1e6;
191             add_stat(out, e, "cpu", t_cpu, mp_tprintf(80, "%.2f ms", t_cpu));
192             double t_rt = e->val_rt / 1e3;
193             add_stat(out, e, "time", t_rt, mp_tprintf(80, "%.2f ms", t_rt));
194             e->val_rt = e->val_th = 0;
195             break;
196         }
197         case VAL_THREAD_CPU_TIME: {
198             int64_t t = get_thread_cpu_time_ns(e->thread);
199             if (!e->cpu_start_ns)
200                 e->cpu_start_ns = t;
201             double t_msec = (t - e->cpu_start_ns) / 1e6;
202             add_stat(out, e, NULL, t_msec, mp_tprintf(80, "%.2f ms", t_msec));
203             e->cpu_start_ns = t;
204             break;
205         }
206         default: ;
207         }
208     }
209 
210     pthread_mutex_unlock(&stats->lock);
211 }
212 
stats_ctx_destroy(void * p)213 static void stats_ctx_destroy(void *p)
214 {
215     struct stats_ctx *ctx = p;
216 
217     pthread_mutex_lock(&ctx->base->lock);
218     LL_REMOVE(list, &ctx->base->list, ctx);
219     ctx->base->num_entries = 0; // invalidate
220     pthread_mutex_unlock(&ctx->base->lock);
221 }
222 
stats_ctx_create(void * ta_parent,struct mpv_global * global,const char * prefix)223 struct stats_ctx *stats_ctx_create(void *ta_parent, struct mpv_global *global,
224                                    const char *prefix)
225 {
226     struct stats_base *base = global->stats;
227     assert(base);
228 
229     struct stats_ctx *ctx = talloc_zero(ta_parent, struct stats_ctx);
230     ctx->base = base;
231     ctx->prefix = talloc_strdup(ctx, prefix);
232     ta_set_destructor(ctx, stats_ctx_destroy);
233 
234     pthread_mutex_lock(&base->lock);
235     LL_APPEND(list, &base->list, ctx);
236     base->num_entries = 0; // invalidate
237     pthread_mutex_unlock(&base->lock);
238 
239     return ctx;
240 }
241 
find_entry(struct stats_ctx * ctx,const char * name)242 static struct stat_entry *find_entry(struct stats_ctx *ctx, const char *name)
243 {
244     for (int n = 0; n < ctx->num_entries; n++) {
245         if (strcmp(ctx->entries[n]->name, name) == 0)
246             return ctx->entries[n];
247     }
248 
249     struct stat_entry *e = talloc_zero(ctx, struct stat_entry);
250     snprintf(e->name, sizeof(e->name), "%s", name);
251     assert(strcmp(e->name, name) == 0); // make e->name larger and don't complain
252 
253     e->full_name = talloc_asprintf(e, "%s/%s", ctx->prefix, e->name);
254 
255     MP_TARRAY_APPEND(ctx, ctx->entries, ctx->num_entries, e);
256     ctx->base->num_entries = 0; // invalidate
257 
258     return e;
259 }
260 
static_value(struct stats_ctx * ctx,const char * name,double val,enum val_type type)261 static void static_value(struct stats_ctx *ctx, const char *name, double val,
262                          enum val_type type)
263 {
264     if (!IS_ACTIVE(ctx))
265         return;
266     pthread_mutex_lock(&ctx->base->lock);
267     struct stat_entry *e = find_entry(ctx, name);
268     e->val_d = val;
269     e->type = type;
270     pthread_mutex_unlock(&ctx->base->lock);
271 }
272 
stats_value(struct stats_ctx * ctx,const char * name,double val)273 void stats_value(struct stats_ctx *ctx, const char *name, double val)
274 {
275     static_value(ctx, name, val, VAL_STATIC);
276 }
277 
stats_size_value(struct stats_ctx * ctx,const char * name,double val)278 void stats_size_value(struct stats_ctx *ctx, const char *name, double val)
279 {
280     static_value(ctx, name, val, VAL_STATIC_SIZE);
281 }
282 
stats_time_start(struct stats_ctx * ctx,const char * name)283 void stats_time_start(struct stats_ctx *ctx, const char *name)
284 {
285     MP_STATS(ctx->base->global, "start %s", name);
286     if (!IS_ACTIVE(ctx))
287         return;
288     pthread_mutex_lock(&ctx->base->lock);
289     struct stat_entry *e = find_entry(ctx, name);
290     e->cpu_start_ns = get_thread_cpu_time_ns(pthread_self());
291     e->time_start_us = mp_time_us();
292     pthread_mutex_unlock(&ctx->base->lock);
293 }
294 
stats_time_end(struct stats_ctx * ctx,const char * name)295 void stats_time_end(struct stats_ctx *ctx, const char *name)
296 {
297     MP_STATS(ctx->base->global, "end %s", name);
298     if (!IS_ACTIVE(ctx))
299         return;
300     pthread_mutex_lock(&ctx->base->lock);
301     struct stat_entry *e = find_entry(ctx, name);
302     if (e->time_start_us) {
303         e->type = VAL_TIME;
304         e->val_rt += mp_time_us() - e->time_start_us;
305         e->val_th += get_thread_cpu_time_ns(pthread_self()) - e->cpu_start_ns;
306         e->time_start_us = 0;
307     }
308     pthread_mutex_unlock(&ctx->base->lock);
309 }
310 
stats_event(struct stats_ctx * ctx,const char * name)311 void stats_event(struct stats_ctx *ctx, const char *name)
312 {
313     if (!IS_ACTIVE(ctx))
314         return;
315     pthread_mutex_lock(&ctx->base->lock);
316     struct stat_entry *e = find_entry(ctx, name);
317     e->val_d += 1;
318     e->type = VAL_INC;
319     pthread_mutex_unlock(&ctx->base->lock);
320 }
321 
register_thread(struct stats_ctx * ctx,const char * name,enum val_type type)322 static void register_thread(struct stats_ctx *ctx, const char *name,
323                             enum val_type type)
324 {
325     pthread_mutex_lock(&ctx->base->lock);
326     struct stat_entry *e = find_entry(ctx, name);
327     e->type = type;
328     e->thread = pthread_self();
329     pthread_mutex_unlock(&ctx->base->lock);
330 }
331 
stats_register_thread_cputime(struct stats_ctx * ctx,const char * name)332 void stats_register_thread_cputime(struct stats_ctx *ctx, const char *name)
333 {
334     register_thread(ctx, name, VAL_THREAD_CPU_TIME);
335 }
336 
stats_unregister_thread(struct stats_ctx * ctx,const char * name)337 void stats_unregister_thread(struct stats_ctx *ctx, const char *name)
338 {
339     register_thread(ctx, name, 0);
340 }
341