diff --git a/include/performance.h b/include/performance.h new file mode 100644 index 0000000..5aa707e --- /dev/null +++ b/include/performance.h @@ -0,0 +1,30 @@ +#pragma once + +#define PERF_FRAME_LATENCY_SAMPLE_SIZE 60 + +struct perf_sample_stats { + double min, max, average; +}; + +struct perf_sample_buffer { + int length; + int count; + int index; + double samples[]; +}; + +struct perf { + struct perf_sample_buffer* frame_latency; +}; + +struct perf_sample_buffer* perf_sample_buffer_create(int length); + +void perf_sample_buffer_add(struct perf_sample_buffer* self, double sample); + +void perf_sample_buffer_get_stats(const struct perf_sample_buffer* self, + struct perf_sample_stats* stats); + +void perf_init(struct perf*); +void perf_deinit(struct perf*); + +void perf_dump_latency_report(const struct perf*); diff --git a/meson.build b/meson.build index d4ae9c4..95f177d 100644 --- a/meson.build +++ b/meson.build @@ -82,6 +82,8 @@ sources = [ 'src/rfbproto.c', 'src/sockets.c', 'src/vncviewer.c', + 'src/ntp.c', + 'src/performance.c', ] dependencies = [ diff --git a/src/main.c b/src/main.c index 82b0688..8cd923e 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 - 2022 Andri Yngvason + * Copyright (c) 2020 - 2023 Andri Yngvason * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -47,9 +47,12 @@ #include "linux-dmabuf-unstable-v1.h" #include "time-util.h" #include "output.h" +#include "ntp.h" +#include "performance.h" #define CANARY_TICK_PERIOD INT64_C(100000) // us #define CANARY_LETHALITY_LEVEL INT64_C(8000) // us +#define LATENCY_REPORT_PERIOD INT64_C(250000) // us struct point { double x, y; @@ -87,6 +90,8 @@ struct pointer_collection* pointers; struct keyboard_collection* keyboards; static int drm_fd = -1; static uint64_t last_canary_tick; +static struct ntp_client ntp; +static struct perf perf; static bool have_egl = false; @@ -608,6 +613,20 @@ static void window_damage_region(struct window* w, } } +static void update_frame_latency_stats(void) +{ + uint32_t server_pts = window->vnc->pts; + + uint32_t client_pts = 0; + if (!ntp_client_translate_server_time(&ntp, &client_pts, server_pts)) + return; + + uint32_t now = gettime_us(); + int32_t latency = (int32_t)(now - client_pts); + + perf_sample_buffer_add(perf.frame_latency, latency); +} + static void render_from_vnc(void) { if (!pixman_region_not_empty(&window->current_damage) && @@ -653,6 +672,8 @@ static void render_from_vnc(void) window_commit(window); window_swap(window); + update_frame_latency_stats(); + pixman_region_clear(&window->current_damage); vnc_client_clear_av_frames(window->vnc); } @@ -804,6 +825,18 @@ static void on_canary_tick(void* obj) delay); } +static void send_ntp_ping(struct ntp_client* ntp_client, uint32_t t0, + uint32_t t1, uint32_t t2, uint32_t t3) +{ + vnc_client_send_ntp_event(window->vnc, t0, t1, t2, t3); +} + +static void on_ntp_event(struct vnc_client* vnc, uint32_t t0, uint32_t t1, + uint32_t t2, uint32_t t3) +{ + ntp_client_process_pong(&ntp, t0, t1, t2, t3); +} + static void create_canary_ticker(void) { last_canary_tick = gettime_us(); @@ -815,6 +848,22 @@ static void create_canary_ticker(void) aml_unref(ticker); } +static void on_latency_report_tick(void* handler) +{ + (void)handler; + + perf_dump_latency_report(&perf); +} + +static void create_latency_report_ticker(void) +{ + struct aml* aml = aml_get_default(); + struct aml_ticker* ticker = aml_ticker_new(LATENCY_REPORT_PERIOD, + on_latency_report_tick, NULL, NULL); + aml_start(aml, ticker); + aml_unref(ticker); +} + void run_main_loop_once(void) { struct aml* aml = aml_get_default(); @@ -968,8 +1017,11 @@ int main(int argc, char* argv[]) if (!vnc) goto vnc_failure; + vnc->userdata = window; + vnc->alloc_fb = on_vnc_client_alloc_fb; vnc->update_fb = on_vnc_client_update_fb; + vnc->ntp_event = on_ntp_event; if (vnc_client_set_pixel_format(vnc, shm_format) < 0) { fprintf(stderr, "Unsupported pixel format\n"); @@ -1008,12 +1060,16 @@ int main(int argc, char* argv[]) goto vnc_setup_failure; } + perf_init(&perf); + ntp_client_init(&ntp, send_ntp_ping, window); + pointers->userdata = vnc; keyboards->userdata = vnc; wl_display_dispatch(wl_display); create_canary_ticker(); + create_latency_report_ticker(); while (do_run) run_main_loop_once(); @@ -1021,6 +1077,10 @@ int main(int argc, char* argv[]) rc = 0; if (window) window_destroy(window); + + ntp_client_deinit(&ntp); + perf_deinit(&perf); + vnc_setup_failure: vnc_client_destroy(vnc); vnc_failure: diff --git a/src/performance.c b/src/performance.c new file mode 100644 index 0000000..1d7840e --- /dev/null +++ b/src/performance.c @@ -0,0 +1,72 @@ +#include "performance.h" + +#include +#include +#include +#include + +struct perf_sample_buffer* perf_sample_buffer_create(int length) +{ + struct perf_sample_buffer* self = malloc(sizeof(*self) + + length * sizeof(self->samples[0])); + self->count = 0; + self->index = 0; + self->length = length; + return self; +} + +void perf_sample_buffer_add(struct perf_sample_buffer* self, double sample) +{ + self->samples[self->index] = sample; + self->index = (self->index + 1) % self->length; + if (self->count < self->length) + self->count += 1; +} + +void perf_sample_buffer_get_stats(const struct perf_sample_buffer* self, + struct perf_sample_stats* stats) +{ + memset(stats, 0, sizeof(*stats)); + + double sum = 0; + double minimum = INFINITY; + double maximum = 0; + + for (int i = 0; i < self->count; ++i) { + double sample = self->samples[i]; + + sum += sample; + + if (sample < minimum) + minimum = sample; + + if (sample > maximum) + maximum = sample; + } + + stats->min = minimum; + stats->max = maximum; + stats->average = sum / (double)self->count; +} + +void perf_init(struct perf* self) +{ + memset(self, 0, sizeof(*self)); + + self->frame_latency = + perf_sample_buffer_create(PERF_FRAME_LATENCY_SAMPLE_SIZE); +} + +void perf_deinit(struct perf* self) +{ + free(self->frame_latency); +} + +void perf_dump_latency_report(const struct perf* self) +{ + struct perf_sample_stats stats; + perf_sample_buffer_get_stats(self->frame_latency, &stats); + + printf("Latency report: frame-latency (min, avg, max): %.1f, %.1f, %.1f\n", + stats.min / 1e3, stats.average / 1e3, stats.max / 1e3); +}