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 | } |