Merge branch 'pw_pulse_debug' into 'master'

Draft: pulse-server: add facilities to dump internal state

See merge request pipewire/pipewire!1117
This commit is contained in:
Barnabás Pőcze 2024-01-04 10:31:15 +00:00
commit 7c12e821b2
4 changed files with 677 additions and 0 deletions

View file

@ -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',

View file

@ -0,0 +1,663 @@
#include <ctype.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <spa/utils/dict.h>
#include <spa/utils/list.h>
#include <spa/support/loop.h>
#include <pipewire/map.h>
#include <pipewire/properties.h>
#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);
}

View file

@ -0,0 +1,10 @@
#ifndef PULSE_SERVER_DEBUG_H
#define PULSE_SERVER_DEBUG_H
#include <stdio.h>
struct impl;
void dump_state(const struct impl *impl, FILE *target);
#endif /* PULSE_SERVER_DEBUG_H */

View file

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