summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2020-04-09 00:27:54 +0200
committerwm4 <wm4@nowhere>2020-04-09 00:33:38 +0200
commitfd3caa264ea0848e7e30db94390063c87e247003 (patch)
tree3eb8268c1afc7a0bab5a544e4664cce23d2ab712
parentc5f8ec76b16268b05d1e3af8f1931eddf5165b8b (diff)
downloadmpv-fd3caa264ea0848e7e30db94390063c87e247003.tar.bz2
mpv-fd3caa264ea0848e7e30db94390063c87e247003.tar.xz
stats: some more performance graphs
Add an infrastructure for collecting performance-related data, use it in some places. Add rendering of them to stats.lua. There were two main goals: minimal impact on the normal code and normal playback. So all these stats_* function calls either happen only during initialization, or return immediately if no stats collection is going on. That's why it does this lazily adding of stats entries etc. (a first iteration made each stats entry an API thing, instead of just a single stats_ctx, but I thought that was getting too intrusive in the "normal" code, even if everything gets worse inside of stats.c). You could get most of this information from various profilers (including the extremely primitive --dump-stats thing in mpv), but this makes it easier to see the most important information at once (at least in theory), partially because we know best about the context of various things. Not very happy with this. It's all pretty primitive and dumb. At this point I just wanted to get over with it, without necessarily having to revisit it later, but with having my stupid statistics. Somehow the code feels terrible. There are a lot of meh decisions in there that could be better or worse (but mostly could be better), and it just sucks but it's also trivial and uninteresting and does the job. I guess I hate programming. It's so tedious and the result is always shit. Anyway, enjoy.
-rw-r--r--DOCS/man/input.rst9
-rw-r--r--DOCS/man/stats.rst35
-rw-r--r--common/global.h1
-rw-r--r--common/stats.c324
-rw-r--r--common/stats.h31
-rw-r--r--demux/demux.c7
-rw-r--r--player/command.c19
-rw-r--r--player/loadfile.c4
-rw-r--r--player/lua.c33
-rw-r--r--player/lua/stats.lua39
-rw-r--r--player/main.c3
-rw-r--r--sub/osd.c11
-rw-r--r--sub/osd_state.h1
-rw-r--r--video/out/vo.c12
-rw-r--r--wscript_build.py1
15 files changed, 519 insertions, 11 deletions
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 <pthread.h>
+#include <time.h>
+#include <unistd.h>
+
+#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" ),