summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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" ),