Add p2p libdbus latency tests 62/267862/9
authorMichal Bloch <m.bloch@samsung.com>
Thu, 2 Dec 2021 16:57:15 +0000 (17:57 +0100)
committerAdrian Szyndela <adrian.s@samsung.com>
Thu, 23 Dec 2021 16:57:01 +0000 (17:57 +0100)
Change-Id: I5fbae009b5a311d2e9b16b3d52f72435cb70a700
Signed-off-by: Michal Bloch <m.bloch@samsung.com>
Makefile
benchmark/libdbus-p2p-client.cpp [new file with mode: 0644]
benchmark/libdbus-p2p-server.cpp [new file with mode: 0644]
packaging/dbus-tools.spec

index 457b555a2e0193d0ea5bccad8c798e254b6a0df5..786cf1362c0c74e8b414ac983129e25ccb7241f7 100644 (file)
--- 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 (file)
index 0000000..3f6d780
--- /dev/null
@@ -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 <dbus/dbus.h>
+
+#include <unistd.h>
+
+#include <chrono>
+#include <iostream>
+#include <thread>
+
+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::microseconds>
+                       (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 (file)
index 0000000..44cf307
--- /dev/null
@@ -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 <dbus/dbus.h>
+
+#include <poll.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <chrono>
+#include <cstdlib>
+#include <csignal>
+#include <functional>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+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 <DBusConnection, DBusConnection_destructor> 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 <uint64_t> :: max ())
+               , max_time (std::numeric_limits <uint64_t> :: 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 <conn_with_metadata *> (userdata);
+
+               dbus_uint64_t now = std::chrono::duration_cast <std::chrono::microseconds>
+                       (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 <conn_with_metadata> 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 <decltype (&ud)> (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 <DBusWatch **> (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;
+}
+
index d632658a6a6991f10deb10383c724cb6622be6a4..23d11eb6ec08448b7f4d053004ce98785b204f8a 100644 (file)
@@ -56,4 +56,6 @@ make
 %{_bindir}/sharedmem
 %{_bindir}/gdbus
 %{_bindir}/libdbus
+%{_bindir}/libdbus-p2p-server
+%{_bindir}/libdbus-p2p-client
 %{_bindir}/p2p-gdbus