summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWim Taymans <wtaymans@redhat.com>2020-09-28 21:00:23 +0200
committerWim Taymans <wtaymans@redhat.com>2020-09-30 15:19:43 +0200
commitcac2c84bee136106676cbbe9adea47836bdfa2d6 (patch)
tree33a5a6ae83664b3c751f5cd6a2f8e57fa720f98d
parent41db0f35b3f45b55e62957d9515475c654fce573 (diff)
beginnings of pw-toptop
-rw-r--r--src/tools/meson.build7
-rw-r--r--src/tools/pw-top.c436
2 files changed, 443 insertions, 0 deletions
diff --git a/src/tools/meson.build b/src/tools/meson.build
index f119fc2a..25253921 100644
--- a/src/tools/meson.build
+++ b/src/tools/meson.build
@@ -38,6 +38,13 @@ executable('pw-metadata',
dependencies : [pipewire_dep],
)
+executable('pw-top',
+ 'pw-top.c',
+ c_args : [ '-D_GNU_SOURCE' ],
+ install: true,
+ dependencies : [pipewire_dep],
+)
+
if get_option('pw-cat') and sndfile_dep.found()
pwcat_sources = [
diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c
new file mode 100644
index 00000000..7744c9c4
--- /dev/null
+++ b/src/tools/pw-top.c
@@ -0,0 +1,436 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <getopt.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/parser.h>
+#include <spa/debug/pod.h>
+
+#include <pipewire/impl.h>
+#include <extensions/profiler.h>
+
+#define MAX_NAME 128
+#define MAX_NODES 1024
+#define MAX_FOLLOWERS 1024
+
+struct measurement {
+ int32_t index;
+ int32_t status;
+ int64_t period;
+ int64_t prev_signal;
+ int64_t signal;
+ int64_t awake;
+ int64_t finish;
+};
+
+struct node {
+ uint32_t id;
+ char name[MAX_NAME];
+ struct measurement measurement;
+ int64_t start_status;
+ int64_t last_status;
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct pw_proxy *profiler;
+ struct spa_hook profiler_listener;
+ int check_profiler;
+
+ int n_nodes;
+ struct node nodes[MAX_NODES];
+};
+
+struct point {
+ int64_t count;
+ float cpu_load[3];
+ struct spa_io_clock clock;
+ uint32_t driver;
+ uint32_t n_followers;
+ uint32_t followers[MAX_NODES];
+};
+
+static int process_info(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ spa_pod_parse_struct(pod,
+ SPA_POD_Long(&point->count),
+ SPA_POD_Float(&point->cpu_load[0]),
+ SPA_POD_Float(&point->cpu_load[1]),
+ SPA_POD_Float(&point->cpu_load[2]));
+ return 0;
+}
+
+static int process_clock(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ spa_pod_parse_struct(pod,
+ SPA_POD_Int(&point->clock.flags),
+ SPA_POD_Int(&point->clock.id),
+ SPA_POD_Stringn(point->clock.name, sizeof(point->clock.name)),
+ SPA_POD_Long(&point->clock.nsec),
+ SPA_POD_Fraction(&point->clock.rate),
+ SPA_POD_Long(&point->clock.position),
+ SPA_POD_Long(&point->clock.duration),
+ SPA_POD_Long(&point->clock.delay),
+ SPA_POD_Double(&point->clock.rate_diff),
+ SPA_POD_Long(&point->clock.next_nsec));
+ return 0;
+}
+
+static int find_node(struct data *d, uint32_t id, const char *name)
+{
+ int i;
+ for (i = 0; i < d->n_nodes; i++) {
+ if (d->nodes[i].id == id && strcmp(d->nodes[i].name, name) == 0)
+ return i;
+ }
+ return -1;
+}
+
+static int add_node(struct data *d, uint32_t id, const char *name)
+{
+ int idx = d->n_nodes;
+
+ if (idx == MAX_NODES)
+ return -1;
+
+ d->n_nodes++;
+
+ strncpy(d->nodes[idx].name, name, MAX_NAME);
+ d->nodes[idx].name[MAX_NAME-1] = '\0';
+ d->nodes[idx].id = id;
+ fprintf(stderr, "logging node %u (\"%s\")\n", id, name);
+
+ return idx;
+}
+
+static int process_driver_block(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ char *name = NULL;
+ uint32_t id = 0;
+ struct measurement m;
+ int idx;
+
+ spa_zero(m);
+ spa_pod_parse_struct(pod,
+ SPA_POD_Int(&id),
+ SPA_POD_String(&name),
+ SPA_POD_Long(&m.prev_signal),
+ SPA_POD_Long(&m.signal),
+ SPA_POD_Long(&m.awake),
+ SPA_POD_Long(&m.finish),
+ SPA_POD_Int(&m.status));
+
+ if ((idx = find_node(d, id, name)) < 0) {
+ if ((idx = add_node(d, id, name)) < 0) {
+ pw_log_warn("too many nodes");
+ return -ENOSPC;
+ }
+ }
+ d->nodes[idx].measurement = m;
+ point->driver = idx;
+ return 0;
+}
+
+static int process_follower_block(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ uint32_t id = 0;
+ const char *name = NULL;
+ struct measurement m;
+ int idx;
+
+ spa_zero(m);
+ spa_pod_parse_struct(pod,
+ SPA_POD_Int(&id),
+ SPA_POD_String(&name),
+ SPA_POD_Long(&m.prev_signal),
+ SPA_POD_Long(&m.signal),
+ SPA_POD_Long(&m.awake),
+ SPA_POD_Long(&m.finish),
+ SPA_POD_Int(&m.status));
+
+ if ((idx = find_node(d, id, name)) < 0) {
+ if ((idx = add_node(d, id, name)) < 0) {
+ pw_log_warn("too many followers");
+ return -ENOSPC;
+ }
+ }
+ d->nodes[idx].measurement = m;
+ point->followers[point->n_followers] = idx;
+ point->n_followers++;
+ return 0;
+}
+
+static void dump_point(struct data *d, struct point *point)
+{
+ struct node *driver = &d->nodes[point->driver];
+
+ if (driver->start_status == 0) {
+ driver->start_status = point->clock.nsec;
+ driver->last_status = point->clock.nsec;
+ }
+ else if (point->clock.nsec - driver->last_status > SPA_NSEC_PER_SEC) {
+ uint32_t i;
+
+ fprintf(stderr, "CPU %f %f %f\n\n",
+ point->cpu_load[0], point->cpu_load[1], point->cpu_load[2]);
+ fprintf(stderr, " %u %s\n", driver->id, driver->name);
+
+ fprintf(stderr, " rate:%u/%u position:%"PRIu64" duration:%"PRIu64
+ " delay:%"PRIi64" dll:%f\n",
+ point->clock.rate.num, point->clock.rate.denom,
+ point->clock.position, point->clock.duration,
+ point->clock.delay, point->clock.rate_diff);
+
+ for (i = 0; i < point->n_followers; i++) {
+ struct node *f = &d->nodes[point->followers[i]];
+ fprintf(stderr, " %u + %s\n", f->id, f->name);
+ }
+ driver->last_status = point->clock.nsec;
+ fprintf(stderr, "\n");
+ }
+}
+
+static void profiler_profile(void *data, const struct spa_pod *pod)
+{
+ struct data *d = data;
+ struct spa_pod *o;
+ struct spa_pod_prop *p;
+ struct point point;
+
+ SPA_POD_STRUCT_FOREACH(pod, o) {
+ int res = 0;
+ if (!spa_pod_is_object_type(o, SPA_TYPE_OBJECT_Profiler))
+ continue;
+
+ spa_zero(point);
+ SPA_POD_OBJECT_FOREACH((struct spa_pod_object*)o, p) {
+ switch(p->key) {
+ case SPA_PROFILER_info:
+ res = process_info(d, &p->value, &point);
+ break;
+ case SPA_PROFILER_clock:
+ res = process_clock(d, &p->value, &point);
+ break;
+ case SPA_PROFILER_driverBlock:
+ res = process_driver_block(d, &p->value, &point);
+ break;
+ case SPA_PROFILER_followerBlock:
+ process_follower_block(d, &p->value, &point);
+ break;
+ default:
+ break;
+ }
+ if (res < 0)
+ break;
+ }
+ if (res < 0)
+ continue;
+
+ dump_point(d, &point);
+ }
+}
+
+static const struct pw_profiler_events profiler_events = {
+ PW_VERSION_PROFILER_EVENTS,
+ .profile = profiler_profile,
+};
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct data *d = data;
+ struct pw_proxy *proxy;
+
+ if (strcmp(type, PW_TYPE_INTERFACE_Profiler) != 0)
+ return;
+
+ if (d->profiler != NULL) {
+ fprintf(stderr, "Ignoring profiler %d: already attached\n", id);
+ return;
+ }
+
+ proxy = pw_registry_bind(d->registry, id, type, PW_VERSION_PROFILER, 0);
+ if (proxy == NULL)
+ goto error_proxy;
+
+ fprintf(stderr, "Attaching to Profiler id:%d\n", id);
+ d->profiler = proxy;
+ pw_proxy_add_object_listener(proxy, &d->profiler_listener, &profiler_events, d);
+
+ return;
+
+error_proxy:
+ pw_log_error("failed to create proxy: %m");
+ return;
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+};
+
+static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *data = _data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE)
+ pw_main_loop_quit(data->loop);
+}
+
+static void on_core_done(void *_data, uint32_t id, int seq)
+{
+ struct data *d = _data;
+
+ if (seq == d->check_profiler) {
+ if (d->profiler == NULL) {
+ pw_log_error("no Profiler Interface found, please load one in the server");
+ pw_main_loop_quit(d->loop);
+ }
+ }
+}
+
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+ .done = on_core_done,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+static void show_help(const char *name)
+{
+ fprintf(stdout, "%s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote Remote daemon name\n",
+ name);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+ struct pw_loop *l;
+ const char *opt_remote = NULL;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+
+ pw_init(&argc, &argv);
+
+ while ((c = getopt_long(argc, argv, "hVr:o:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(argv[0]);
+ return 0;
+ case 'V':
+ fprintf(stdout, "%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'r':
+ opt_remote = optarg;
+ break;
+ default:
+ show_help(argv[0]);
+ return -1;
+ }
+ }
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "Can't create data loop: %m\n");
+ return -1;
+ }
+
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+
+ data.context = pw_context_new(l, NULL, 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "Can't create context: %m\n");
+ return -1;
+ }
+
+ pw_context_load_module(data.context, PW_EXTENSION_MODULE_PROFILER, NULL, NULL);
+
+ data.core = pw_context_connect(data.context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, opt_remote,
+ NULL),
+ 0);
+ if (data.core == NULL) {
+ fprintf(stderr, "Can't connect: %m\n");
+ return -1;
+ }
+
+ pw_core_add_listener(data.core,
+ &data.core_listener,
+ &core_events, &data);
+ data.registry = pw_core_get_registry(data.core,
+ PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(data.registry,
+ &data.registry_listener,
+ &registry_events, &data);
+
+ data.check_profiler = pw_core_sync(data.core, 0, 0);
+
+ pw_main_loop_run(data.loop);
+
+ pw_proxy_destroy((struct pw_proxy*)data.profiler);
+ pw_proxy_destroy((struct pw_proxy*)data.registry);
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+
+ pw_deinit();
+
+ return 0;
+}