You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by jd...@apache.org on 2020/05/12 11:52:54 UTC

[qpid-proton] branch master updated: PROTON-220: Google Benchmark proton-c and proton-cpp microbenchmarks (#211)

This is an automated email from the ASF dual-hosted git repository.

jdanek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/qpid-proton.git


The following commit(s) were added to refs/heads/master by this push:
     new 7abcfc7  PROTON-220: Google Benchmark proton-c and proton-cpp microbenchmarks (#211)
7abcfc7 is described below

commit 7abcfc7aba6cfd1c938324cf34a51555c845a589
Author: Jirka Daněk <dn...@redhat.com>
AuthorDate: Tue May 12 13:52:43 2020 +0200

    PROTON-220: Google Benchmark proton-c and proton-cpp microbenchmarks (#211)
---
 .travis.yml                            |  11 +-
 CMakeLists.txt                         |   6 +-
 c/CMakeLists.txt                       |   4 +
 c/benchmarks/CMakeLists.txt            |  33 +++
 c/benchmarks/README.md                 |  26 ++
 c/benchmarks/benchmarks_main.cpp       |   3 +
 c/benchmarks/connection-driver.cpp     | 487 +++++++++++++++++++++++++++++++++
 c/benchmarks/message-encoding_list.cpp | 134 +++++++++
 c/benchmarks/message-encoding_map.cpp  | 164 +++++++++++
 cpp/CMakeLists.txt                     |   4 +
 cpp/benchmarks/CMakeLists.txt          |  33 +++
 cpp/benchmarks/README.md               |   3 +
 cpp/benchmarks/benchmarks_main.cpp     |   3 +
 cpp/benchmarks/container.cpp           |  60 ++++
 14 files changed, 969 insertions(+), 2 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index e722316..bfc0d82 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -29,6 +29,15 @@ matrix:
   include:
   - os: linux
     dist: bionic
+    sudo: true
+    compiler:
+    - gcc
+    env:
+    - QPID_PROTON_CMAKE_ARGS='-DENABLE_BENCHMARKS=ON -DRUNTIME_CHECK=OFF'
+    before_install:
+    - sudo apt-get install -y libbenchmark-dev
+  - os: linux
+    dist: bionic
     compiler: gcc
     env:
     # python-tox-test fails and ruby tests segfault
@@ -96,7 +105,7 @@ addons:
       - swig
 # update:true workaround as of Feb. 22/20 as per
 # https://travis-ci.community/t/macos-build-fails-because-of-homebrew-bundle-unknown-command/7296/10
-    update: true 
+    update: true
 
 install:
 - python -m pip install --user --upgrade pip
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5f36ad5..c596219 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -116,9 +116,12 @@ if (APPLE)
   set (NOENABLE_WARNING_ERROR ON)
 endif (APPLE)
 
+# Benchmarks should be disabled by default
+set (NOENABLE_BENCHMARKS ON)
+
 # Add options here called <whatever> they will turn into "ENABLE_<whatever" and can be
 # overridden on a platform specific basis above by NOENABLE_<whatever>
-set (OPTIONS WARNING_ERROR UNDEFINED_ERROR LINKTIME_OPTIMIZATION HIDE_UNEXPORTED_SYMBOLS FUZZ_TESTING)
+set (OPTIONS WARNING_ERROR UNDEFINED_ERROR LINKTIME_OPTIMIZATION HIDE_UNEXPORTED_SYMBOLS FUZZ_TESTING BENCHMARKS)
 
 foreach (OPTION ${OPTIONS})
   if (NOT NOENABLE_${OPTION})
@@ -132,6 +135,7 @@ option(ENABLE_UNDEFINED_ERROR "Check for unresolved library symbols" ${DEFAULT_U
 option(ENABLE_LINKTIME_OPTIMIZATION "Perform link time optimization" ${DEFAULT_LINKTIME_OPTIMIZATION})
 option(ENABLE_HIDE_UNEXPORTED_SYMBOLS "Only export library symbols that are explicitly requested" ${DEFAULT_HIDE_UNEXPORTED_SYMBOLS})
 option(ENABLE_FUZZ_TESTING "Enable building fuzzers and regression testing with libFuzzer" ${DEFAULT_FUZZ_TESTING})
+option(ENABLE_BENCHMARKS "Enable building and running benchmarks with Google Benchmark" ${DEFAULT_BENCHMARKS})
 
 # Set any additional compiler specific flags
 if (CMAKE_COMPILER_IS_GNUCC)
diff --git a/c/CMakeLists.txt b/c/CMakeLists.txt
index d2e2888..44a9745 100644
--- a/c/CMakeLists.txt
+++ b/c/CMakeLists.txt
@@ -576,6 +576,10 @@ install (FILES
   ${CMAKE_CURRENT_BINARY_DIR}/ProtonConfigVersion.cmake
   DESTINATION ${LIB_INSTALL_DIR}/cmake/Proton)
 
+if (ENABLE_BENCHMARKS)
+  add_subdirectory(benchmarks)
+endif (ENABLE_BENCHMARKS)
+
 add_subdirectory(docs)
 add_subdirectory(examples)
 add_subdirectory(tests)
diff --git a/c/benchmarks/CMakeLists.txt b/c/benchmarks/CMakeLists.txt
new file mode 100644
index 0000000..e2c2a62
--- /dev/null
+++ b/c/benchmarks/CMakeLists.txt
@@ -0,0 +1,33 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+find_package(benchmark REQUIRED)
+
+# incompatibility in older libbenchmark, https://github.com/google/benchmark/issues/494#issuecomment-502444478
+get_source_file_property(COMPILE_FLAGS benchmarks_main.cpp current_compile_flags)
+set_source_files_properties(benchmarks_main.cpp PROPERTIES COMPILE_FLAGS "${current_compile_flags} -Wno-pedantic")
+
+add_executable(c-benchmarks benchmarks_main.cpp connection-driver.cpp message-encoding_list.cpp message-encoding_map.cpp)
+target_link_libraries(c-benchmarks benchmark pthread qpid-proton)
+
+add_test(NAME c-benchmarks COMMAND c-benchmarks)
diff --git a/c/benchmarks/README.md b/c/benchmarks/README.md
new file mode 100644
index 0000000..91caab4
--- /dev/null
+++ b/c/benchmarks/README.md
@@ -0,0 +1,26 @@
+# Microbenchmarks
+
+## Compiling
+
+    -DENABLE_BENCHMARKS
+
+## Running
+
+Suppress CPU frequency scaling
+
+    # https://www.kernel.org/doc/html/latest/admin-guide/pm/intel_pstate.html
+    echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo
+
+Maybe reserve specific cores for the benchmark and and possibly also switch off Multi Threading (on Intel).
+This makes the results less noisy.
+
+    # https://llvm.org/docs/Benchmarking.html
+
+Let Google Benchmark compute average, median, stddev of time from multiple executions
+
+    --benchmark_repetitions=9
+
+## Profiling
+
+    sudo sh -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid'
+    sudo sh -c 'echo 0 > /proc/sys/kernel/kptr_restrict'
diff --git a/c/benchmarks/benchmarks_main.cpp b/c/benchmarks/benchmarks_main.cpp
new file mode 100644
index 0000000..71fefa0
--- /dev/null
+++ b/c/benchmarks/benchmarks_main.cpp
@@ -0,0 +1,3 @@
+#include <benchmark/benchmark.h>
+
+BENCHMARK_MAIN();
diff --git a/c/benchmarks/connection-driver.cpp b/c/benchmarks/connection-driver.cpp
new file mode 100644
index 0000000..e2fdad1
--- /dev/null
+++ b/c/benchmarks/connection-driver.cpp
@@ -0,0 +1,487 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <unistd.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wait.h>
+
+#include <benchmark/benchmark.h>
+
+#include "proton/connection_driver.h"
+#include "proton/engine.h"
+#include "proton/log.h"
+#include "proton/message.h"
+#include "proton/transport.h"
+#include <proton/listener.h>
+#include <proton/netaddr.h>
+#include <proton/proactor.h>
+#include <proton/sasl.h>
+
+
+// variant of the receive.c proactor example
+// borrowed things from the quiver arrow, too
+
+#define MAX_SIZE 1024
+
+typedef char str[MAX_SIZE];
+
+typedef struct app_data_t {
+  str container_id;
+  pn_rwbytes_t message_buffer{};
+  int message_count;
+  int received = 0;
+  pn_message_t *message;
+  int sent = 0;
+  pn_rwbytes_t msgout;
+  int credit_window = 5000;
+  int acknowledged = 0;
+  int closed = 0;
+} app_data_t;
+
+static void decode_message(pn_delivery_t *dlv);
+static void check_condition(pn_event_t *e, pn_condition_t *cond);
+static void shovel(pn_connection_driver_t &sender, pn_connection_driver_t &receiver);
+
+static void handle_receiver(app_data_t *app, pn_event_t *event);
+static void handle_sender(app_data_t *app, pn_event_t *event);
+
+const bool VERBOSE = false;
+const bool ERRORS = true;
+
+// useful for debugging
+static void turn_on_transport_logging(pn_transport_t& transport) {
+    pn_log_enable(true);
+    pn_transport_trace(&transport,
+        PN_TRACE_FRM | PN_TRACE_EVT);
+}
+
+static void BM_InitCloseSender(benchmark::State &state) {
+  if (VERBOSE)
+    printf("BEGIN BM_InitCloseSender\n");
+
+  for (auto _ : state) {
+    pn_connection_driver_t sender;
+    if (pn_connection_driver_init(&sender, NULL, NULL) != 0) {
+      printf("sender: pn_connection_driver_init failed\n");
+      exit(1);
+    }
+
+    pn_connection_driver_close(&sender);
+    pn_connection_driver_destroy(&sender);
+  }
+
+  if (VERBOSE)
+    printf("END BM_InitCloseSender\n");
+}
+
+BENCHMARK(BM_InitCloseSender)->Unit(benchmark::kMicrosecond);
+
+static void BM_InitCloseReceiver(benchmark::State &state) {
+  if (VERBOSE)
+    printf("BEGIN BM_InitCloseReceiver\n");
+
+  for (auto _ : state) {
+    pn_connection_driver_t receiver;
+    if (pn_connection_driver_init(&receiver, NULL, NULL) != 0) {
+      printf("receiver: pn_connection_driver_init failed\n");
+      exit(1);
+    }
+
+    pn_connection_driver_close(&receiver);
+    pn_connection_driver_destroy(&receiver);
+  }
+
+  if (VERBOSE)
+    printf("END BM_InitCloseReceiver\n");
+}
+
+BENCHMARK(BM_InitCloseReceiver)->Unit(benchmark::kMicrosecond);
+
+// sends single message and closes the connection
+static void BM_EstablishConnection(benchmark::State &state) {
+  if (VERBOSE)
+    printf("BEGIN BM_EstablishConnection\n");
+
+  for (auto _ : state) {
+    app_data_t app = {};
+    app.message_count = 1;
+    app.message = pn_message();
+    sprintf(app.container_id, "%s:%06x", "BM_EstablishConnection",
+            rand() & 0xffffff);
+
+    pn_connection_driver_t receiver;
+    if (pn_connection_driver_init(&receiver, NULL, NULL) != 0) {
+      printf("receiver: pn_connection_driver_init failed\n");
+      exit(1);
+    }
+
+    pn_connection_driver_t sender;
+    if (pn_connection_driver_init(&sender, NULL, NULL) != 0) {
+      printf("sender: pn_connection_driver_init failed\n");
+      exit(1);
+    }
+
+    do {
+      pn_event_t *event;
+      while ((event = pn_connection_driver_next_event(&sender)) != NULL) {
+        handle_sender(&app, event);
+      }
+      shovel(sender, receiver);
+      while ((event = pn_connection_driver_next_event(&receiver)) != NULL) {
+        handle_receiver(&app, event);
+      }
+      shovel(receiver, sender);
+    } while (app.closed < 2); // until both sender and receiver are closed
+
+    pn_message_free(app.message);
+
+    pn_connection_driver_close(&receiver);
+    pn_connection_driver_close(&sender);
+
+    shovel(receiver, sender);
+    shovel(sender, receiver);
+
+    // this can take long time, up to 500 ms
+    pn_connection_driver_destroy(&receiver);
+    pn_connection_driver_destroy(&sender);
+  }
+
+  state.SetLabel("connections");
+  state.SetItemsProcessed(state.iterations());
+
+  if (VERBOSE)
+    printf("END BM_EstablishConnection\n");
+}
+
+BENCHMARK(BM_EstablishConnection)->Unit(benchmark::kMicrosecond);
+
+static void BM_SendReceiveMessages(benchmark::State &state) {
+  if (VERBOSE)
+    printf("BEGIN BM_SendReceiveMessages\n");
+
+  app_data_t app = {};
+  app.message_count = -1; // unlimited
+  app.message = pn_message();
+  app.credit_window = state.range(0);
+  sprintf(app.container_id, "%s:%06x", "BM_SendReceiveMessages",
+          rand() & 0xffffff);
+
+  pn_connection_driver_t receiver;
+  if (pn_connection_driver_init(&receiver, NULL, NULL) != 0) {
+    printf("receiver: pn_connection_driver_init failed\n");
+    exit(1);
+  }
+
+  pn_connection_driver_t sender;
+  if (pn_connection_driver_init(&sender, NULL, NULL) != 0) {
+    printf("sender: pn_connection_driver_init failed\n");
+    exit(1);
+  }
+
+  for (auto _ : state) {
+    pn_event_t *event;
+    while ((event = pn_connection_driver_next_event(&sender)) != NULL) {
+      handle_sender(&app, event);
+    }
+    shovel(sender, receiver);
+    while ((event = pn_connection_driver_next_event(&receiver)) != NULL) {
+      handle_receiver(&app, event);
+    }
+    shovel(receiver, sender);
+  }
+
+  pn_connection_driver_close(&receiver);
+  pn_connection_driver_close(&sender);
+
+  shovel(receiver, sender);
+  shovel(sender, receiver);
+
+  // this can take long time, up to 500 ms
+  pn_connection_driver_destroy(&receiver);
+  pn_connection_driver_destroy(&sender);
+
+  state.SetLabel("messages");
+  state.SetItemsProcessed(app.acknowledged);
+
+  if (VERBOSE)
+    printf("END BM_SendReceiveMessages\n");
+}
+
+BENCHMARK(BM_SendReceiveMessages)
+    ->RangeMultiplier(3)
+    ->Range(1, 200000)
+    ->ArgName("creditWindow")
+    ->Arg(1000)
+    ->Unit(benchmark::kMillisecond);
+
+
+///* Create a message with a map { "sequence" : number } encode it and return
+/// the encoded buffer. */
+static void send_message(app_data_t *app, pn_link_t *sender) {
+  /* Construct a message with the map { "sequence": 42 } */
+  pn_data_t *body;
+  pn_message_clear(app->message);
+  body = pn_message_body(app->message);
+  pn_data_put_int(pn_message_id(app->message),
+                  app->sent); /* Set the message_id also */
+  pn_data_put_map(body);
+  pn_data_enter(body);
+  pn_data_put_string(body, pn_bytes(sizeof("sequence") - 1, "sequence"));
+  pn_data_put_int(body, 42); /* The sequence number */
+  pn_data_exit(body);
+  if (pn_message_send(app->message, sender, &app->message_buffer) < 0) {
+    fprintf(stderr, "error sending message: %s\n",
+            pn_error_text(pn_message_error(app->message)));
+    exit(1);
+  }
+}
+
+static void handle_sender(app_data_t *app, pn_event_t *event) {
+  switch (pn_event_type(event)) {
+
+  case PN_CONNECTION_INIT: {
+    pn_connection_t *c = pn_event_connection(event);
+    pn_connection_set_container(c, "sendercid");
+    pn_connection_open(c);
+    pn_session_t *s = pn_session(c);
+    pn_session_open(s);
+
+    pn_link_t *l = pn_sender(s, "my_sender");
+    pn_terminus_set_address(pn_link_target(l), "example");
+    pn_link_set_snd_settle_mode(l, PN_SND_UNSETTLED);
+    pn_link_set_rcv_settle_mode(l, PN_RCV_FIRST);
+    pn_link_open(l);
+  } break;
+
+  case PN_LINK_FLOW: {
+    if (VERBOSE)
+      printf("BEGIN handle_sender: PN_LINK_FLOW\n");
+    /* The peer has given us some credit, now we can send messages */
+    pn_link_t *sender = pn_event_link(event);
+    while (pn_link_credit(sender) > 0 && app->sent != app->message_count) {
+      ++app->sent;
+      /* Use sent counter as unique delivery tag. */
+      pn_delivery(sender, pn_dtag((const char *)&app->sent, sizeof(app->sent)));
+      send_message(app, sender);
+    }
+    break;
+  }
+
+  case PN_DELIVERY: {
+    /* We received acknowledgement from the peer that a message was delivered. */
+    pn_delivery_t *d = pn_event_delivery(event);
+    if (pn_delivery_remote_state(d) == PN_ACCEPTED) {
+      if (VERBOSE)
+        printf("got PN_ACCEPTED\n");
+      if (++app->acknowledged == app->message_count) {
+        if (VERBOSE)
+          printf("%d messages sent and acknowledged\n", app->acknowledged);
+        pn_connection_close(pn_event_connection(event));
+        /* Continue handling events till we receive TRANSPORT_CLOSED */
+      }
+    } else {
+      fprintf(stderr, "unexpected delivery state %d\n", (int) pn_delivery_remote_state(d));
+      pn_connection_close(pn_event_connection(event));
+      exit(EXIT_FAILURE);
+    }
+    break;
+  }
+
+  case PN_CONNECTION_REMOTE_OPEN:
+    pn_connection_open(pn_event_connection(event)); /* Complete the open */
+    break;
+
+  case PN_SESSION_REMOTE_OPEN:
+    pn_session_open(pn_event_session(event));
+    break;
+
+  case PN_TRANSPORT_ERROR:
+    check_condition(event, pn_transport_condition(pn_event_transport(event)));
+    pn_connection_close(pn_event_connection(event));
+    break;
+
+  case PN_CONNECTION_REMOTE_CLOSE:
+    check_condition(event,
+                    pn_connection_remote_condition(pn_event_connection(event)));
+    pn_connection_close(pn_event_connection(event));
+    break;
+
+  case PN_SESSION_REMOTE_CLOSE:
+    check_condition(event,
+                    pn_session_remote_condition(pn_event_session(event)));
+    pn_connection_close(pn_event_connection(event));
+    break;
+
+  case PN_LINK_REMOTE_CLOSE:
+  case PN_LINK_REMOTE_DETACH:
+    check_condition(event, pn_link_remote_condition(pn_event_link(event)));
+    pn_connection_close(pn_event_connection(event));
+    break;
+
+  case PN_TRANSPORT_CLOSED:
+    app->closed++;
+    break;
+
+  default:
+    break;
+  }
+}
+
+static void handle_receiver(app_data_t *app, pn_event_t *event) {
+  switch (pn_event_type(event)) {
+
+  case PN_CONNECTION_INIT: {
+    pn_connection_t *c = pn_event_connection(event);
+    pn_connection_set_container(c, app->container_id);
+    // pn_connection_open(c);
+    pn_session_t *s = pn_session(c);
+    pn_session_open(s);
+
+    pn_link_t *l = pn_receiver(s, "my_receiver");
+    pn_terminus_set_address(pn_link_source(l), "example");
+    pn_link_open(l);
+  } break;
+
+  case PN_LINK_REMOTE_OPEN: {
+    pn_link_t *l = pn_event_link(event);
+    pn_terminus_t *t = pn_link_target(l);
+    pn_terminus_t *rt = pn_link_remote_target(l);
+    pn_terminus_set_address(t, pn_terminus_get_address(rt));
+    pn_link_open(l);
+    if (pn_link_is_receiver(l)) {
+      pn_link_flow(l, app->credit_window);
+    }
+    break;
+  }
+
+  case PN_DELIVERY: {
+    /* A message has been received */
+    pn_link_t *link = NULL;
+    pn_delivery_t *dlv = pn_event_delivery(event);
+    if (pn_delivery_readable(dlv) && !pn_delivery_partial(dlv)) {
+      link = pn_delivery_link(dlv);
+      decode_message(dlv);
+      /* Accept the delivery */
+      pn_delivery_update(dlv, PN_ACCEPTED);
+      /* done with the delivery, move to the next and free it */
+      pn_link_advance(link);
+      pn_delivery_settle(dlv); /* dlv is now freed */
+    }
+    pn_link_flow(link, app->credit_window - pn_link_credit(link));
+  } break;
+
+  case PN_CONNECTION_REMOTE_OPEN:
+    pn_connection_open(pn_event_connection(event)); /* Complete the open */
+    break;
+
+  case PN_SESSION_REMOTE_OPEN:
+    pn_session_open(pn_event_session(event));
+    break;
+
+  case PN_TRANSPORT_ERROR:
+    check_condition(event, pn_transport_condition(pn_event_transport(event)));
+    pn_connection_close(pn_event_connection(event));
+    break;
+
+  case PN_CONNECTION_REMOTE_CLOSE:
+    check_condition(event,
+                    pn_connection_remote_condition(pn_event_connection(event)));
+    pn_connection_close(pn_event_connection(event));
+    break;
+
+  case PN_SESSION_REMOTE_CLOSE:
+    check_condition(event,
+                    pn_session_remote_condition(pn_event_session(event)));
+    pn_connection_close(pn_event_connection(event));
+    break;
+
+  case PN_LINK_REMOTE_CLOSE:
+  case PN_LINK_REMOTE_DETACH:
+    check_condition(event, pn_link_remote_condition(pn_event_link(event)));
+    pn_connection_close(pn_event_connection(event));
+    break;
+
+  case PN_TRANSPORT_CLOSED:
+    app->closed++;
+    break;
+
+  default:
+    break;
+  }
+}
+
+static void check_condition(pn_event_t *e, pn_condition_t *cond) {
+  if (VERBOSE)
+    printf("beginning check_condition\n");
+  if (pn_condition_is_set(cond)) {
+    if (VERBOSE || ERRORS)
+      fprintf(stderr, "%s: %s: %s\n", pn_event_type_name(pn_event_type(e)),
+              pn_condition_get_name(cond), pn_condition_get_description(cond));
+  }
+}
+
+///* Copies output from first connection driver into input of the second. */
+static void shovel(pn_connection_driver_t &sender, pn_connection_driver_t &receiver) {
+  pn_bytes_t wbuf = pn_connection_driver_write_buffer(&receiver);
+  if (wbuf.size == 0) {
+    pn_connection_driver_write_done(&receiver, 0);
+    return;
+  }
+
+  pn_rwbytes_t rbuf = pn_connection_driver_read_buffer(&sender);
+  if (rbuf.start == NULL) {
+    printf("shovel: rbuf.start is null\n");
+    fflush(stdout);
+    fflush(stderr);
+    exit(1);
+  }
+
+  size_t s = rbuf.size < wbuf.size ? rbuf.size : wbuf.size;
+  memcpy(rbuf.start, wbuf.start, s);
+
+  pn_connection_driver_read_done(&sender, s);
+  pn_connection_driver_write_done(&receiver, s);
+}
+
+static void decode_message(pn_delivery_t *dlv) {
+  static char buffer[MAX_SIZE];
+  ssize_t len;
+  // try to decode the message body
+  if (pn_delivery_pending(dlv) < MAX_SIZE) {
+    // read in the raw data
+    len = pn_link_recv(pn_delivery_link(dlv), buffer, MAX_SIZE);
+    if (len > 0) {
+      // decode it into a proton message
+      pn_message_t *m = pn_message();
+      if (PN_OK == pn_message_decode(m, buffer, len)) {
+        pn_string_t *s = pn_string(NULL);
+        pn_inspect(pn_message_body(m), s);
+        if (VERBOSE)
+          printf("%s\n", pn_string_get(s));
+        pn_free(s);
+      }
+      pn_message_free(m);
+    }
+  }
+}
diff --git a/c/benchmarks/message-encoding_list.cpp b/c/benchmarks/message-encoding_list.cpp
new file mode 100644
index 0000000..51e8ceb
--- /dev/null
+++ b/c/benchmarks/message-encoding_list.cpp
@@ -0,0 +1,134 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <unistd.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "proton/connection_driver.h"
+#include "proton/engine.h"
+#include "proton/log.h"
+#include "proton/message.h"
+
+#include <benchmark/benchmark.h>
+#include <proton/listener.h>
+#include <proton/netaddr.h>
+#include <proton/proactor.h>
+#include <proton/sasl.h>
+#include <wait.h>
+
+const bool VERBOSE = false;
+const bool ERRORS = true;
+
+static void decode_message_buffer(pn_rwbytes_t data) {
+  pn_message_t *m = pn_message();
+  int err = pn_message_decode(m, data.start, data.size);
+  if (!err) {
+    /* Print the decoded message */
+    pn_string_t *s = pn_string(NULL);
+    pn_inspect(pn_message_body(m), s);
+    if (VERBOSE) {
+      printf("%s\n", pn_string_get(s));
+      fflush(stdout);
+    }
+    pn_free(s);
+    pn_message_free(m);
+    free(data.start);
+  } else {
+    fprintf(stderr, "decode error: %s\n", pn_error_text(pn_message_error(m)));
+    exit(EXIT_FAILURE);
+  }
+}
+
+static void BM_EncodeListMessage(benchmark::State &state) {
+  if (VERBOSE)
+    printf("BEGIN BM_EncodeListMessage\n");
+
+  uint64_t entries = 0;
+
+  pn_message_t *message = pn_message();
+  for (auto _ : state) {
+    pn_message_clear(message);
+    pn_data_t *body;
+    body = pn_message_body(message);
+    pn_data_put_list(body);
+    pn_data_enter(body);
+    for (size_t i = 0; i < state.range(0); i += 2) {
+      pn_data_put_string(
+          body, pn_bytes(sizeof("some list value") - 1, "some list value"));
+      pn_data_put_int(body, 42);
+      entries += 2;
+    }
+    pn_data_exit(body);
+    pn_rwbytes_t buf{};
+    if (pn_message_encode2(message, &buf) == 0) {
+      state.SkipWithError(pn_error_text(pn_message_error(message)));
+    }
+    if (buf.start)
+      free(buf.start);
+  }
+  pn_message_free(message);
+
+  state.SetLabel("entries");
+  state.SetItemsProcessed(entries);
+
+  if (VERBOSE)
+    printf("END BM_EncodeListMessage\n");
+}
+
+BENCHMARK(BM_EncodeListMessage)
+    ->Arg(10)
+    ->Arg(100)
+    ->Arg(1000)
+    ->Arg(10000)
+    ->ArgName("entries")
+    ->Unit(benchmark::kMillisecond);
+
+static void BM_CreateListMessage(benchmark::State &state) {
+  if (VERBOSE)
+    printf("BEGIN BM_CreateListMessage\n");
+
+  uint64_t entries = 0;
+
+  pn_message_t *message = pn_message();
+  pn_data_t *body;
+  body = pn_message_body(message);
+  pn_data_put_list(body);
+  pn_data_enter(body);
+  for (auto _ : state) {
+    pn_data_put_string(
+        body, pn_bytes(sizeof("some list value") - 1, "some list value"));
+    pn_data_put_int(body, 42);
+    entries += 2;
+  }
+  pn_data_exit(body);
+  pn_message_free(message);
+
+  state.SetLabel("entries");
+  state.SetItemsProcessed(entries);
+
+  if (VERBOSE)
+    printf("END BM_CreateListMessage\n");
+}
+
+BENCHMARK(BM_CreateListMessage);
diff --git a/c/benchmarks/message-encoding_map.cpp b/c/benchmarks/message-encoding_map.cpp
new file mode 100644
index 0000000..86f8e1d
--- /dev/null
+++ b/c/benchmarks/message-encoding_map.cpp
@@ -0,0 +1,164 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <unistd.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "proton/connection_driver.h"
+#include "proton/engine.h"
+#include "proton/log.h"
+#include "proton/message.h"
+
+#include <benchmark/benchmark.h>
+#include <proton/listener.h>
+#include <proton/netaddr.h>
+#include <proton/proactor.h>
+#include <proton/sasl.h>
+#include <wait.h>
+
+#define MAX_SIZE 1024
+
+typedef char str[MAX_SIZE];
+
+const bool VERBOSE = false;
+const bool ERRORS = true;
+
+static void decode_message_buffer(pn_rwbytes_t data) {
+  pn_message_t *m = pn_message();
+  int err = pn_message_decode(m, data.start, data.size);
+  if (!err) {
+    /* Print the decoded message */
+    pn_string_t *s = pn_string(NULL);
+    pn_inspect(pn_message_body(m), s);
+    if (VERBOSE) {
+      printf("%s\n", pn_string_get(s));
+      fflush(stdout);
+    }
+    pn_free(s);
+    pn_message_free(m);
+    free(data.start);
+  } else {
+    fprintf(stderr, "decode error: %s\n", pn_error_text(pn_message_error(m)));
+    exit(EXIT_FAILURE);
+  }
+}
+
+static void BM_EncodeMapMessage(benchmark::State &state) {
+  if (VERBOSE)
+    printf("BEGIN BM_EncodeMapMessage\n");
+
+  uint64_t entries = 0;
+
+  pn_message_t *message = pn_message();
+
+  for (auto _ : state) {
+    pn_message_clear(message);
+    pn_data_t *body;
+    body = pn_message_body(message);
+    pn_data_put_map(body);
+    pn_data_enter(body);
+    for (size_t i = 0; i < state.range(0); ++i) {
+      pn_data_put_int(body, i);
+      pn_data_put_string(
+          body, pn_bytes(sizeof("some key value") - 1, "some key value"));
+      entries += 2;
+    }
+    pn_data_exit(body);
+    pn_rwbytes_t buf{};
+    if (pn_message_encode2(message, &buf) == 0) {
+      state.SkipWithError(pn_error_text(pn_message_error(message)));
+    }
+    if (buf.start)
+      free(buf.start);
+  }
+  pn_message_free(message);
+
+  state.SetLabel("entries");
+  state.SetItemsProcessed(entries);
+
+  if (VERBOSE)
+    printf("END BM_EncodeMapMessage\n");
+}
+
+BENCHMARK(BM_EncodeMapMessage)
+    ->Arg(10)
+    ->Arg(100)
+    ->Arg(1000)
+    ->Arg(10000)
+    ->ArgName("entries")
+    ->Unit(benchmark::kMillisecond);
+
+static void BM_CreateMapMessage(benchmark::State &state) {
+  if (VERBOSE)
+    printf("BEGIN BM_CreateMapMessage\n");
+
+  uint64_t entries = 0;
+
+  pn_message_t *message = pn_message();
+
+  pn_message_clear(message);
+  pn_data_t *body;
+  body = pn_message_body(message);
+  pn_data_put_map(body);
+  pn_data_enter(body);
+  for (auto _ : state) {
+    pn_data_put_int(body, entries);
+    pn_data_put_string(
+        body, pn_bytes(sizeof("some key value") - 1, "some key value"));
+    entries += 2;
+  }
+  pn_data_exit(body);
+  pn_message_free(message);
+
+  state.SetLabel("entries");
+  state.SetItemsProcessed(entries);
+
+  if (VERBOSE)
+    printf("END BM_CreateMapMessage\n");
+}
+
+BENCHMARK(BM_CreateMapMessage);
+
+
+static void decode_message(pn_delivery_t *dlv) {
+  static char buffer[MAX_SIZE];
+  ssize_t len;
+  // try to decode the message body
+  if (pn_delivery_pending(dlv) < MAX_SIZE) {
+    // read in the raw data
+    len = pn_link_recv(pn_delivery_link(dlv), buffer, MAX_SIZE);
+    if (len > 0) {
+      // decode it into a proton message
+      pn_message_t *m = pn_message();
+      if (PN_OK == pn_message_decode(m, buffer, len)) {
+        pn_string_t *s = pn_string(NULL);
+        pn_inspect(pn_message_body(m), s);
+        if (VERBOSE)
+          printf("%s\n", pn_string_get(s));
+        pn_free(s);
+      }
+      pn_message_free(m);
+    }
+  }
+}
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 2b5730e..59b6a01 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -331,3 +331,7 @@ endmacro(add_core_catch_test)
 
 add_catch_test(url)
 add_core_catch_test(object)
+
+if (ENABLE_BENCHMARKS)
+  add_subdirectory(benchmarks)
+endif (ENABLE_BENCHMARKS)
diff --git a/cpp/benchmarks/CMakeLists.txt b/cpp/benchmarks/CMakeLists.txt
new file mode 100644
index 0000000..63a5bd7
--- /dev/null
+++ b/cpp/benchmarks/CMakeLists.txt
@@ -0,0 +1,33 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+find_package(benchmark REQUIRED)
+
+# incompatibility in older libbenchmark, https://github.com/google/benchmark/issues/494#issuecomment-502444478
+get_source_file_property(COMPILE_FLAGS benchmarks_main.cpp current_compile_flags)
+set_source_files_properties(benchmarks_main.cpp PROPERTIES COMPILE_FLAGS "${current_compile_flags} -Wno-pedantic")
+
+add_executable(cpp-benchmarks benchmarks_main.cpp container.cpp)
+target_link_libraries(cpp-benchmarks benchmark pthread qpid-proton-cpp)
+
+add_test(NAME cpp-benchmarks COMMAND cpp-benchmarks)
diff --git a/cpp/benchmarks/README.md b/cpp/benchmarks/README.md
new file mode 100644
index 0000000..4ef5306
--- /dev/null
+++ b/cpp/benchmarks/README.md
@@ -0,0 +1,3 @@
+# Microbenchmarks
+
+See c/benchmarks for instructions
\ No newline at end of file
diff --git a/cpp/benchmarks/benchmarks_main.cpp b/cpp/benchmarks/benchmarks_main.cpp
new file mode 100644
index 0000000..71fefa0
--- /dev/null
+++ b/cpp/benchmarks/benchmarks_main.cpp
@@ -0,0 +1,3 @@
+#include <benchmark/benchmark.h>
+
+BENCHMARK_MAIN();
diff --git a/cpp/benchmarks/container.cpp b/cpp/benchmarks/container.cpp
new file mode 100644
index 0000000..21a9ba3
--- /dev/null
+++ b/cpp/benchmarks/container.cpp
@@ -0,0 +1,60 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <iostream>
+
+#include <benchmark/benchmark.h>
+
+#include <proton/connection_options.hpp>
+#include <proton/container.hpp>
+#include <proton/messaging_handler.hpp>
+
+// PROTON-2137 [cpp] Performance regression found in 0.29.0
+//  proton would spend long time initializing ssl when not needed
+
+class handler : public proton::messaging_handler {
+  std::string url;
+
+public:
+  explicit handler(const std::string &u) : url(u) {}
+
+private:
+  void on_container_start(proton::container &c) override {
+    c.connect(url, proton::connection_options().sasl_enabled(false));
+  }
+
+  void on_connection_open(proton::connection &c) override { c.close(); }
+};
+
+// expected to run in < 1 ms, for me it is 160 us
+static void BM_UnsuccessfulContainerRun(benchmark::State &state) {
+  for (auto _ : state) {
+    try {
+      handler h("127.0.0.1:0"); // wrong port
+      proton::container(h).run();
+      state.SkipWithError("Exception expected");
+    } catch (std::exception &e) {
+      // expected
+    }
+  }
+}
+
+BENCHMARK(BM_UnsuccessfulContainerRun)->Unit(benchmark::kMicrosecond);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org