--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
+