#include <algorithm>
#include <chrono>
-#include <cstdlib>
#include <csignal>
-#include <functional>
-#include <iostream>
+#include <cstdlib>
#include <fstream>
+#include <functional>
#include <iomanip>
+#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;
bool busy_wait = false;
/* When enabled, the server collects all method call times and
- * writes them out to a CSV file. Useful for distribution charts. */
- bool raw_data_on = false;
+ * writes them out to a CSV file under the specified path. Useful
+ * for distribution charts. */
+ std::optional <std::string> raw_data_path = std::nullopt;
};
run_opts get_opt (int argc, char ** argv)
run_opts ret;
int opt;
- while ((opt = getopt(argc, argv, "wit:p:rh")) != -1)
+ while ((opt = getopt(argc, argv, ":ihp:r:t:w")) != -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 't':
+ ret.warmup_tries = std::stoi(optarg);
+ break;
case 'r':
- ret.raw_data_on = true;
+ ret.raw_data_path.emplace(optarg);
break;
+ case 'w':
+ ret.busy_wait = true;
+ break;
+ case ':':
+ if (optopt == 'r') {
+ ret.raw_data_path.emplace("libdbus-p2p-latency.csv");
+ break;
+ }
+
+ [[fallthrough]];
case 'h':
default:
ret.show_help = true;
return ret;
}
-struct DBusConnection_destructor {
+struct DBusConnection_deleter {
void operator () (DBusConnection * conn) {
dbus_connection_unregister_object_path (conn, TEST_PATH);
dbus_connection_close (conn);
size_t warmup_tries;
- bool raw_data_on;
-
- std::ofstream &raw_data_out_file;
+ static std::ofstream raw_data_out_file;
- std::unique_ptr <DBusConnection, DBusConnection_destructor> conn;
+ std::unique_ptr <DBusConnection, DBusConnection_deleter> conn;
bool finished;
static DBusObjectPathVTable vtable;
- conn_with_metadata (const run_opts & ro, DBusConnection * c, std::ofstream & rdof)
+ 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)
- , raw_data_on(ro.raw_data_on)
- , raw_data_out_file(rdof)
, conn (c)
, finished (false)
{
, min_time (std::move(x.min_time))
, max_time (std::move(x.max_time))
, warmup_tries (std::move(x.warmup_tries))
- , raw_data_on (std::move(x.raw_data_on))
- , raw_data_out_file (x.raw_data_out_file)
, conn (std::move(x.conn))
, finished (std::move(x.finished))
{
min_time = std::move(x.min_time);
max_time = std::move(x.max_time);
warmup_tries = std::move(x.warmup_tries);
- raw_data_on = std::move(x.raw_data_on);
- raw_data_out_file = std::move(x.raw_data_out_file);
conn = std::move(x.conn);
finished = std::move(x.finished);
~ conn_with_metadata () = default;
};
+std::ofstream conn_with_metadata::raw_data_out_file;
+
DBusObjectPathVTable conn_with_metadata::vtable =
{ [] (DBusConnection * c, void * userdata) -> void {
/* Unregistering and disconnection handler.
/* 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),*/
+ * 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>
+ dbus_uint64_t now = std::chrono::duration_cast <std::chrono::nanoseconds>
(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 */
+ * but `dbus_message_get_args` doesn't accept nulls. */
char * arr_ptr;
int arr_size;
THIS->min_time = std::min (THIS->min_time, diff);
THIS->max_time = std::max (THIS->max_time, diff);
- if (THIS->raw_data_on) {
- THIS->raw_data_out_file << std::setprecision(4);
- THIS->raw_data_out_file << (double)diff << std::endl;
- }
+ if (THIS->raw_data_out_file.is_open())
+ THIS->raw_data_out_file << diff / 1000.0 << std::endl;
} else {
-- THIS->warmup_tries;
}
void show_help (const char * argv0)
{
std::cout << "Usage: " << argv0
- << " [-t warmup_tries] "
- << " [-p sock_path] "
- << " [-i (infinite listening)] "
- << " [-w (busy wait)] "
- << " [-r (raw data dump)] "
+ << " [-t warmup_tries]"
+ " [-p sock_path]"
+ " [-i (infinite listening)]"
+ " [-w (busy wait)]"
+ " [-r [dump_csv_file_path]]"
<< std::endl
;
}
cwm.finished = true;
- auto avg_time = double(cwm.total_time) / cwm.total_count;
+ if (! cwm.total_count) {
+ std::cerr << "connection broken during warm-up phase" << std::endl;
+ return;
+ }
+
+ auto avg_time = cwm.total_time / cwm.total_count;
std::cout
- << "min: " << cwm.min_time << "us, "
- << "max: " << cwm.max_time << "us, "
- << "avg: " << avg_time << "us"
+ << "min: " << cwm.min_time / 1000.0 << "us, "
+ << "max: " << cwm.max_time / 1000.0 << "us, "
+ << "avg: " << avg_time / 1000.0 << "us"
<< std::endl;
}
+struct DBusServer_deleter {
+ void operator () (DBusServer * server) {
+ dbus_server_disconnect (server);
+ dbus_server_unref (server);
+ }
+};
+
void read_from_watch (DBusWatch * watch, bool wait_infinitely)
{
struct pollfd pfd;
dbus_watch_handle (watch, DBUS_WATCH_READABLE);
}
+extern "C" void sig_handler (int sig)
+{
+ /* Ensures RAII by replacing std::terminate, which
+ * is the default but does not destroy objects (but
+ * see the thread_local note below). */
+
+ std::exit (EXIT_SUCCESS);
+}
+
int main (int argc, char ** argv)
{
+ std::signal (SIGINT , sig_handler);
+ std::signal (SIGTERM, sig_handler);
+
run_opts opt = get_opt (argc, argv);
- std::ofstream raw_data_out_file;
if (opt.show_help) {
show_help (argv[0]);
return EXIT_FAILURE;
}
- server = dbus_server_listen (("unix:path=" + opt.sock_path).c_str(), nullptr);
+ /* thread_local ensures the destructor runs, the destructor doesn't
+ * run on std::exit without it as per the std::exit specification.
+ * Otherwise it makes no difference since we're single-threaded. */
+ thread_local std::unique_ptr <DBusServer, DBusServer_deleter> 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;
}
- if (opt.raw_data_on) {
- raw_data_out_file.open("libdbus-p2p-latency.csv");
- if (!raw_data_out_file.is_open()) {
+ if (opt.raw_data_path) {
+ conn_with_metadata::raw_data_out_file.open(opt.raw_data_path.value());
+ if (!conn_with_metadata::raw_data_out_file.is_open()) {
std::cerr << "cannot create raw data output file" << std::endl;
return EXIT_FAILURE;
}
- raw_data_out_file << "Latency" << std::endl;
- raw_data_out_file << std::fixed;
+ conn_with_metadata::raw_data_out_file << "Latency" << std::endl
+ << std::fixed << std::setprecision(3);
}
- // 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_tuple (std::ref(opt), std::ref (conns), std::ref(raw_data_out_file));
- dbus_server_set_new_connection_function (server
+ thread_local std::vector <conn_with_metadata> conns;
+ auto ud = std::make_pair (std::ref(opt), std::ref (conns));
+ dbus_server_set_new_connection_function (server.get()
, [] (DBusServer * s, DBusConnection * c, void * data) {
- auto [opts, connz, rdof] = *(reinterpret_cast <decltype (&ud)> (data));
- connz.emplace_back (opts, c, rdof);
+ 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
+ dbus_server_set_watch_functions (server.get()
, [] (DBusWatch * w, void * data) -> dbus_bool_t {
auto main_watch = reinterpret_cast <DBusWatch **> (data);
*main_watch = w;
, nullptr // userdata free func
);
+ /* Parser tools expect microseconds, but we calculate
+ * from nanoseconds, so have 3 decimal digits. */
+ std::cout << std::fixed << std::setprecision(3);
+
do {
read_from_watch (watch, conns.empty ());
} while (opt.infinite_listening || ! conns.empty ());
- return 0;
+ return EXIT_SUCCESS;
}