Created
June 7, 2023 15:25
-
-
Save dimtpap/cc670ae7c613fbb886aa1c7adc316da7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* PipeWire */ | |
/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ | |
/* SPDX-License-Identifier: MIT */ | |
#include <stdio.h> | |
#include <signal.h> | |
#include <getopt.h> | |
#include <locale.h> | |
#include <spa/utils/result.h> | |
#include <spa/utils/string.h> | |
#include <spa/pod/parser.h> | |
#include <spa/debug/types.h> | |
#include <pipewire/impl.h> | |
#include <pipewire/extensions/profiler.h> | |
#define MAX_NAME 128 | |
#define MAX_FOLLOWERS 64 | |
#define DEFAULT_FILENAME "profiler.log" | |
struct follower { | |
uint32_t id; | |
char name[MAX_NAME]; | |
}; | |
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; | |
int64_t count; | |
int64_t start_status; | |
int64_t last_status; | |
struct pw_proxy *profiler; | |
struct spa_hook profiler_listener; | |
int check_profiler; | |
uint32_t driver_id; | |
int n_followers; | |
struct follower followers[MAX_FOLLOWERS]; | |
}; | |
struct measurement { | |
int64_t period; | |
int64_t prev_signal; | |
int64_t signal; | |
int64_t awake; | |
int64_t finish; | |
int32_t status; | |
}; | |
struct point { | |
int64_t count; | |
float cpu_load[3]; | |
struct spa_io_clock clock; | |
struct measurement driver; | |
struct measurement follower[MAX_FOLLOWERS]; | |
}; | |
static int process_info(struct data *d, const struct spa_pod *pod, struct point *point) | |
{ | |
return 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])); | |
} | |
static int process_clock(struct data *d, const struct spa_pod *pod, struct point *point) | |
{ | |
return 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)); | |
} | |
static int process_driver_block(struct data *d, const struct spa_pod *pod, struct point *point) | |
{ | |
char *name = NULL; | |
uint32_t driver_id = 0; | |
struct measurement driver; | |
int res; | |
spa_zero(driver); | |
if ((res = spa_pod_parse_struct(pod, | |
SPA_POD_Int(&driver_id), | |
SPA_POD_String(&name), | |
SPA_POD_Long(&driver.prev_signal), | |
SPA_POD_Long(&driver.signal), | |
SPA_POD_Long(&driver.awake), | |
SPA_POD_Long(&driver.finish), | |
SPA_POD_Int(&driver.status))) < 0) | |
return res; | |
if (d->driver_id == 0) { | |
d->driver_id = driver_id; | |
printf("logging driver %u\n", driver_id); | |
} | |
else if (d->driver_id != driver_id) | |
return -1; | |
point->driver = driver; | |
return 0; | |
} | |
static int find_follower(struct data *d, uint32_t id, const char *name) | |
{ | |
int i; | |
for (i = 0; i < d->n_followers; i++) { | |
if (d->followers[i].id == id && spa_streq(d->followers[i].name, name)) | |
return i; | |
} | |
return -1; | |
} | |
static int add_follower(struct data *d, uint32_t id, const char *name) | |
{ | |
int idx = d->n_followers; | |
if (idx == MAX_FOLLOWERS) | |
return -1; | |
d->n_followers++; | |
strncpy(d->followers[idx].name, name, MAX_NAME); | |
d->followers[idx].name[MAX_NAME-1] = '\0'; | |
d->followers[idx].id = id; | |
printf("logging follower %u (\"%s\")\n", id, name); | |
return idx; | |
} | |
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 res, idx; | |
spa_zero(m); | |
if ((res = 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))) < 0) | |
return res; | |
printf("Follower ID: %u\n", id); | |
printf("Follower Name: %s\n", name); | |
printf("Prev signal: %lld\n", m.prev_signal); | |
printf("Signal: %lld\n", m.signal); | |
printf("Awake: %lld\n", m.awake); | |
printf("Finish: %lld\n", m.finish); | |
printf("Status: %d\n", m.status); | |
if ((idx = find_follower(d, id, name)) < 0) { | |
if ((idx = add_follower(d, id, name)) < 0) { | |
pw_log_warn("too many followers"); | |
return -ENOSPC; | |
} | |
} | |
point->follower[idx] = m; | |
return 0; | |
} | |
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; | |
printf("--- Profile callback ---\n"); | |
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; | |
} | |
} | |
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 (!spa_streq(type, PW_TYPE_INTERFACE_Profiler)) | |
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; | |
printf("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 && res == -EPIPE) | |
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, bool error) | |
{ | |
fprintf(error ? stderr : stdout, "%s [options]\n" | |
" -h, --help Show this help\n" | |
" --version Show version\n" | |
" -r, --remote Remote daemon name\n" | |
" -o, --output Profiler output name (default \"%s\")\n", | |
name, | |
DEFAULT_FILENAME); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
struct data data = { 0 }; | |
struct pw_loop *l; | |
const char *opt_remote = NULL; | |
const char *opt_output = DEFAULT_FILENAME; | |
static const struct option long_options[] = { | |
{ "help", no_argument, NULL, 'h' }, | |
{ "version", no_argument, NULL, 'V' }, | |
{ "remote", required_argument, NULL, 'r' }, | |
{ "output", required_argument, NULL, 'o' }, | |
{ NULL, 0, NULL, 0} | |
}; | |
int c; | |
setlocale(LC_ALL, ""); | |
pw_init(&argc, &argv); | |
while ((c = getopt_long(argc, argv, "hVr:o:", long_options, NULL)) != -1) { | |
switch (c) { | |
case 'h': | |
show_help(argv[0], false); | |
return 0; | |
case 'V': | |
printf("%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 'o': | |
opt_output = optarg; | |
break; | |
case 'r': | |
opt_remote = optarg; | |
break; | |
default: | |
show_help(argv[0], true); | |
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, | |
®istry_events, &data); | |
data.check_profiler = pw_core_sync(data.core, 0, 0); | |
pw_main_loop_run(data.loop); | |
if (data.profiler) { | |
spa_hook_remove(&data.profiler_listener); | |
pw_proxy_destroy((struct pw_proxy*)data.profiler); | |
} | |
spa_hook_remove(&data.registry_listener); | |
pw_proxy_destroy((struct pw_proxy*)data.registry); | |
spa_hook_remove(&data.core_listener); | |
pw_context_destroy(data.context); | |
pw_main_loop_destroy(data.loop); | |
pw_deinit(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment