From 88b3d9525e37625900e17348195163fca363cc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Fri, 14 Jan 2022 19:02:00 +0100 Subject: [PATCH 1/2] pulse-server: add facilities to dump internal state Add a `dump_state()` function which can be used to dump many parts of the internal state of the server into a `FILE` in JSON format. --- src/modules/meson.build | 1 + src/modules/module-protocol-pulse/debug.c | 663 ++++++++++++++++++++++ src/modules/module-protocol-pulse/debug.h | 10 + 3 files changed, 674 insertions(+) create mode 100644 src/modules/module-protocol-pulse/debug.c create mode 100644 src/modules/module-protocol-pulse/debug.h diff --git a/src/modules/meson.build b/src/modules/meson.build index 85f309a8c..4bc7f0fd0 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -341,6 +341,7 @@ pipewire_module_protocol_pulse_sources = [ 'module-protocol-pulse/client.c', 'module-protocol-pulse/collect.c', 'module-protocol-pulse/cmd.c', + 'module-protocol-pulse/debug.c', 'module-protocol-pulse/extension.c', 'module-protocol-pulse/extensions/ext-device-manager.c', 'module-protocol-pulse/extensions/ext-device-restore.c', diff --git a/src/modules/module-protocol-pulse/debug.c b/src/modules/module-protocol-pulse/debug.c new file mode 100644 index 000000000..4032091f0 --- /dev/null +++ b/src/modules/module-protocol-pulse/debug.c @@ -0,0 +1,663 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "client.h" +#include "debug.h" +#include "defs.h" +#include "format.h" +#include "internal.h" +#include "message.h" +#include "module.h" +#include "operation.h" +#include "pending-sample.h" +#include "quirks.h" +#include "sample.h" +#include "sample-play.h" +#include "server.h" +#include "stream.h" +#include "utils.h" +#include "volume.h" + +enum s_type { + t_unknown, + t_key, + t_value, + t_obj_begin, + t_array_begin, +}; + +struct s_scope { + struct s_scope *prev; + enum s_type type; +}; + +struct serializer { + FILE *target; + + struct s_scope first; + struct s_scope *current; + + enum s_type last; +}; + +/* ========================================================================== */ + +static void s_enter_container(struct serializer *s, struct s_scope *scope, const char c, enum s_type type) +{ + if (s->last == t_value) + fputc(',', s->target); + + scope->prev = s->current; + scope->type = s->last = type; + s->current = scope; + + fputc(c, s->target); +} + +static void s_leave_container(struct serializer *s, struct s_scope *scope, const char c, enum s_type type) +{ + spa_assert(s->current == scope); + spa_assert(s->last == t_value || s->last == type); + spa_assert(s->current->type == type); + + s->current = scope->prev; + s->last = t_value; + + fputc(c, s->target); +} + +/* ========================================================================== */ + +#define s_object(s, ...) \ +do { \ + struct s_scope __scope; \ + s_enter_container((s), &__scope, '{', t_obj_begin); \ + do { __VA_ARGS__ } while (false); \ + s_leave_container((s), &__scope, '}', t_obj_begin); \ +} while (false) + +#define s_k_object(s, key, ...) \ +do { \ + s_key((s), (key)); \ + s_object((s), __VA_ARGS__); \ +} while (false); + +#define s_array(s, ...) \ +do { \ + struct s_scope __scope; \ + s_enter_container((s), &__scope, '[', t_array_begin); \ + do { __VA_ARGS__ } while (false); \ + s_leave_container((s), &__scope, ']', t_array_begin); \ +} while (false) + +#define s_k_array(s, key, ...) \ +do { \ + s_key((s), (key)); \ + s_array((s), __VA_ARGS__); \ +} while (false); + +/* ========================================================================== */ + +static void s_emit_string(FILE *file, const char *str) +{ + for (; *str; str++) { + const char ch = *str; + + if (ch == '\\') + fputs("\\\\", file); + else if (ch == '"') + fputs("\\\"", file); + else if (ch == '\n') + fputs("\\n", file); + else if (ch == '\t') + fputs("\\t", file); + else if (iscntrl(ch)) + fprintf(file, "\\u%04X", (unsigned int) ch); + else + fputc(ch, file); + } +} + +static void s_key(struct serializer *s, const char *key) +{ + spa_assert(s->last == t_obj_begin || s->last == t_value); + spa_assert(s->current->type == t_obj_begin); + + if (s->last == t_value) + fputc(',', s->target); + + s->last = t_key; + + fputc('"', s->target); + s_emit_string(s->target, key); + fputs("\":", s->target); +} + +static bool can_emit_value(const struct serializer *s) +{ + switch (s->current->type) { + case t_obj_begin: + return s->last == t_key; + case t_array_begin: + return s->last == t_value || s->last == t_array_begin; + default: + return false; + } +} + +#define s_emit_value(s, ...) \ +do { \ + spa_assert(can_emit_value(s)); \ + if ((s)->last == t_value) \ + fputc(',', (s)->target); \ + do { __VA_ARGS__ } while (false); \ + (s)->last = t_value; \ +} while (false) + +static void s_str(struct serializer *s, const char *str) +{ + s_emit_value(s, { + if (str) { + fputc('"', s->target); + s_emit_string(s->target, str); + fputc('"', s->target); + } + else { + fputs("null", s->target); + } + }); +} + +static void s_bool(struct serializer *s, bool x) +{ + s_emit_value(s, { + fputs(x ? "true" : "false", s->target); + }); +} + +static void s_int(struct serializer *s, int64_t x) +{ + s_emit_value(s, { + fprintf(s->target, "%" PRId64, x); + }); +} + +static void s_float(struct serializer *s, double x) +{ + s_emit_value(s, { + fprintf(s->target, "%f", x); + }); +} + +static void s_ptr(struct serializer *s, const void *ptr) +{ + s_emit_value(s, { + if (ptr) + fprintf(s->target, "\"%p\"", ptr); + else + fputs("null", s->target); + }); +} + +/* ========================================================================== */ + +static void s_k_bool(struct serializer *s, const char *key, bool x) +{ + s_key(s, key); + s_bool(s, x); +} + +static void s_k_int(struct serializer *s, const char *key, int64_t x) +{ + s_key(s, key); + s_int(s, x); +} + +static void s_k_str(struct serializer *s, const char *key, const char *str) +{ + s_key(s, key); + s_str(s, str); +} + +/* ========================================================================== */ + +#define CHECK_NULL(ptr) \ +do { \ + if (!(ptr)) { \ + s_ptr(s, NULL); \ + return; \ + } \ +} while (false) + +static void dump_props(struct serializer *s, const struct pw_properties *props) +{ + CHECK_NULL(props); + + s_object(s, { + const struct spa_dict_item *item; + spa_dict_for_each (item, &props->dict) + s_k_str(s, item->key, item->value); + }); +} + +static void dump_source(struct serializer *s, const struct spa_source *source) +{ + CHECK_NULL(source); + + s_object(s, { + s_k_int(s, "fd", source->fd); + s_k_int(s, "mask", source->mask); + }); +} + +static void dump_message(struct serializer *s, const struct message *m) +{ + CHECK_NULL(m); + + s_object(s, { + s_k_int(s, "channel", m->channel); + s_k_int(s, "length", m->length); + s_k_int(s, "allocated", m->allocated); + s_k_array(s, "extra", { + s_int(s, m->extra[0]); + s_int(s, m->extra[1]); + s_int(s, m->extra[2]); + s_int(s, m->extra[3]); + }); + }); +} + +static void dump_channel_map(struct serializer *s, const struct channel_map *cm) +{ + CHECK_NULL(cm); + + s_object(s, { + s_k_int(s, "channels", cm->channels); + s_k_array(s, "map", { + for (size_t i = 0; i < cm->channels; i++) + s_int(s, cm->map[i]); + }); + }); +} + +static void dump_sample_spec(struct serializer *s, const struct sample_spec *ss) +{ + CHECK_NULL(ss); + + s_object(s, { + s_k_int(s, "channels", ss->channels); + s_k_int(s, "format", ss->format); + s_k_int(s, "rate", ss->rate); + }); +} + +static void dump_volume(struct serializer *s, const struct volume *volume) +{ + CHECK_NULL(volume); + + s_object(s, { + s_k_int(s, "channels", volume->channels); + s_k_array(s, "values", { + for (size_t i = 0; i < volume->channels; i++) + s_float(s, volume->values[i]); + }); + }); +} + +static void dump_sample(struct serializer *s, const struct sample *sample) +{ + CHECK_NULL(sample); + + s_object(s, { + s_k_int(s, "ref", sample->ref); + s_k_int(s, "index", sample->index); + s_k_str(s, "name", sample->name); + s_k_int(s, "length", sample->length); + s_key(s, "map"); dump_channel_map(s, &sample->map); + s_key(s, "ss"); dump_sample_spec(s, &sample->ss); + s_key(s, "props"); dump_props(s, sample->props); + }); +} + +static void dump_pw_stream(struct serializer *s, struct pw_stream *stream) +{ + CHECK_NULL(stream); + + s_object(s, { + const char *error; + enum pw_stream_state state = pw_stream_get_state(stream, &error); + + s_k_array(s, "state", { + s_int(s, state); + s_str(s, pw_stream_state_as_string(state)); + s_str(s, error); + }); + + s_k_str(s, "name", pw_stream_get_name(stream)); + s_k_int(s, "node-id", pw_stream_get_node_id(stream)); + + s_key(s, "props"); dump_props(s, pw_stream_get_properties(stream)); + }); +} + +static void dump_stream(struct serializer *s, const struct stream *stream) +{ + CHECK_NULL(stream); + + s_object(s, { + s_k_int(s, "id", stream->id); + s_k_int(s, "channel", stream->channel); + s_k_int(s, "create_tag", stream->create_tag); + + s_k_int(s, "type", stream->type); + s_k_int(s, "direction", stream->direction); + + s_k_object(s, "attr", { + s_k_int(s, "maxlength", stream->attr.maxlength); + s_k_int(s, "tlength", stream->attr.tlength); + s_k_int(s, "prebuf", stream->attr.prebuf); + s_k_int(s, "minreq", stream->attr.minreq); + s_k_int(s, "fragsize", stream->attr.fragsize); + }); + + s_k_int(s, "read_index", stream->read_index); + s_k_int(s, "write_index", stream->write_index); + + s_k_int(s, "underrun_for", stream->underrun_for); + s_k_bool(s, "is_underrun", stream->is_underrun); + s_k_int(s, "playing_for", stream->playing_for); + + s_k_int(s, "requested", stream->requested); + s_k_int(s, "last_quantum", stream->last_quantum); + s_k_int(s, "delay", stream->delay); + s_k_int(s, "timestamp", stream->timestamp); + + s_k_int(s, "frame_size", stream->frame_size); + s_k_int(s, "rate", stream->rate); + + s_key(s, "map"); dump_channel_map(s, &stream->map); + s_key(s, "ss"); dump_sample_spec(s, &stream->ss); + s_key(s, "volume"); dump_volume(s, &stream->volume); + + s_k_bool(s, "muted", stream->muted); + s_k_bool(s, "volume_set", stream->volume_set); + s_k_bool(s, "muted_set", stream->muted_set); + + s_k_bool(s, "corked", stream->corked); + + s_k_int(s, "drain_tag", stream->drain_tag); + s_k_bool(s, "draining", stream->draining); + + s_k_bool(s, "early_requests", stream->early_requests); + s_k_bool(s, "adjust_latency", stream->adjust_latency); + + s_k_bool(s, "in_prebuf", stream->in_prebuf); + s_k_bool(s, "killed", stream->killed); + s_k_bool(s, "pending", stream->pending); + + s_key(s, "stream"); dump_pw_stream(s, stream->stream); + + s_key(s, "props"); dump_props(s, stream->props); + }); +} + +static void dump_client(struct serializer *s, const struct client *client) +{ + CHECK_NULL(client); + + s_object(s, { + s_k_int(s, "ref", client->ref); + s_k_str(s, "name", client->name); + + s_key(s, "source"); dump_source(s, client->source); + + s_k_int(s, "version", client->version); + s_k_int(s, "quirks", client->quirks); + + s_k_array(s, "quirks", { + s_int(s, client->quirks); + s_array(s, { + if (client->quirks & QUIRK_FORCE_S16_FORMAT) + s_str(s, "force-s16-info"); + if (client->quirks & QUIRK_REMOVE_CAPTURE_DONT_MOVE) + s_str(s, "remove-capture-dont-move"); + if (client->quirks & QUIRK_BLOCK_SOURCE_VOLUME) + s_str(s, "block-source-volume"); + if (client->quirks & QUIRK_BLOCK_SINK_VOLUME) + s_str(s, "block-sink-volume"); + }); + }); + + s_k_array(s, "subscribed", { + s_int(s, client->subscribed); + s_array(s, { + if (client->subscribed & SUBSCRIPTION_MASK_SINK) + s_str(s, "sink"); + if (client->subscribed & SUBSCRIPTION_MASK_SOURCE) + s_str(s, "source"); + if (client->subscribed & SUBSCRIPTION_MASK_SINK_INPUT) + s_str(s, "sink-input"); + if (client->subscribed & SUBSCRIPTION_MASK_SOURCE_OUTPUT) + s_str(s, "source-output"); + if (client->subscribed & SUBSCRIPTION_MASK_MODULE) + s_str(s, "module"); + if (client->subscribed & SUBSCRIPTION_MASK_CLIENT) + s_str(s, "client"); + if (client->subscribed & SUBSCRIPTION_MASK_SAMPLE_CACHE) + s_str(s, "sample-cache"); + if (client->subscribed & SUBSCRIPTION_MASK_SERVER) + s_str(s, "server"); + if (client->subscribed & SUBSCRIPTION_MASK_AUTOLOAD) + s_str(s, "autoload"); + if (client->subscribed & SUBSCRIPTION_MASK_CARD) + s_str(s, "card"); + }); + }); + + s_k_int(s, "in_index", client->in_index); + s_k_int(s, "out_index", client->out_index); + + s_k_bool(s, "authenticated", client->authenticated); + s_k_bool(s, "disconnected", client->disconnect); + + s_k_int(s, "connect_tag", client->connect_tag); + + s_k_str(s, "default_sink", client->default_sink); + s_k_str(s, "default_source", client->default_source); + s_key(s, "routes"); dump_props(s, client->routes); + + s_key(s, "message"); dump_message(s, client->message); + + s_k_array(s, "out_messages", { + const struct message *m; + spa_list_for_each(m, &client->out_messages, link) + dump_message(s, m); + }); + + s_k_array(s, "streams", { + size_t size = pw_map_get_size(&client->streams); + for (size_t i = 0; i < size; i++) { + const struct stream *stream = pw_map_lookup(&client->streams, i); + if (stream) + dump_stream(s, stream); + } + }); + + s_k_array(s, "operations", { + const struct operation *o; + spa_list_for_each(o, &client->operations, link) { + s_object(s, { + s_k_int(s, "tag", o->tag); + }); + } + }); + + s_k_array(s, "pending_samples", { + const struct pending_sample *ps; + spa_list_for_each(ps, &client->pending_samples, link) { + s_object(s, { + s_k_int(s, "tag", ps->tag); + s_k_object(s, "play", { + s_k_int(s, "id", ps->play->id); + s_k_int(s, "offset", ps->play->offset); + s_k_int(s, "stride", ps->play->stride); + s_k_object(s, "sample", { + s_k_int(s, "ref", ps->play->sample->ref); + s_k_int(s, "index", ps->play->sample->index); + s_k_str(s, "name", ps->play->sample->name); + s_k_int(s, "length", ps->play->sample->length); + }); + }); + }); + } + }); + + s_key(s, "props"); dump_props(s, client->props); + }); +} + +static void dump_server(struct serializer *s, const struct server *server) +{ + CHECK_NULL(server); + + s_object(s, { + s_k_int(s, "af", server->addr.ss_family); + + s_key(s, "source"); dump_source(s, server->source); + + s_k_bool(s, "activated", server->activated); + + s_k_object(s, "client_stats", { + s_k_int(s, "current", server->n_clients); + s_k_int(s, "max", server->max_clients); + s_k_int(s, "waiting", server->wait_clients); + }); + + s_k_int(s, "listen_backlog", server->listen_backlog); + s_k_str(s, "client_access", server->client_access); + + s_k_array(s, "clients", { + const struct client *client; + spa_list_for_each(client, &server->clients, link) + dump_client(s, client); + }); + }); +} + +static void dump_module(struct serializer *s, const struct module *module) +{ + CHECK_NULL(module); + + s_object(s, { + s_k_int(s, "index", module->index); + + s_k_object(s, "info", { + s_k_str(s, "name", module->info->name); + s_k_bool(s, "load_once", module->info->load_once); + }); + + s_k_bool(s, "loaded", module->loaded); + s_k_bool(s, "unloading", module->unloading); + s_k_str(s, "args", module->args); + s_key(s, "props"); dump_props(s, module->props); + }); +} + +static void dump_frac(struct serializer *s, const char *key, const struct spa_fraction *frac) +{ + CHECK_NULL(frac); + + s_k_array(s, key, { + s_int(s, frac->num); + s_int(s, frac->denom); + }); +} + +static void dump_impl(struct serializer *s, const struct impl *impl) +{ + CHECK_NULL(impl); + + s_object(s, { + s_k_object(s, "stats", { + s_k_int(s, "allocated", impl->stat.allocated); + s_k_int(s, "n_allocated", impl->stat.n_allocated); + s_k_int(s, "accumulated", impl->stat.accumulated); + s_k_int(s, "n_accumulated", impl->stat.n_accumulated); + s_k_int(s, "sample_cache", impl->stat.sample_cache); + }); + + s_k_object(s, "defs", { + dump_frac(s, "min_req", &impl->defs.min_req); + dump_frac(s, "default_req", &impl->defs.default_req); + + dump_frac(s, "min_frag", &impl->defs.min_frag); + dump_frac(s, "default_frag", &impl->defs.default_frag); + + dump_frac(s, "default_tlength", &impl->defs.default_tlength); + dump_frac(s, "min_quantum", &impl->defs.min_quantum); + + s_key(s, "sample_spec"); dump_sample_spec(s, &impl->defs.sample_spec); + s_key(s, "channel_map"); dump_channel_map(s, &impl->defs.channel_map); + + s_k_int(s, "quantum_limit", impl->defs.quantum_limit); + }); + + s_k_array(s, "servers", { + const struct server *server; + spa_list_for_each(server, &impl->servers, link) + dump_server(s, server); + }); + + s_k_array(s, "modules", { + size_t size = pw_map_get_size(&impl->modules); + for (size_t i = 0; i < size; i++) { + const struct module *module = pw_map_lookup(&impl->modules, i); + if (module) + dump_module(s, module); + } + }); + + s_k_array(s, "samples", { + size_t size = pw_map_get_size(&impl->samples); + for (size_t i = 0; i < size; i++) { + const struct sample *sample = pw_map_lookup(&impl->samples, i); + if (sample) + dump_sample(s, sample); + } + }); + + s_k_array(s, "cleanup_clients", { + const struct client *client; + spa_list_for_each(client, &impl->cleanup_clients, link) + dump_client(s, client); + }); + + s_k_array(s, "free_messages", { + const struct message *m; + spa_list_for_each(m, &impl->free_messages, link) + dump_message(s, m); + }); + + s_key(s, "props"); dump_props(s, impl->props); + }); +} + +void dump_state(const struct impl *impl, FILE *target) +{ + struct serializer s = { + .target = target, + .current = &s.first, + }; + + dump_impl(&s, impl); +} diff --git a/src/modules/module-protocol-pulse/debug.h b/src/modules/module-protocol-pulse/debug.h new file mode 100644 index 000000000..eb72bb18e --- /dev/null +++ b/src/modules/module-protocol-pulse/debug.h @@ -0,0 +1,10 @@ +#ifndef PULSE_SERVER_DEBUG_H +#define PULSE_SERVER_DEBUG_H + +#include + +struct impl; + +void dump_state(const struct impl *impl, FILE *target); + +#endif /* PULSE_SERVER_DEBUG_H */ From b0163a44a6bfc102ee22555266a2a637029adb3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Thu, 20 Jan 2022 18:14:35 +0100 Subject: [PATCH 2/2] pulse-server: add message handler to dump state When the core object receives the 'internal:dump-state' message with any parameters, it will use the previously added debug facilities to dump the internal state of the server and send it back to the client. --- src/modules/module-protocol-pulse/message-handler.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/module-protocol-pulse/message-handler.c b/src/modules/module-protocol-pulse/message-handler.c index 07132c102..9274fef16 100644 --- a/src/modules/module-protocol-pulse/message-handler.c +++ b/src/modules/module-protocol-pulse/message-handler.c @@ -19,6 +19,7 @@ #include "client.h" #include "collect.h" +#include "debug.h" #include "log.h" #include "manager.h" #include "message-handler.h" @@ -114,6 +115,8 @@ static int core_object_message_handler(struct client *client, struct pw_manager_ int res = malloc_trim(0); fprintf(response, "%d", res); #endif + } else if (spa_streq(message, "pipewire-pulse:dump-state")) { + dump_state(client->impl, response); } else { return -ENOSYS; }