Coverage Report

Created: 2025-12-11 06:55

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