Implement a performance testing tool for sensors

There are a lot of hypothesis being made about how to improve the
performance of the sensor subsystem within OpenBMC.  There are a number
of statements being made about dbus performance that actually seem
likely to be related to the efficiency of specific implementations of
certain sensors within OpenBMC.  Blocking calls, non blocking calls,
asio, bulk collection, eventing, threads and other design decisions all
can have an effect on the performance of a sensor application.

This commit attempts to write a small, portable daemon that publishes
sensor interfaces read from memory in a relatively simple and
controllable manner.  This allows running it on a bmc (with services
unloaded) to determine some theoretical "max" performance
characteristics, assuming 0 cost for grabbing actual values.

Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I27f1560ba13492ccff6a01013c3a1d5ee210cef0
diff --git a/dbus_sensor_tester/dbus_sensor_tester.bb b/dbus_sensor_tester/dbus_sensor_tester.bb
new file mode 100644
index 0000000..b012d8b
--- /dev/null
+++ b/dbus_sensor_tester/dbus_sensor_tester.bb
@@ -0,0 +1,17 @@
+LICENSE = "Apache-2.0"
+LIC_FILES_CHKSUM = ""
+
+SRC_URI = "git://git@github.com:openbmc/openbmc-tools.git;protocol=ssh"
+
+PV = "1.0+git${SRCPV}"
+SRCREV = "e2f90b8cbbed95d8aef762350d1d49c157c6fa79"
+
+S = "$WORKDIR}/git/dbus_sensor_tester"
+
+inherit meson pkgconfig
+
+DEPENDS += " \
+    sdbusplus \
+    boost \
+    cli11 \
+"
diff --git a/dbus_sensor_tester/main.cpp b/dbus_sensor_tester/main.cpp
new file mode 100644
index 0000000..21a6fb5
--- /dev/null
+++ b/dbus_sensor_tester/main.cpp
@@ -0,0 +1,128 @@
+#include <CLI/App.hpp>
+#include <CLI/Config.hpp>
+#include <CLI/Formatter.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <chrono>
+#include <functional>
+#include <iostream>
+#include <memory>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+boost::asio::io_context io;
+std::vector<std::shared_ptr<sdbusplus::asio::dbus_interface>> sensorInterfaces;
+
+int update_interval_seconds = 1;
+
+size_t reads = 0;
+
+void on_loop(boost::asio::steady_timer *timer,
+             const boost::system::error_code &error) {
+
+  if (error) {
+    return;
+  }
+  std::chrono::steady_clock::time_point start =
+      std::chrono::steady_clock::now();
+
+  static double value = -100.0;
+
+  for (auto &sensor : sensorInterfaces) {
+    if (!sensor->set_property("Value", value)) {
+      std::cout << "Can't set property for sensor\n";
+    }
+    value += 10.0;
+    if (value >= 100.0) {
+      value = -100.0;
+    }
+  }
+  if (!sensorInterfaces.empty()) {
+    std::cout << sensorInterfaces.size() << " updates took "
+              << std::chrono::duration_cast<std::chrono::duration<float>>(
+                     std::chrono::steady_clock::now() - start)
+                     .count()
+              << " seconds\n";
+  }
+
+  if (reads > 0) {
+    std::cout << "Read " << reads << " sensor updates\n";
+    reads = 0;
+  }
+
+  timer->expires_from_now(std::chrono::seconds(update_interval_seconds));
+  timer->async_wait(std::bind_front(on_loop, timer));
+};
+
+int main(int argc, const char **argv) {
+  CLI::App app{"dbus performance test application"};
+
+  size_t number_of_sensors = 0;
+  app.add_option("-n", number_of_sensors, "Number of sensors to create");
+
+  bool watch_sensor_updates = false;
+  app.add_flag("-w", watch_sensor_updates,
+               "Watch for all sensor values from dbus");
+  CLI11_PARSE(app, argc, argv);
+
+  if (number_of_sensors == 0 && watch_sensor_updates == false) {
+    std::cout << "Nothing to do\n";
+    app.exit(CLI::CallForHelp());
+    return -1;
+  }
+
+  std::shared_ptr<sdbusplus::asio::connection> connection =
+      std::make_shared<sdbusplus::asio::connection>(io);
+  sdbusplus::asio::object_server objectServer(connection);
+
+  std::string name = "foobar";
+  sensorInterfaces.reserve(number_of_sensors);
+  for (size_t sensorIndex = 0; sensorIndex < number_of_sensors; sensorIndex++) {
+    sdbusplus::message::object_path path(
+        "/xyz/openbmc_project/sensors/temperature/");
+    path /= name + std::to_string(sensorIndex);
+    std::shared_ptr<sdbusplus::asio::dbus_interface> sensorInterface =
+        objectServer.add_interface(path.str,
+                                   "xyz.openbmc_project.Sensor.Value");
+    sensorInterface->register_property<std::string>(
+        "Unit", "xyz.openbmc_project.Sensor.Unit.DegreesC");
+    sensorInterface->register_property<double>("MaxValue", 100);
+    sensorInterface->register_property<double>("MinValue", -100);
+    sensorInterface->register_property<double>("Value", 42);
+
+    sensorInterface->initialize();
+    sensorInterfaces.emplace_back(sensorInterface);
+  }
+
+  std::cout << "Done initializing\n";
+
+  boost::asio::steady_timer timer(io);
+  timer.expires_from_now(std::chrono::seconds(update_interval_seconds));
+  timer.async_wait(std::bind_front(on_loop, &timer));
+  std::optional<sdbusplus::bus::match::match> match;
+  if (watch_sensor_updates) {
+    std::string expr = "type='signal',member='PropertiesChanged',path_"
+                       "namespace='/xyz/openbmc_project/sensors'";
+
+    match.emplace(
+        static_cast<sdbusplus::bus::bus &>(*connection), expr,
+        [](sdbusplus::message::message &message) {
+          std::string objectName;
+          std::vector<std::pair<std::string, std::variant<double>>> result;
+          try {
+            message.read(objectName, result);
+          } catch (const sdbusplus::exception_t &) {
+            std::cerr << "Error reading match data\n";
+            return;
+          }
+          for (auto &property : result) {
+            if (property.first == "Value") {
+              reads++;
+            }
+          }
+        });
+  }
+
+  io.run();
+
+  return 0;
+}
\ No newline at end of file
diff --git a/dbus_sensor_tester/meson.build b/dbus_sensor_tester/meson.build
new file mode 100644
index 0000000..31d28c5
--- /dev/null
+++ b/dbus_sensor_tester/meson.build
@@ -0,0 +1,138 @@
+project('dbus-sensor-tester', 'cpp',
+        version : '1.0',
+        meson_version: '>=0.57.0',
+        default_options: [
+            'b_lto_mode=default',
+            'b_lto_threads=0',
+            'b_lto=true',
+            'b_ndebug=if-release',
+            'buildtype=debugoptimized',
+            'cpp_rtti=false',
+            'cpp_std=c++20',
+            'warning_level=3',
+            'werror=true',
+           ])
+
+# Project related links
+
+project_pretty_name = 'dbus-sensor-tester'
+
+# Validate the c++ Standard
+
+if get_option('cpp_std') != 'c++20'
+    error('This project requires c++20 support')
+endif
+
+# Get compiler and default build type
+
+cxx = meson.get_compiler('cpp')
+build = get_option('buildtype')
+optimization = get_option('optimization')
+summary('Build Type',build, section : 'Build Info')
+summary('Optimization',optimization, section : 'Build Info')
+
+# Disable lto when compiling with no optimization
+if(get_option('optimization') == '0')
+  add_project_arguments('-fno-lto', language: 'cpp')
+  message('Disabling lto & its supported features as optimization is disabled')
+endif
+
+# Add compiler arguments
+
+# -Wpedantic, -Wextra comes by default with warning level
+add_project_arguments(
+  cxx.get_supported_arguments([
+  '-Wcast-align',
+  '-Wconversion',
+  '-Wformat=2',
+  '-Wold-style-cast',
+  '-Woverloaded-virtual',
+  '-Wsign-conversion',
+  '-Wunused',
+  '-Wno-attributes',
+  ]),
+  language: 'cpp'
+)
+
+if (cxx.get_id() == 'clang' and cxx.version().version_compare('>9.0'))
+add_project_arguments(
+  cxx.get_supported_arguments([
+    '-Weverything',
+    '-Wno-c++98-compat-pedantic',
+    '-Wno-c++98-compat',
+    '-Wno-documentation-unknown-command',
+    '-Wno-documentation',
+    '-Wno-exit-time-destructors',
+    '-Wno-global-constructors',
+    '-Wno-newline-eof',
+    '-Wno-padded',
+    '-Wno-shadow',
+    '-Wno-used-but-marked-unused',
+    '-Wno-weak-vtables',
+  ]),
+  language:'cpp')
+endif
+
+# if compiler is gnu-gcc , and version is > 8.0 then we add few more
+# compiler arguments , we know that will pass
+
+if (cxx.get_id() == 'gcc' and cxx.version().version_compare('>8.0'))
+  add_project_arguments(
+    cxx.get_supported_arguments([
+     '-Wduplicated-cond',
+     '-Wduplicated-branches',
+     '-Wlogical-op',
+     '-Wunused-parameter',
+     '-Wnull-dereference',
+     '-Wdouble-promotion',
+     ]),
+    language:'cpp')
+endif
+
+# Find the dependency modules, if not found use meson wrap to get them
+# automatically during the configure step
+dependencies = []
+
+sdbusplus = dependency('sdbusplus', required : false, include_type: 'system')
+if not sdbusplus.found()
+  sdbusplus_proj = subproject('sdbusplus', required: true)
+  sdbusplus = sdbusplus_proj.get_variable('sdbusplus_dep')
+  sdbusplus = sdbusplus.as_system('system')
+endif
+dependencies += sdbusplus
+
+cli11 = dependency('cli11', required : false, include_type: 'system')
+if not cli11.found()
+  cli11_proj = subproject('cli11', required: true)
+  cli11 = cli11_proj.get_variable('CLI11_dep')
+  cli11 = cli11.as_system('system')
+endif
+dependencies += cli11
+
+systemd = dependency('systemd')
+dependencies += [systemd]
+
+boost = dependency('boost', version : '>=1.78.0', required : false, include_type: 'system')
+if not boost.found()
+  subproject('boost', required: false)
+  boost_inc = include_directories('subprojects/boost_1_78_0/', is_system:true)
+  boost  = declare_dependency(include_directories : boost_inc)
+  boost = boost.as_system('system')
+endif
+dependencies += boost
+
+srcfiles_sensortest = [
+]
+
+systemd_system_unit_dir = systemd.get_variable(pkgconfig: 'systemdsystemunitdir')
+bindir = get_option('prefix') + '/' +get_option('bindir')
+
+# Generate the executable
+executable(
+  'sensortest',
+  srcfiles_sensortest + ['main.cpp'],
+  dependencies: dependencies,
+  link_args: '-Wl,--gc-sections',
+  install: true,
+  install_dir: bindir
+)
diff --git a/dbus_sensor_tester/subprojects/boost.wrap b/dbus_sensor_tester/subprojects/boost.wrap
new file mode 100644
index 0000000..2f7a22f
--- /dev/null
+++ b/dbus_sensor_tester/subprojects/boost.wrap
@@ -0,0 +1,4 @@
+[wrap-file]
+source_url = https://downloads.yoctoproject.org/mirror/sources/boost_1_78_0.tar.bz2
+source_hash = 8681f175d4bdb26c52222665793eef08490d7758529330f98d3b29dd0735bccc
+source_filename = 1_78_0.tar.bz2
diff --git a/dbus_sensor_tester/subprojects/cli11.wrap b/dbus_sensor_tester/subprojects/cli11.wrap
new file mode 100644
index 0000000..e6f1639
--- /dev/null
+++ b/dbus_sensor_tester/subprojects/cli11.wrap
@@ -0,0 +1,8 @@
+[wrap-file]
+directory = CLI11-2.1.2
+source_url = https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.1.2.tar.gz
+source_filename = CLI11-2.1.2.tar.gz
+source_hash = 26291377e892ba0e5b4972cdfd4a2ab3bf53af8dac1f4ea8fe0d1376b625c8cb
+
+[provide]
+cli11 = CLI11_dep
diff --git a/dbus_sensor_tester/subprojects/sdbusplus.wrap b/dbus_sensor_tester/subprojects/sdbusplus.wrap
new file mode 100644
index 0000000..d470130
--- /dev/null
+++ b/dbus_sensor_tester/subprojects/sdbusplus.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/openbmc/sdbusplus.git
+revision = HEAD