diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index 958b7a4241..af80052b1d 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -2875,6 +2875,15 @@ Property list Note that directly accessing this structure via subkeys is not supported, the only access is through aforementioned ``MPV_FORMAT_NODE``. +``perf-info`` + Further performance data. Querying this property triggers internal + collection of some data, and may slow down the player. Each query will reset + some internal state. Property change notification doesn't and won't work. + All of this may change in the future, so don't use this. The builtin + ``stats`` script is supposed to be the only user; since it's bundled and + built with the source code, it can use knowledge of mpv internal to render + the information properly. See ``stats`` script description for some details. + ``video-bitrate``, ``audio-bitrate``, ``sub-bitrate`` Bitrate values calculated on the packet level. This works by dividing the bit size of all packets between two keyframes by their presentation diff --git a/DOCS/man/stats.rst b/DOCS/man/stats.rst index 857fc231e2..603de4ba97 100644 --- a/DOCS/man/stats.rst +++ b/DOCS/man/stats.rst @@ -24,6 +24,7 @@ stats: 1 Show usual stats 2 Show frame timings 3 Input cache stats +4 Internal stuff ==== ================== Font @@ -56,6 +57,8 @@ Configurable Options Default: 2 ``key_page_3`` Default: 3 +``key_page_4`` + Default: 4 Key bindings for page switching while stats are displayed. @@ -163,3 +166,35 @@ Using ``input.conf``, it is also possible to directly display a certain page:: i script-binding stats/display-page-1 e script-binding stats/display-page-2 + +Internal stuff page +~~~~~~~~~~~~~~~~~~~ + +Most entries shown on this page have rather vague meaning. Likely none of this +is useful for you. Don't attempt to use it. Forget its existence. + +Selecting this for the first time will start collecting some internal +performance data. That means performance will be slightly lower than normal for +the rest of the time the player is running (even if the stats page is closed). +Note that the stats page itself + +The displayed information is accumulated over the redraw delay (shown as +``poll-time`` field). + +This adds entries for each Lua script. If there are too many scripts running, +parts of the list will simply be out of the screen. + +If the underlying platform does not support pthread per thread times, the +displayed times will be 0 or something random (I suspect that at time of this +writing, only Linux provides the correct via pthread APIs for per thread times). + +Most entries are added lazily and only during data collection, which is why +entries may pop up randomly after some time. It's also why the memory usage +entries for scripts that have been inactive since the start of data collection +are missing. + +Memory usage is approximate and does not reflect internal fragmentation. + +If entries have ``/time`` and ``/cpu`` variants, the former gives the real time +(monotonic clock), while the latter the thread CPU time (only if the +corresponding pthread API works and is supported). diff --git a/common/global.h b/common/global.h index f6f83cf68c..f95cf28a4d 100644 --- a/common/global.h +++ b/common/global.h @@ -9,6 +9,7 @@ struct mpv_global { struct m_config_shadow *config; struct mp_client_api *client_api; char *configdir; + struct stats_base *stats; }; #endif diff --git a/common/stats.c b/common/stats.c new file mode 100644 index 0000000000..af06457500 --- /dev/null +++ b/common/stats.c @@ -0,0 +1,324 @@ +#include +#include +#include + +#include "common.h" +#include "global.h" +#include "misc/linked_list.h" +#include "misc/node.h" +#include "msg.h" +#include "options/m_option.h" +#include "osdep/atomic.h" +#include "osdep/timer.h" +#include "stats.h" + +struct stats_base { + struct mpv_global *global; + + atomic_bool active; + + pthread_mutex_t lock; + + struct { + struct stats_ctx *head, *tail; + } list; + + struct stat_entry **entries; + int num_entries; + + int64_t last_time; +}; + +struct stats_ctx { + struct stats_base *base; + const char *prefix; + + struct { + struct stats_ctx *prev, *next; + } list; + + struct stat_entry **entries; + int num_entries; +}; + +enum val_type { + VAL_UNSET = 0, + VAL_STATIC, + VAL_STATIC_SIZE, + VAL_TIME, + VAL_THREAD_CPU_TIME, +}; + +struct stat_entry { + char name[32]; + const char *full_name; // including stats_ctx.prefix + + enum val_type type; + double val_d; + int64_t val_rt; + int64_t val_th; + int64_t time_start_us; + int64_t cpu_start_ns; + pthread_t thread; +}; + +#define IS_ACTIVE(ctx) \ + (atomic_load_explicit(&(ctx)->base->active, memory_order_relaxed)) + +// Overflows only after I'm dead. +static int64_t get_thread_cpu_time_ns(pthread_t thread) +{ +#if defined(_POSIX_TIMERS) && _POSIX_TIMERS > 0 && defined(_POSIX_THREAD_CPUTIME) + clockid_t id; + struct timespec tv; + if (pthread_getcpuclockid(thread, &id) == 0 && + clock_gettime(id, &tv) == 0) + { + return tv.tv_sec * (1000LL * 1000LL * 1000LL) + tv.tv_nsec; + } +#endif + return 0; +} + +static void stats_destroy(void *p) +{ + struct stats_base *stats = p; + + // All entries must have been destroyed before this. + assert(!stats->list.head); + + pthread_mutex_destroy(&stats->lock); +} + +void stats_global_init(struct mpv_global *global) +{ + assert(!global->stats); + struct stats_base *stats = talloc_zero(global, struct stats_base); + ta_set_destructor(stats, stats_destroy); + pthread_mutex_init(&stats->lock, NULL); + + global->stats = stats; + stats->global = global; +} + +static void add_stat(struct mpv_node *list, struct stat_entry *e, + const char *suffix, double num_val, char *text) +{ + struct mpv_node *ne = node_array_add(list, MPV_FORMAT_NODE_MAP); + + node_map_add_string(ne, "name", suffix ? + mp_tprintf(80, "%s/%s", e->full_name, suffix) : e->full_name); + node_map_add_double(ne, "value", num_val); + if (text) + node_map_add_string(ne, "text", text); +} + +static int cmp_entry(const void *p1, const void *p2) +{ + struct stat_entry **e1 = (void *)p1; + struct stat_entry **e2 = (void *)p2; + return strcmp((*e1)->full_name, (*e2)->full_name); +} + +void stats_global_query(struct mpv_global *global, struct mpv_node *out) +{ + struct stats_base *stats = global->stats; + assert(stats); + + pthread_mutex_lock(&stats->lock); + + atomic_store(&stats->active, true); + + if (!stats->num_entries) { + for (struct stats_ctx *ctx = stats->list.head; ctx; ctx = ctx->list.next) + { + for (int n = 0; n < ctx->num_entries; n++) { + MP_TARRAY_APPEND(stats, stats->entries, stats->num_entries, + ctx->entries[n]); + } + } + if (stats->num_entries) { + qsort(stats->entries, stats->num_entries, sizeof(stats->entries[0]), + cmp_entry); + } + } + + node_init(out, MPV_FORMAT_NODE_ARRAY, NULL); + + int64_t now = mp_time_us(); + if (stats->last_time) { + double t_ms = (now - stats->last_time) / 1e3; + struct mpv_node *ne = node_array_add(out, MPV_FORMAT_NODE_MAP); + node_map_add_string(ne, "name", "poll-time"); + node_map_add_double(ne, "value", t_ms); + node_map_add_string(ne, "text", mp_tprintf(80, "%.2f ms", t_ms)); + + // Very dirty way to reset everything if the stats.lua page was probably + // closed. Not enough energy left for clean solution. Fuck it. + if (t_ms > 2000) { + for (int n = 0; n < stats->num_entries; n++) { + struct stat_entry *e = stats->entries[n]; + + e->cpu_start_ns = 0; + e->val_rt = e->val_th = 0; + if (e->type != VAL_THREAD_CPU_TIME) + e->type = 0; + } + } + } + stats->last_time = now; + + for (int n = 0; n < stats->num_entries; n++) { + struct stat_entry *e = stats->entries[n]; + + switch (e->type) { + case VAL_STATIC: + add_stat(out, e, NULL, e->val_d, NULL); + break; + case VAL_STATIC_SIZE: { + char *s = format_file_size(e->val_d); + add_stat(out, e, NULL, e->val_d, s); + talloc_free(s); + break; + } + case VAL_TIME: { + double t_cpu = e->val_th / 1e6; + add_stat(out, e, "cpu", t_cpu, mp_tprintf(80, "%.2f ms", t_cpu)); + double t_rt = e->val_rt / 1e3; + add_stat(out, e, "time", t_rt, mp_tprintf(80, "%.2f ms", t_rt)); + e->val_rt = e->val_th = 0; + break; + } + case VAL_THREAD_CPU_TIME: { + int64_t t = get_thread_cpu_time_ns(e->thread); + if (!e->cpu_start_ns) + e->cpu_start_ns = t; + double t_msec = (t - e->cpu_start_ns) / 1e6; + add_stat(out, e, NULL, t_msec, mp_tprintf(80, "%.2f ms", t_msec)); + e->cpu_start_ns = t; + break; + } + default: ; + } + } + + pthread_mutex_unlock(&stats->lock); +} + +static void stats_ctx_destroy(void *p) +{ + struct stats_ctx *ctx = p; + + pthread_mutex_lock(&ctx->base->lock); + LL_REMOVE(list, &ctx->base->list, ctx); + ctx->base->num_entries = 0; // invalidate + pthread_mutex_unlock(&ctx->base->lock); +} + +struct stats_ctx *stats_ctx_create(void *ta_parent, struct mpv_global *global, + const char *prefix) +{ + struct stats_base *base = global->stats; + assert(base); + + struct stats_ctx *ctx = talloc_zero(ta_parent, struct stats_ctx); + ctx->base = base; + ctx->prefix = talloc_strdup(ctx, prefix); + ta_set_destructor(ctx, stats_ctx_destroy); + + pthread_mutex_lock(&base->lock); + LL_APPEND(list, &base->list, ctx); + base->num_entries = 0; // invalidate + pthread_mutex_unlock(&base->lock); + + return ctx; +} + +static struct stat_entry *find_entry(struct stats_ctx *ctx, const char *name) +{ + for (int n = 0; n < ctx->num_entries; n++) { + if (strcmp(ctx->entries[n]->name, name) == 0) + return ctx->entries[n]; + } + + struct stat_entry *e = talloc_zero(ctx, struct stat_entry); + snprintf(e->name, sizeof(e->name), "%s", name); + assert(strcmp(e->name, name) == 0); // make e->name larger and don't complain + + e->full_name = talloc_asprintf(e, "%s/%s", ctx->prefix, e->name); + + MP_TARRAY_APPEND(ctx, ctx->entries, ctx->num_entries, e); + ctx->base->num_entries = 0; // invalidate + + return e; +} + +static void static_value(struct stats_ctx *ctx, const char *name, double val, + enum val_type type) +{ + if (!IS_ACTIVE(ctx)) + return; + pthread_mutex_lock(&ctx->base->lock); + struct stat_entry *e = find_entry(ctx, name); + e->val_d = val; + e->type = type; + pthread_mutex_unlock(&ctx->base->lock); +} + +void stats_value(struct stats_ctx *ctx, const char *name, double val) +{ + static_value(ctx, name, val, VAL_STATIC); +} + +void stats_size_value(struct stats_ctx *ctx, const char *name, double val) +{ + static_value(ctx, name, val, VAL_STATIC_SIZE); +} + +void stats_time_start(struct stats_ctx *ctx, const char *name) +{ + MP_STATS(ctx->base->global, "start %s", name); + if (!IS_ACTIVE(ctx)) + return; + pthread_mutex_lock(&ctx->base->lock); + struct stat_entry *e = find_entry(ctx, name); + e->cpu_start_ns = get_thread_cpu_time_ns(pthread_self()); + e->time_start_us = mp_time_us(); + pthread_mutex_unlock(&ctx->base->lock); +} + +void stats_time_end(struct stats_ctx *ctx, const char *name) +{ + MP_STATS(ctx->base->global, "end %s", name); + if (!IS_ACTIVE(ctx)) + return; + pthread_mutex_lock(&ctx->base->lock); + struct stat_entry *e = find_entry(ctx, name); + if (e->time_start_us) { + e->type = VAL_TIME; + e->val_rt += mp_time_us() - e->time_start_us; + e->val_th += get_thread_cpu_time_ns(pthread_self()) - e->cpu_start_ns; + e->time_start_us = 0; + } + pthread_mutex_unlock(&ctx->base->lock); +} + +static void register_thread(struct stats_ctx *ctx, const char *name, + enum val_type type) +{ + pthread_mutex_lock(&ctx->base->lock); + struct stat_entry *e = find_entry(ctx, name); + e->type = type; + e->thread = pthread_self(); + pthread_mutex_unlock(&ctx->base->lock); +} + +void stats_register_thread_cputime(struct stats_ctx *ctx, const char *name) +{ + register_thread(ctx, name, VAL_THREAD_CPU_TIME); +} + +void stats_unregister_thread(struct stats_ctx *ctx, const char *name) +{ + register_thread(ctx, name, 0); +} diff --git a/common/stats.h b/common/stats.h new file mode 100644 index 0000000000..4568a57cff --- /dev/null +++ b/common/stats.h @@ -0,0 +1,31 @@ +#pragma once + +struct mpv_global; +struct mpv_node; +struct stats_ctx; + +void stats_global_init(struct mpv_global *global); +void stats_global_query(struct mpv_global *global, struct mpv_node *out); + +// stats_ctx can be free'd with ta_free(), or by using the ta_parent. +struct stats_ctx *stats_ctx_create(void *ta_parent, struct mpv_global *global, + const char *prefix); + +// A static numeric value. +void stats_value(struct stats_ctx *ctx, const char *name, double val); + +// Like stats_value(), but render as size in bytes. +void stats_size_value(struct stats_ctx *ctx, const char *name, double val); + +// Report the real time and CPU time in seconds between _start and _end calls +// as value, and report the average and number of all times. +void stats_time_start(struct stats_ctx *ctx, const char *name); +void stats_time_end(struct stats_ctx *ctx, const char *name); + +// Report the thread's CPU time. This needs to be called only once per thread. +// The current thread is assumed to stay valid until the stats_ctx is destroyed +// or stats_unregister_thread() is called, otherwise UB will occur. +void stats_register_thread_cputime(struct stats_ctx *ctx, const char *name); + +// Remove reference to pthread_self(). +void stats_unregister_thread(struct stats_ctx *ctx, const char *name); diff --git a/demux/demux.c b/demux/demux.c index 1922f68397..ad1a72ac5d 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -39,6 +39,7 @@ #include "common/msg.h" #include "common/global.h" #include "common/recorder.h" +#include "common/stats.h" #include "misc/charset_conv.h" #include "misc/thread_tools.h" #include "osdep/atomic.h" @@ -167,6 +168,7 @@ const struct m_sub_options demux_conf = { struct demux_internal { struct mp_log *log; struct mpv_global *global; + struct stats_ctx *stats; bool can_cache; // not a slave demuxer; caching makes sense bool can_record; // stream recording is allowed @@ -2551,6 +2553,8 @@ static void *demux_thread(void *pctx) mpthread_set_name("demux"); pthread_mutex_lock(&in->lock); + stats_register_thread_cputime(in->stats, "thread"); + while (!in->thread_terminate) { if (thread_work(in)) continue; @@ -2568,6 +2572,8 @@ static void *demux_thread(void *pctx) in->wakeup_cb(in->wakeup_cb_ctx); } + stats_unregister_thread(in->stats, "thread"); + pthread_mutex_unlock(&in->lock); return NULL; } @@ -3262,6 +3268,7 @@ static struct demuxer *open_given_type(struct mpv_global *global, *in = (struct demux_internal){ .global = global, .log = demuxer->log, + .stats = stats_ctx_create(in, global, "demuxer"), .can_cache = params && params->is_top_level, .can_record = params && params->stream_record, .opts = opts, diff --git a/player/command.c b/player/command.c index e1f755d46c..a41a636c7d 100644 --- a/player/command.c +++ b/player/command.c @@ -38,6 +38,7 @@ #include "common/codecs.h" #include "common/msg.h" #include "common/msg_control.h" +#include "common/stats.h" #include "filters/f_decoder_wrapper.h" #include "command.h" #include "osdep/timer.h" @@ -2446,6 +2447,23 @@ out: return ret; } +static int mp_property_perf_info(void *ctx, struct m_property *p, int action, + void *arg) +{ + MPContext *mpctx = ctx; + + switch (action) { + case M_PROPERTY_GET_TYPE: + *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; + return M_PROPERTY_OK; + case M_PROPERTY_GET: { + stats_global_query(mpctx->global, (struct mpv_node *)arg); + return M_PROPERTY_OK; + } + } + return M_PROPERTY_NOT_IMPLEMENTED; +} + static int mp_property_vo(void *ctx, struct m_property *p, int action, void *arg) { MPContext *mpctx = ctx; @@ -3425,6 +3443,7 @@ static const struct m_property mp_properties_base[] = { {"current-window-scale", mp_property_current_window_scale}, {"vo-configured", mp_property_vo_configured}, {"vo-passes", mp_property_vo_passes}, + {"perf-info", mp_property_perf_info}, {"current-vo", mp_property_vo}, {"container-fps", mp_property_fps}, {"estimated-vf-fps", mp_property_vf_fps}, diff --git a/player/loadfile.c b/player/loadfile.c index 9efacea766..6af9ac37fd 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -46,6 +46,7 @@ #include "common/common.h" #include "common/encode.h" #include "common/recorder.h" +#include "common/stats.h" #include "input/input.h" #include "audio/out/ao.h" @@ -1772,6 +1773,9 @@ struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction, // Return if all done. void mp_play_files(struct MPContext *mpctx) { + struct stats_ctx *stats = stats_ctx_create(mpctx, mpctx->global, "main"); + stats_register_thread_cputime(stats, "thread"); + // Wait for all scripts to load before possibly starting playback. if (!mp_clients_all_initialized(mpctx)) { MP_VERBOSE(mpctx, "Waiting for scripts...\n"); diff --git a/player/lua.c b/player/lua.c index c816118b8b..0c5a56ec14 100644 --- a/player/lua.c +++ b/player/lua.c @@ -36,6 +36,7 @@ #include "options/m_property.h" #include "common/msg.h" #include "common/msg_control.h" +#include "common/stats.h" #include "options/m_option.h" #include "input/input.h" #include "options/path.h" @@ -88,6 +89,8 @@ struct script_ctx { struct mp_log *log; struct mpv_handle *client; struct MPContext *mpctx; + size_t lua_malloc_size; + struct stats_ctx *stats; }; #if LUA_VERSION_NUM <= 501 @@ -156,6 +159,30 @@ static void steal_node_alloctions(void *tmp, mpv_node *node) talloc_steal(tmp, node_get_alloc(node)); } +// lua_Alloc compatible. Serves only to retrieve memory usage. +static void *mp_lua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) +{ + struct script_ctx *ctx = ud; + + // Ah, what the fuck, screw whoever introduced this to Lua 5.2. + if (!ptr) + osize = 0; + + if (nsize) { + ptr = realloc(ptr, nsize); + if (!ptr) + return NULL; + } else { + free(ptr); + ptr = NULL; + } + + ctx->lua_malloc_size = ctx->lua_malloc_size - osize + nsize; + stats_size_value(ctx->stats, "mem", ctx->lua_malloc_size); + + return ptr; +} + static struct script_ctx *get_ctx(lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, "ctx"); @@ -396,14 +423,18 @@ static int load_lua(struct mp_script_args *args) .log = args->log, .filename = args->filename, .path = args->path, + .stats = stats_ctx_create(ctx, args->mpctx->global, + mp_tprintf(80, "script/%s", mpv_client_name(args->client))), }; + stats_register_thread_cputime(ctx->stats, "cpu"); + if (LUA_VERSION_NUM != 501 && LUA_VERSION_NUM != 502) { MP_FATAL(ctx, "Only Lua 5.1 and 5.2 are supported.\n"); goto error_out; } - lua_State *L = ctx->state = luaL_newstate(); + lua_State *L = ctx->state = lua_newstate(mp_lua_alloc, ctx); if (!L) { MP_FATAL(ctx, "Could not initialize Lua.\n"); goto error_out; diff --git a/player/lua/stats.lua b/player/lua/stats.lua index 98bc1be7c9..93c2c99c18 100644 --- a/player/lua/stats.lua +++ b/player/lua/stats.lua @@ -18,6 +18,7 @@ local o = { key_page_1 = "1", key_page_2 = "2", key_page_3 = "3", + key_page_4 = "4", duration = 4, redraw_delay = 1, -- acts as duration in the toggling case @@ -101,6 +102,7 @@ local function init_buffers() cache_ahead_buf = {0, pos = 1, len = 50, max = 0} cache_speed_buf = {0, pos = 1, len = 50, max = 0} end +local perf_buffers = {} -- Save all properties known to this version of mpv local property_list = {} for p in string.gmatch(mp.get_property("property-list"), "([^,]+)") do property_list[p] = true end @@ -111,6 +113,11 @@ local property_aliases = { ["container-fps"] = "fps", } +local function graph_add_value(graph, value) + graph.pos = (graph.pos % graph.len) + 1 + graph[graph.pos] = value + graph.max = max(graph.max, value) +end -- Return deprecated name for the given property local function compat(p) @@ -347,6 +354,21 @@ local function append_perfdata(s, dedicated_page) end end +local function append_general_perfdata(s) + for _, data in ipairs(mp.get_property_native("perf-info") or {}) do + append(s, data.text or data.value, {prefix=data.name..":"}) + + if o.plot_perfdata and o.use_ass and data.value then + buf = perf_buffers[data.name] + if not buf then + buf = {0, pos = 1, len = 50, max = 0} + perf_buffers[data.name] = buf + end + graph_add_value(buf, data.value) + s[#s+1] = generate_graph(buf, buf.pos, buf.len, buf.max, nil, 0.8, 1) + end + end +end local function append_display_sync(s) if not mp.get_property_bool("display-sync-active", false) then @@ -594,6 +616,16 @@ local function vo_stats() return table.concat(stats) end +local function perf_stats() + local stats = {} + eval_ass_formatting() + add_header(stats) + local page = pages[o.key_page_4] + append(stats, "", {prefix=o.nl .. o.nl .. page.desc .. ":", nl="", indent=""}) + append_general_perfdata(stats, true) + return table.concat(stats) +end + local function opt_time(t) if type(t) == type(1.1) then return mp.format_time(t) @@ -693,12 +725,6 @@ local function cache_stats() return table.concat(stats) end -local function graph_add_value(graph, value) - graph.pos = (graph.pos % graph.len) + 1 - graph[graph.pos] = value - graph.max = max(graph.max, value) -end - -- Record 1 sample of cache statistics. -- (Unlike record_data(), this does not return a function, but runs directly.) local function record_cache_stats() @@ -725,6 +751,7 @@ pages = { [o.key_page_1] = { f = default_stats, desc = "Default" }, [o.key_page_2] = { f = vo_stats, desc = "Extended Frame Timings" }, [o.key_page_3] = { f = cache_stats, desc = "Cache Statistics" }, + [o.key_page_4] = { f = perf_stats, desc = "Internal performance info" }, } diff --git a/player/main.c b/player/main.c index b0150b2b80..b5695a663a 100644 --- a/player/main.c +++ b/player/main.c @@ -43,6 +43,7 @@ #include "common/common.h" #include "common/msg.h" #include "common/msg_control.h" +#include "common/stats.h" #include "common/global.h" #include "filters/f_decoder_wrapper.h" #include "options/parse_configfile.h" @@ -275,6 +276,8 @@ struct MPContext *mp_create(void) mpctx->global = talloc_zero(mpctx, struct mpv_global); + stats_global_init(mpctx->global); + // Nothing must call mp_msg*() and related before this mp_msg_init(mpctx->global); mpctx->log = mp_log_new(mpctx, mpctx->global->log, "!cplayer"); diff --git a/sub/osd.c b/sub/osd.c index e5cc677819..93e1a07162 100644 --- a/sub/osd.c +++ b/sub/osd.c @@ -32,6 +32,7 @@ #include "options/options.h" #include "common/global.h" #include "common/msg.h" +#include "common/stats.h" #include "player/client.h" #include "player/command.h" #include "osd.h" @@ -124,6 +125,7 @@ struct osd_state *osd_create(struct mpv_global *global) .global = global, .log = mp_log_new(osd, global->log, "osd"), .force_video_pts = MP_NOPTS_VALUE, + .stats = stats_ctx_create(osd, global, "osd"), }; pthread_mutex_init(&osd->lock, NULL); osd->opts = osd->opts_cache->opts; @@ -326,11 +328,20 @@ void osd_draw(struct osd_state *osd, struct mp_osd_res res, if (obj->sub) sub_lock(obj->sub); + char *stat_type_render = obj->is_sub ? "sub-render" : "osd-render"; + char *stat_type_draw = obj->is_sub ? "sub-draw" : "osd-draw"; + stats_time_start(osd->stats, stat_type_render); + struct sub_bitmaps imgs; render_object(osd, obj, res, video_pts, formats, &imgs); + + stats_time_end(osd->stats, stat_type_render); + if (imgs.num_parts > 0) { if (formats[imgs.format]) { + stats_time_start(osd->stats, stat_type_draw); cb(cb_ctx, &imgs); + stats_time_end(osd->stats, stat_type_draw); } else { MP_ERR(osd, "Can't render OSD part %d (format %d).\n", obj->type, imgs.format); diff --git a/sub/osd_state.h b/sub/osd_state.h index b563502fd6..3469f4383d 100644 --- a/sub/osd_state.h +++ b/sub/osd_state.h @@ -77,6 +77,7 @@ struct osd_state { struct mp_osd_render_opts *opts; struct mpv_global *global; struct mp_log *log; + struct stats_ctx *stats; struct mp_draw_sub_cache *draw_cache; }; diff --git a/video/out/vo.c b/video/out/vo.c index e5f8752f07..52325ac953 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -40,6 +40,7 @@ #include "options/m_config.h" #include "common/msg.h" #include "common/global.h" +#include "common/stats.h" #include "video/hwdec.h" #include "video/mp_image.h" #include "sub/osd.h" @@ -162,6 +163,8 @@ struct vo_internal { double display_fps; double reported_display_fps; + + struct stats_ctx *stats; }; extern const struct m_sub_options gl_video_conf; @@ -294,6 +297,7 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, .dispatch = mp_dispatch_create(vo), .req_frames = 1, .estimated_vsync_jitter = -1, + .stats = stats_ctx_create(vo, global, "vo"), }; mp_dispatch_set_wakeup_fn(vo->in->dispatch, dispatch_wakeup_cb, vo); pthread_mutex_init(&vo->in->lock, NULL); @@ -900,7 +904,7 @@ static bool render_frame(struct vo *vo) pthread_mutex_unlock(&in->lock); wakeup_core(vo); // core can queue new video now - MP_STATS(vo, "start video-draw"); + stats_time_start(in->stats, "video-draw"); if (vo->driver->draw_frame) { vo->driver->draw_frame(vo, frame); @@ -908,11 +912,11 @@ static bool render_frame(struct vo *vo) vo->driver->draw_image(vo, mp_image_new_ref(frame->current)); } - MP_STATS(vo, "end video-draw"); + stats_time_end(in->stats, "video-draw"); wait_until(vo, target); - MP_STATS(vo, "start video-flip"); + stats_time_start(in->stats, "video-flip"); vo->driver->flip_page(vo); @@ -927,7 +931,7 @@ static bool render_frame(struct vo *vo) if (vsync.last_queue_display_time < 0) vsync.last_queue_display_time = mp_time_us(); - MP_STATS(vo, "end video-flip"); + stats_time_end(in->stats, "video-flip"); pthread_mutex_lock(&in->lock); in->dropped_frame = prev_drop_count < vo->in->drop_count; diff --git a/wscript_build.py b/wscript_build.py index 62f2264efe..2a656a604d 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -273,6 +273,7 @@ def build(ctx): ( "common/msg.c" ), ( "common/playlist.c" ), ( "common/recorder.c" ), + ( "common/stats.c" ), ( "common/tags.c" ), ( "common/version.c" ),