From e829820ec03f9d75734df3a2694f308e1bd0ce6d Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Thu, 2 Dec 2021 17:57:15 +0100 Subject: [PATCH] Add p2p libdbus latency tests Change-Id: I5fbae009b5a311d2e9b16b3d52f72435cb70a700 Signed-off-by: Michal Bloch --- Makefile | 12 +- benchmark/libdbus-p2p-client.cpp | 163 ++++++++++++++ benchmark/libdbus-p2p-server.cpp | 360 +++++++++++++++++++++++++++++++ packaging/dbus-tools.spec | 2 + 4 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 benchmark/libdbus-p2p-client.cpp create mode 100644 benchmark/libdbus-p2p-server.cpp diff --git a/Makefile b/Makefile index 457b555..786cf13 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,10 @@ LDFLAGS=`pkg-config --libs glib-2.0 dbus-1 gio-2.0` LDFLAGS+=-lpthread -lrt -lm DFTSRC=benchmark/common.c -all: +CXXFLAGS = -std=c++2a -Wall -g `pkg-config --cflags dbus-1` +LDLIBS = -pthread `pkg-config --libs dbus-1` + +all: libdbus-p2p-client libdbus-p2p-server gcc $(CFLAGS) -pg -o benchmark/pipe $(DFTSRC) benchmark/pipe.c $(LDFLAGS) gcc $(CFLAGS) -o benchmark/socket $(DFTSRC) benchmark/socket.c $(LDFLAGS) gcc $(CFLAGS) -o benchmark/sharedmem $(DFTSRC) benchmark/sharedmem.c $(LDFLAGS) @@ -13,6 +16,11 @@ all: gcc $(CFLAGS) -o benchmark/p2p-gdbus $(DFTSRC) benchmark/p2p-gdbus.c $(LDFLAGS) gcc $(CFLAGS) -o benchmark/libdbus $(DFTSRC) benchmark/libdbus.c $(LDFLAGS) +libdbus-p2p-client: benchmark/libdbus-p2p-client.cpp + $(CXX) $(CXXFLAGS) $^ $(LDLIBS) -o benchmark/$@ + +libdbus-p2p-server: benchmark/libdbus-p2p-server.cpp + $(CXX) $(CXXFLAGS) $^ $(LDLIBS) -o benchmark/$@ install: make -C policychecker install @@ -22,4 +30,6 @@ install: cp benchmark/sharedmem $(DESTDIR)/usr/bin/sharedmem cp benchmark/gdbus $(DESTDIR)/usr/bin/gdbus cp benchmark/p2p-gdbus $(DESTDIR)/usr/bin/p2p-gdbus + cp benchmark/libdbus-p2p-server $(DESTDIR)/usr/bin/libdbus-p2p-server + cp benchmark/libdbus-p2p-client $(DESTDIR)/usr/bin/libdbus-p2p-client cp benchmark/libdbus $(DESTDIR)/usr/bin/libdbus diff --git a/benchmark/libdbus-p2p-client.cpp b/benchmark/libdbus-p2p-client.cpp new file mode 100644 index 0000000..3f6d780 --- /dev/null +++ b/benchmark/libdbus-p2p-client.cpp @@ -0,0 +1,163 @@ +/* MIT License + * + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * 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 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 + +#include + +#include +#include +#include + +using namespace std::chrono_literals; + +static constexpr const char * TEST_PATH = "/org/test"; +static constexpr const char * TEST_NAME = "org.test"; +static constexpr const char * TEST_METHOD = "Perf"; + +struct run_opts { + bool show_help = false; + + /* The value is mostly conforming to other test's default, + * though a small payload makes a lot of sense since D-Bus + * is a signalling protocol. */ + size_t payload = 3; + + /* 5000us is the default used by the other (daemon-based) test. + * + * It seems like reducing this delay also reduces the latency; my + * theory is that this prevents the CPU from "wandering off" to do + * something else, invalidating the cache, and coming back late. + * + * On the other hand, the extra delay can "stack" if it is lower + * than the processing time and distort the result the opposite way. + * + * Keep this in mind when tweaking. */ + std::chrono::microseconds delay = 5000us; + + /* ~10 minutes considering the default delay. Can afford to be + * fairly large since the server handles a broken connection well + * and will just take however many tries it managed to receive. */ + size_t try_count = 120'000; + + /* Matches the server's default for convenience. */ + std::string sock_path = "/tmp/test"; +}; + +run_opts get_opt (int argc, char ** argv) +{ + run_opts ret; + + int opt; + while ((opt = getopt(argc, argv, "d:m:t:p:h")) != -1) + switch (opt) { + + case 'm': + ret.payload = std::stoi(optarg); + break; + case 't': + ret.try_count = std::stoi(optarg); + break; + case 'd': + ret.delay = std::chrono::microseconds(std::stoi(optarg)); + break; + case 'p': + ret.sock_path = optarg; + break; + case 'h': + default: + ret.show_help = true; + break; + } + + return ret; +} + +void show_help (const char * argv0) +{ + std::cout << "Usage: " << argv0 + << " [-m message_size] " + << " [-t try_count] " + << " [-d delay_usec] " + << " [-p sock_path] " + << std::endl + ; +} + +int main (int argc, char ** argv) +{ + run_opts opt = get_opt (argc, argv); + + if (opt.show_help) { + show_help (argv[0]); + return EXIT_FAILURE; + } + + auto sock_path = "unix:path=" + opt.sock_path; + auto conn = dbus_connection_open_private (sock_path.c_str(), nullptr); + + /* Not something fancy like a unique_ptr or a vector, + * because `dbus_message_append_args` wants the address + * of the pointer and those just return a temporary */ + auto message = new char [opt.payload]; + + for (size_t i = 0; i < opt.try_count; ++i) { + auto * const msg = dbus_message_new_method_call + ( TEST_NAME + , TEST_PATH + , TEST_NAME + , TEST_METHOD + ); + + dbus_message_set_no_reply (msg, TRUE); + + /* Append first, we want as little of this sort of + * overhead performed after the timestamp has been + * taken as possible. */ + dbus_message_append_args (msg + , DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, & message, opt.payload + , DBUS_TYPE_INVALID + ); + + dbus_uint64_t ts = std::chrono::duration_cast + (std::chrono::high_resolution_clock::now().time_since_epoch()) + .count() + ; + + dbus_message_append_args (msg + , DBUS_TYPE_UINT64, & ts + , DBUS_TYPE_INVALID + ); + + dbus_connection_send (conn, msg, nullptr); + dbus_message_unref (msg); + + std::this_thread::sleep_for (opt.delay); + } + + delete [] message; + + dbus_connection_close (conn); + dbus_connection_unref (conn); + + return EXIT_SUCCESS; +} diff --git a/benchmark/libdbus-p2p-server.cpp b/benchmark/libdbus-p2p-server.cpp new file mode 100644 index 0000000..44cf307 --- /dev/null +++ b/benchmark/libdbus-p2p-server.cpp @@ -0,0 +1,360 @@ +/* MIT License + * + * Copyright (c) 2021 Samsung Electronics Co., Ltd. + * + * 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 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 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr const char * TEST_PATH = "/org/test"; + +static DBusServer * server; + +void exit_func () +{ + /* `server` guaranteed non-null + * via atexit() timing. */ + + dbus_server_disconnect (server); + dbus_server_unref (server); +} + +extern "C" void sig_handler (int sig) +{ + /* SIG_DFL doesn't seem to run + * the atexit() handlers. */ + + std::exit (EXIT_SUCCESS); +} + +struct run_opts { + bool show_help = false; + + /* With the default client settings, this is 5 seconds' worth of + * messages and less than 1% of total log count. Note that the + * client does not send extra warm-up logs, so this value is + * subtracted from the total and should not be lower than it. */ + size_t warmup_tries = 1'000; + + /* The test makes no attempt to check whether there is sufficient + * access level (etc.) to the path so should be manually changed + * if the default is not available. Additionally, running many + * servers in parallel requires changing the path on the extras. */ + std::string sock_path = "/tmp/test"; + + /* When enabled, the server won't quit after it has no connections + * after having handled at least one connection. */ + bool infinite_listening = false; + + /* When enabled, the server busy-waits on a connection. This makes + * the results comparable and reproducible (no random extra delays + * caused by the scheduler, less likely that cache gets invalidated + * etc), but also makes the test correspond worse to real-life use. + * + * Good for %-based comparisons, but avoid for time measurements. */ + bool busy_wait = false; +}; + +run_opts get_opt (int argc, char ** argv) +{ + run_opts ret; + + int opt; + while ((opt = getopt(argc, argv, "wit:p:h")) != -1) + switch (opt) { + + case 't': + ret.warmup_tries = std::stoi(optarg); + break; + case 'w': + ret.busy_wait = true; + break; + case 'i': + ret.infinite_listening = true; + break; + case 'p': + ret.sock_path = optarg; + break; + case 'h': + default: + ret.show_help = true; + break; + } + + return ret; +} + +struct DBusConnection_destructor { + void operator () (DBusConnection * conn) { + dbus_connection_unregister_object_path (conn, TEST_PATH); + dbus_connection_close (conn); + dbus_connection_unref (conn); + } +}; + +struct conn_with_metadata { + dbus_uint64_t total_time; + dbus_uint64_t total_count; + + dbus_uint64_t min_time; + dbus_uint64_t max_time; + + size_t warmup_tries; + + std::unique_ptr conn; + + bool finished; + + static DBusObjectPathVTable vtable; + + conn_with_metadata (const run_opts & ro, DBusConnection * c) + : total_time (0) + , total_count (0) + , min_time (std::numeric_limits :: max ()) + , max_time (std::numeric_limits :: min ()) + , warmup_tries (ro.warmup_tries) + , conn (c) + , finished (false) + { + dbus_connection_ref (c); + + dbus_connection_register_object_path + ( conn.get() + , TEST_PATH + , & vtable + , this + ); + } + + conn_with_metadata (conn_with_metadata &&x) + : total_time (std::move(x.total_time)) + , total_count (std::move(x.total_count)) + , min_time (std::move(x.min_time)) + , max_time (std::move(x.max_time)) + , warmup_tries (std::move(x.warmup_tries)) + , conn (std::move(x.conn)) + , finished (std::move(x.finished)) + { + /* Refresh the userdata reference, + * since `this` effectively changes. */ + + dbus_connection_unregister_object_path + ( conn.get() + , TEST_PATH + ); + + dbus_connection_register_object_path + ( conn.get() + , TEST_PATH + , & vtable + , this + ); + } + + conn_with_metadata & operator = (conn_with_metadata &&x) + { + /* This is very sad, I don't have the mana + * to implement something better. */ + + total_time = std::move(x.total_time); + total_count = std::move(x.total_count); + min_time = std::move(x.min_time); + max_time = std::move(x.max_time); + warmup_tries = std::move(x.warmup_tries); + conn = std::move(x.conn); + finished = std::move(x.finished); + + dbus_connection_unregister_object_path + ( conn.get() + , TEST_PATH + ); + dbus_connection_register_object_path + ( conn.get() + , TEST_PATH + , & vtable + , this + ); + + return *this; + } + + ~ conn_with_metadata () = default; +}; + +DBusObjectPathVTable conn_with_metadata::vtable = + { [] (DBusConnection * c, void * userdata) -> void { + /* Unregistering and disconnection handler. + * Empty since we don't unregister until closing, + * and the destructor handles disconnection. */ + } + + , [] (DBusConnection * c, DBusMessage * msg, void * userdata) -> DBusHandlerResult { + + /* Couldn't use the real `this`, even if this was inside the class proper, + * because that would be capturing a variable, which prevents the lambda + * from being convertible to a function pointer (as required by the vtable),*/ + auto THIS = reinterpret_cast (userdata); + + dbus_uint64_t now = std::chrono::duration_cast + (std::chrono::high_resolution_clock::now().time_since_epoch()) + .count() + ; + + /* These two are garbage that we don't care about, + * but `dbus_message_get_args` doesn't accept nulls */ + char * arr_ptr; + int arr_size; + + dbus_uint64_t ts; + dbus_message_get_args (msg, nullptr + , DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, & arr_ptr, & arr_size + , DBUS_TYPE_UINT64, & ts + , DBUS_TYPE_INVALID + ); + + if (THIS->warmup_tries == 0) { + auto diff = now - ts; + THIS->total_time += diff; + ++ THIS->total_count; + + THIS->min_time = std::min (THIS->min_time, diff); + THIS->max_time = std::max (THIS->max_time, diff); + } else { + -- THIS->warmup_tries; + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + , nullptr +}; + +void show_help (const char * argv0) +{ + std::cout << "Usage: " << argv0 + << " [-t warmup_tries] " + << " [-p sock_path] " + << " [-i (infinite listening)] " + << " [-w (busy wait)] " + << std::endl + ; +} + +void dispatch_connection (conn_with_metadata & cwm, bool busy_wait) +{ + if (dbus_connection_read_write_dispatch (cwm.conn.get(), busy_wait ? 0 : -1)) + return; + + cwm.finished = true; + + auto avg_time = double(cwm.total_time) / cwm.total_count; + std::cout + << "min: " << cwm.min_time << "us, " + << "max: " << cwm.max_time << "us, " + << "avg: " << avg_time << "us" + << std::endl; +} + +void read_from_watch (DBusWatch * watch, bool wait_infinitely) +{ + struct pollfd pfd; + pfd.events = POLLIN; + pfd.fd = dbus_watch_get_unix_fd (watch); + + int nfds; + do { + nfds = poll (& pfd, 1, wait_infinitely ? -1 : 0); + } while (nfds == -1); + + if (nfds == 1) + dbus_watch_handle (watch, DBUS_WATCH_READABLE); +} + +int main (int argc, char ** argv) +{ + run_opts opt = get_opt (argc, argv); + + if (opt.show_help) { + show_help (argv[0]); + return EXIT_FAILURE; + } + + server = dbus_server_listen (("unix:path=" + opt.sock_path).c_str(), nullptr); + if (!server) { + std::cerr << "listening on " << opt.sock_path << "failed" << std::endl; + return EXIT_FAILURE; + } + + // Set up cleanup functions for `server` + std::atexit (exit_func); + std::signal (SIGINT , sig_handler); + std::signal (SIGTERM, sig_handler); + + std::vector conns; + auto ud = std::make_pair (std::ref(opt), std::ref (conns)); + dbus_server_set_new_connection_function (server + , [] (DBusServer * s, DBusConnection * c, void * data) { + auto [opts, connz] = *(reinterpret_cast (data)); + connz.emplace_back (opts, c); + } + , & ud // userdata + , nullptr // userdata free func + ); + + DBusWatch * watch; + dbus_server_set_watch_functions (server + , [] (DBusWatch * w, void * data) -> dbus_bool_t { + auto main_watch = reinterpret_cast (data); + *main_watch = w; + + return FALSE; + } + , nullptr // remove watch func + , nullptr // toggle watch func + , & watch // userdata + , nullptr // userdata free func + ); + + do { + read_from_watch (watch, conns.empty ()); + + for (auto & conn : conns) + dispatch_connection (conn, opt.busy_wait); + + std::erase_if (conns, [] (const auto & x) { return x.finished; }); + + } while (opt.infinite_listening || ! conns.empty ()); + + return 0; +} + diff --git a/packaging/dbus-tools.spec b/packaging/dbus-tools.spec index d632658..23d11eb 100644 --- a/packaging/dbus-tools.spec +++ b/packaging/dbus-tools.spec @@ -56,4 +56,6 @@ make %{_bindir}/sharedmem %{_bindir}/gdbus %{_bindir}/libdbus +%{_bindir}/libdbus-p2p-server +%{_bindir}/libdbus-p2p-client %{_bindir}/p2p-gdbus -- 2.34.1