diff --git a/examples/zpages/BUILD b/examples/zpages/BUILD index a277c44810..778c7ffe3a 100644 --- a/examples/zpages/BUILD +++ b/examples/zpages/BUILD @@ -24,8 +24,7 @@ cc_binary( "//conditions:default": ["-pthread"], }), deps = [ - "//ext:headers", - "//ext/src/zpages", + "//ext/zpages", "//sdk/src/trace", ], ) diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 75205ac71e..8d4e62acef 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -1,5 +1,2 @@ -add_subdirectory(src) - -if(BUILD_TESTING) - add_subdirectory(test) -endif() +add_subdirectory(http) +add_subdirectory(zpages) diff --git a/ext/http/CMakeLists.txt b/ext/http/CMakeLists.txt new file mode 100644 index 0000000000..9a84b352a6 --- /dev/null +++ b/ext/http/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(server) diff --git a/ext/BUILD b/ext/http/server/BUILD similarity index 100% rename from ext/BUILD rename to ext/http/server/BUILD diff --git a/ext/http/server/CMakeLists.txt b/ext/http/server/CMakeLists.txt new file mode 100644 index 0000000000..0b422b5d6a --- /dev/null +++ b/ext/http/server/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(http_server INTERFACE) + +target_include_directories(http_server INTERFACE include) diff --git a/ext/include/opentelemetry/ext/http/server/file_http_server.h b/ext/http/server/include/opentelemetry/ext/http/server/file_http_server.h similarity index 100% rename from ext/include/opentelemetry/ext/http/server/file_http_server.h rename to ext/http/server/include/opentelemetry/ext/http/server/file_http_server.h diff --git a/ext/include/opentelemetry/ext/http/server/http_server.h b/ext/http/server/include/opentelemetry/ext/http/server/http_server.h similarity index 100% rename from ext/include/opentelemetry/ext/http/server/http_server.h rename to ext/http/server/include/opentelemetry/ext/http/server/http_server.h diff --git a/ext/include/opentelemetry/ext/http/server/socket_tools.h b/ext/http/server/include/opentelemetry/ext/http/server/socket_tools.h similarity index 100% rename from ext/include/opentelemetry/ext/http/server/socket_tools.h rename to ext/http/server/include/opentelemetry/ext/http/server/socket_tools.h diff --git a/ext/src/CMakeLists.txt b/ext/src/CMakeLists.txt deleted file mode 100644 index 189a03f69c..0000000000 --- a/ext/src/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(zpages) diff --git a/ext/src/zpages/BUILD b/ext/src/zpages/BUILD deleted file mode 100644 index ab9f3507b8..0000000000 --- a/ext/src/zpages/BUILD +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2020, OpenTelemetry Authors -# -# Licensed 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. - -package(default_visibility = ["//visibility:public"]) - -cc_library( - name = "zpages", - srcs = glob(["**/*.cc"]), - hdrs = glob(["**/*.h"]), - include_prefix = "ext/zpages", - deps = [ - "//api", - "//ext:headers", - "//sdk:headers", - "@github_nlohmann_json//:json", - ], -) diff --git a/ext/src/zpages/CMakeLists.txt b/ext/src/zpages/CMakeLists.txt deleted file mode 100644 index dae0249bf4..0000000000 --- a/ext/src/zpages/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_library( - opentelemetry_zpages - tracez_processor.cc - tracez_data_aggregator.cc - ../../include/opentelemetry/ext/zpages/tracez_processor.h - ../../include/opentelemetry/ext/zpages/tracez_data_aggregator.h - ../../include/opentelemetry/ext/zpages/tracez_http_server.h) - -target_include_directories(opentelemetry_zpages PUBLIC ../../include) - -target_link_libraries(opentelemetry_zpages opentelemetry_api - opentelemetry_trace) diff --git a/ext/test/CMakeLists.txt b/ext/test/CMakeLists.txt deleted file mode 100644 index 189a03f69c..0000000000 --- a/ext/test/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(zpages) diff --git a/ext/test/zpages/BUILD b/ext/test/zpages/BUILD deleted file mode 100644 index 07c287d2fa..0000000000 --- a/ext/test/zpages/BUILD +++ /dev/null @@ -1,35 +0,0 @@ -cc_test( - name = "threadsafe_span_data_tests", - srcs = [ - "threadsafe_span_data_test.cc", - ], - deps = [ - "//ext/src/zpages", - "//sdk/src/trace", - "@com_google_googletest//:gtest_main", - ], -) - -cc_test( - name = "tracez_data_aggregator_tests", - srcs = [ - "tracez_data_aggregator_test.cc", - ], - deps = [ - "//ext/src/zpages", - "//sdk/src/trace", - "@com_google_googletest//:gtest_main", - ], -) - -cc_test( - name = "tracez_processor_tests", - srcs = [ - "tracez_processor_test.cc", - ], - deps = [ - "//ext/src/zpages", - "//sdk/src/trace", - "@com_google_googletest//:gtest_main", - ], -) diff --git a/ext/zpages/BUILD b/ext/zpages/BUILD new file mode 100644 index 0000000000..4937379edc --- /dev/null +++ b/ext/zpages/BUILD @@ -0,0 +1,14 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "zpages", + srcs = glob(["src/**/*.cc"]), + hdrs = glob(["include/**/*.h"]), + strip_include_prefix = "include", + deps = [ + "//api", + "//ext/http/server:headers", + "//sdk:headers", + "@github_nlohmann_json//:json", + ], +) diff --git a/ext/zpages/CMakeLists.txt b/ext/zpages/CMakeLists.txt new file mode 100644 index 0000000000..8690cbd3fd --- /dev/null +++ b/ext/zpages/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library( + opentelemetry_zpages src/tracez_processor.cc src/tracez_data_aggregator.cc + # TODO: add Cmake build for nlohmann JSON + # src/tracez_http_server.cc +) + +target_include_directories(opentelemetry_zpages PUBLIC include) + +target_link_libraries(opentelemetry_zpages opentelemetry_api + opentelemetry_trace http_server) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/ext/src/zpages/README.md b/ext/zpages/README.md similarity index 87% rename from ext/src/zpages/README.md rename to ext/zpages/README.md index e9e3eff7c3..db10fff5bb 100644 --- a/ext/src/zpages/README.md +++ b/ext/zpages/README.md @@ -1,6 +1,6 @@ # zPages ## Overview -zPages are a quick and light way to view tracing and metrics information on standard OpenTelemetry C++ instrumented applications. It requires no external dependencies or backend setup. See more information in the OTel zPages experimental [spec](https://github.com/open-telemetry/opentelemetry-specification/blob/5b86d4b6c42e6d1e47d9155ac1e2e27f0f0b7769/experimental/trace/zpages.md). OTel C++ currently only offers Tracez; future zPages to potentially add include TraceConfigz, RPCz, and Statsz. Events and links need to be added to Tracez. +zPages are a quick and light way to view tracing and metrics information on standard OpenTelemetry C++ instrumented applications. It requires no external dependencies or backend setup. See more information in the OTel zPages experimental [spec](https://github.com/open-telemetry/opentelemetry-specification/blob/5b86d4b6c42e6d1e47d9155ac1e2e27f0f0b7769/experimental/trace/zpages.md). OTel C++ currently only offers Tracez, which are currently not showing events and links; future zPages to potentially add include TraceConfigz, RPCz, and Statsz. There's also a Medium article on zPages [here](https://medium.com/opentelemetry/zpages-in-opentelemetry-2b080a81eb47). # Usage > TODO: Add CMake instructions @@ -25,3 +25,4 @@ zPages are a quick and light way to view tracing and metrics information on stan - [Tracez Span Processor](https://docs.google.com/document/d/1kO4iZARYyr-EGBlY2VNM3ELU3iw6ZrC58Omup_YT-fU/edit#) - [Tracez Data Aggregator](https://docs.google.com/document/d/1ziKFgvhXFfRXZjOlAHQRR-TzcNcTXzg1p2I9oPCEIoU/edit?ts=5ef0d177#heading=h.5irk4csrpu0y) - [Tracez Http Server](https://docs.google.com/document/d/1U1V8QZ5LtGl4Mich-aJ6KZGLHrMIE8pWyspmzvnIefI/edit#) - includes reference pictures of the zPages/Tracez UI + diff --git a/ext/include/opentelemetry/ext/zpages/latency_boundaries.h b/ext/zpages/include/opentelemetry/ext/zpages/latency_boundaries.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/latency_boundaries.h rename to ext/zpages/include/opentelemetry/ext/zpages/latency_boundaries.h diff --git a/ext/include/opentelemetry/ext/zpages/static/tracez_index.h b/ext/zpages/include/opentelemetry/ext/zpages/static/tracez_index.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/static/tracez_index.h rename to ext/zpages/include/opentelemetry/ext/zpages/static/tracez_index.h diff --git a/ext/include/opentelemetry/ext/zpages/static/tracez_script.h b/ext/zpages/include/opentelemetry/ext/zpages/static/tracez_script.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/static/tracez_script.h rename to ext/zpages/include/opentelemetry/ext/zpages/static/tracez_script.h diff --git a/ext/include/opentelemetry/ext/zpages/static/tracez_style.h b/ext/zpages/include/opentelemetry/ext/zpages/static/tracez_style.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/static/tracez_style.h rename to ext/zpages/include/opentelemetry/ext/zpages/static/tracez_style.h diff --git a/ext/include/opentelemetry/ext/zpages/threadsafe_span_data.h b/ext/zpages/include/opentelemetry/ext/zpages/threadsafe_span_data.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/threadsafe_span_data.h rename to ext/zpages/include/opentelemetry/ext/zpages/threadsafe_span_data.h diff --git a/ext/include/opentelemetry/ext/zpages/tracez_data.h b/ext/zpages/include/opentelemetry/ext/zpages/tracez_data.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/tracez_data.h rename to ext/zpages/include/opentelemetry/ext/zpages/tracez_data.h diff --git a/ext/include/opentelemetry/ext/zpages/tracez_data_aggregator.h b/ext/zpages/include/opentelemetry/ext/zpages/tracez_data_aggregator.h similarity index 98% rename from ext/include/opentelemetry/ext/zpages/tracez_data_aggregator.h rename to ext/zpages/include/opentelemetry/ext/zpages/tracez_data_aggregator.h index 6d2d0a054e..d8de9c467d 100644 --- a/ext/include/opentelemetry/ext/zpages/tracez_data_aggregator.h +++ b/ext/zpages/include/opentelemetry/ext/zpages/tracez_data_aggregator.h @@ -161,6 +161,9 @@ class TracezDataAggregator /** Condition variable that notifies the thread when object is about to be destroyed **/ std::condition_variable cv_; + + /** Friend class used for benchmarking **/ + friend class TracezDataAggregatorPeer; }; } // namespace zpages diff --git a/ext/include/opentelemetry/ext/zpages/tracez_http_server.h b/ext/zpages/include/opentelemetry/ext/zpages/tracez_http_server.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/tracez_http_server.h rename to ext/zpages/include/opentelemetry/ext/zpages/tracez_http_server.h diff --git a/ext/include/opentelemetry/ext/zpages/tracez_processor.h b/ext/zpages/include/opentelemetry/ext/zpages/tracez_processor.h similarity index 97% rename from ext/include/opentelemetry/ext/zpages/tracez_processor.h rename to ext/zpages/include/opentelemetry/ext/zpages/tracez_processor.h index c96461db74..6f35b2bb46 100644 --- a/ext/include/opentelemetry/ext/zpages/tracez_processor.h +++ b/ext/zpages/include/opentelemetry/ext/zpages/tracez_processor.h @@ -91,6 +91,9 @@ class TracezSpanProcessor : public opentelemetry::sdk::trace::SpanProcessor private: mutable std::mutex mtx_; CollectedSpans spans_; + + /** Friend class used for benchmarking **/ + friend class TracezProcessorPeer; }; } // namespace zpages } // namespace ext diff --git a/ext/include/opentelemetry/ext/zpages/zpages.h b/ext/zpages/include/opentelemetry/ext/zpages/zpages.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/zpages.h rename to ext/zpages/include/opentelemetry/ext/zpages/zpages.h diff --git a/ext/include/opentelemetry/ext/zpages/zpages_http_server.h b/ext/zpages/include/opentelemetry/ext/zpages/zpages_http_server.h similarity index 100% rename from ext/include/opentelemetry/ext/zpages/zpages_http_server.h rename to ext/zpages/include/opentelemetry/ext/zpages/zpages_http_server.h diff --git a/ext/src/zpages/tracez_data_aggregator.cc b/ext/zpages/src/tracez_data_aggregator.cc similarity index 100% rename from ext/src/zpages/tracez_data_aggregator.cc rename to ext/zpages/src/tracez_data_aggregator.cc diff --git a/ext/src/zpages/tracez_http_server.cc b/ext/zpages/src/tracez_http_server.cc similarity index 100% rename from ext/src/zpages/tracez_http_server.cc rename to ext/zpages/src/tracez_http_server.cc diff --git a/ext/src/zpages/tracez_processor.cc b/ext/zpages/src/tracez_processor.cc similarity index 100% rename from ext/src/zpages/tracez_processor.cc rename to ext/zpages/src/tracez_processor.cc diff --git a/ext/zpages/test/BUILD b/ext/zpages/test/BUILD new file mode 100644 index 0000000000..b642625918 --- /dev/null +++ b/ext/zpages/test/BUILD @@ -0,0 +1,61 @@ +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + +cc_test( + name = "threadsafe_span_data_test", + srcs = [ + "threadsafe_span_data_test.cc", + ], + deps = [ + "//ext/zpages", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "tracez_data_aggregator_test", + srcs = [ + "tracez_data_aggregator_test.cc", + ], + deps = [ + "//ext/zpages", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "tracez_processor_test", + srcs = [ + "tracez_processor_test.cc", + ], + deps = [ + "//ext/zpages", + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +otel_cc_benchmark( + name = "tracez_aggregator_benchmark", + srcs = [ + "tracez_data_aggregator_benchmark.cc", + ], + deps = [ + "//api", + "//ext/zpages", + "//sdk/src/trace", + ], +) + +otel_cc_benchmark( + name = "tracez_processor_benchmark", + srcs = [ + "tracez_processor_benchmark.cc", + ], + deps = [ + "//api", + "//ext/zpages", + "//sdk/src/trace", + ], +) diff --git a/ext/test/zpages/CMakeLists.txt b/ext/zpages/test/CMakeLists.txt similarity index 100% rename from ext/test/zpages/CMakeLists.txt rename to ext/zpages/test/CMakeLists.txt diff --git a/ext/test/zpages/threadsafe_span_data_test.cc b/ext/zpages/test/threadsafe_span_data_test.cc similarity index 100% rename from ext/test/zpages/threadsafe_span_data_test.cc rename to ext/zpages/test/threadsafe_span_data_test.cc diff --git a/ext/zpages/test/tracez_data_aggregator_benchmark.cc b/ext/zpages/test/tracez_data_aggregator_benchmark.cc new file mode 100644 index 0000000000..ad5925fec4 --- /dev/null +++ b/ext/zpages/test/tracez_data_aggregator_benchmark.cc @@ -0,0 +1,322 @@ +#include "opentelemetry/ext/zpages/tracez_data_aggregator.h" + +#include +#include +#include + +#include "opentelemetry/context/threadlocal_context.h" +#include "opentelemetry/sdk/trace/tracer.h" + +using namespace opentelemetry::sdk::trace; +using opentelemetry::core::SteadyTimestamp; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext +{ +namespace zpages +{ + +/////////////////////////////// BENCHMARK HELPER FUNCTIONS ////////////////////////////// + +/* + * Helper function that creates and ends num_spans spans instantly. If is_unique is + * true, then all spans will have different names. + */ +void StartEndSpans(std::shared_ptr &tracer, + unsigned int num_spans, + bool is_unique) +{ + opentelemetry::trace::StartSpanOptions start; + opentelemetry::trace::EndSpanOptions end; + start.start_steady_time = SteadyTimestamp(nanoseconds(0)); + end.end_steady_time = SteadyTimestamp(nanoseconds(1)); + for (unsigned int i = 0; i < num_spans; i++) + tracer->StartSpan(is_unique ? std::to_string(num_spans) : "", start)->End(end); +} + +/* + * Helper function that creates and ends num_spans spans instantly, while evenly + * spreading spans across all latency bands. If is_unique is true, then all spans + * will have different names. + */ +void StartEndSpansLatency(std::shared_ptr &tracer, + unsigned int num_spans, + bool is_unique, + unsigned int latency_span_offset) +{ + opentelemetry::trace::StartSpanOptions start; + start.start_steady_time = SteadyTimestamp(nanoseconds(0)); + for (unsigned int i = 0; i < num_spans; i++) + { + // Latency bucket depends on the index. Pass-by-ref offset means each span name will have all + // buckets filled + nanoseconds latency_band = + kLatencyBoundaries[num_spans + latency_span_offset % kLatencyBoundaries.size()]; + opentelemetry::trace::EndSpanOptions end; + end.end_steady_time = SteadyTimestamp(latency_band); + + tracer->StartSpan(is_unique ? std::to_string(num_spans) : "", start)->End(end); + } +} + +/* + * Helper function that creates and ends num_spans spans instantly, while + * simulating error codes. If is_unique is true, then all spans will have + * different names. + */ +void StartEndSpansError(std::shared_ptr &tracer, + unsigned int num_spans, + bool is_unique) +{ + for (unsigned int i = 0; i < num_spans; i++) + tracer + ->StartSpan(is_unique ? std::to_string(num_spans) : "") + // Random error status + ->SetStatus(opentelemetry::trace::CanonicalCode::CANCELLED, ""); +} + +/* + * Helper function that creates num_spans spans that always run. To remain in scope and running, + * spans are referenced in the passed in vector. If is_unique is true, then all spans + * will have different names. + */ +void StartSpans(std::vector> &spans, + std::shared_ptr &tracer, + unsigned int num_spans, + bool is_unique) +{ + for (unsigned int i = 0; i < num_spans; i++) + spans.push_back(tracer->StartSpan(is_unique ? std::to_string(num_spans) : "")); +} + +/* + * Helper function that creates about num_spans spans, evenly split between running, latency (which + * is further split by bucket), and error. If is_unique is true, then all spans will have + * different names. The running spans vector is returned so that they remain running. A vector is + * returned holding the running spans so they can be held in the caller function and remain running. + */ +std::vector> MakeManySpans( + std::shared_ptr &tracer, + unsigned int num_spans, + bool is_unique, + unsigned int &latency_span_offset) +{ + // Running spans must be stored in a vector in order to stay running, since only OnStart is called + // for those spans. This vector is only accessed by the run thread, as the other functions' spans + // automatically get moved to the processor memory when calling both OnStart and OnEnd + std::vector> running_spans; + // Use threads to speed up the work + std::thread run(StartSpans, std::ref(running_spans), std::ref(tracer), num_spans / 6, is_unique); + std::thread err(StartEndSpansError, std::ref(tracer), num_spans / 6, is_unique); + StartEndSpansLatency(tracer, num_spans * 3 / 4, is_unique, latency_span_offset); + run.join(); + err.join(); + // Increment offset for later + latency_span_offset++; + return running_spans; +} + +/////////////////////////// AGGREGATOR PEER ////////////////////////////// + +/* + * Friend class allows us to access aggregator private variables for + * benchmarking and isolation of different functions + */ +class TracezDataAggregatorPeer +{ +public: + TracezDataAggregatorPeer(std::shared_ptr processor) + { + // Set up the aggregator + aggregator_ = std::unique_ptr(new TracezDataAggregator(processor)); + + // Disable the aggregetor's periodic background thread aggregation work, which + // it normally does during production. Disabling it allows us to isolate + // aggregation work for benchmarking + if (aggregator_->execute_.load(std::memory_order_acquire)) + { + aggregator_->execute_.store(false, std::memory_order_release); + aggregator_->cv_.notify_one(); + aggregator_->aggregate_spans_thread_.join(); + } + } + + /* + * Join and end periodic query loop in background if it's running. + */ + ~TracezDataAggregatorPeer() + { + if (run_.load(std::memory_order_acquire)) + { + run_.store(false, std::memory_order_release); + query_thread_.join(); + } + } + + /* + * Calling the aggregation work function, locking as needed. + */ + void Aggregate() + { + std::lock_guard lock(mtx_); + aggregator_->AggregateSpans(); + } + + /* + * Starts a background thread to query for aggregated data every + * query_interval nanoseconds. This should affect performance for the + * aggregator, since aggregation work cannot be done while aggegation + * data is being requested. This simulates a user visiting or refreshing + * the Tracez webpage many times, except this function does and holds + * nothing in memory. + */ + void StartPeriodicQueryThread(nanoseconds query_interval = nanoseconds(1)) + { + run_.store(true, std::memory_order_release); + query_thread_ = std::thread([this, query_interval]() { + while (run_.load(std::memory_order_acquire)) + { + std::unique_lock lock(mtx_); + auto aggregations = aggregator_->GetAggregatedTracezData(); + // Continue if query interval passed + cont_.wait_for(lock, query_interval); + } + }); + } + +private: + std::unique_ptr aggregator_; + // Keep queries running while peer is in memory + std::thread query_thread_; + // Ensure queries and aggregations don't happen simultaneously + std::mutex mtx_; + std::atomic run_; + std::condition_variable cont_; +}; + +//////////////////////// FIXTURE FOR SHARED SETUP CODE /////////////////// + +/* + * This is a regular fixture that initializes the shared needed components + * to create spans and do the aggregation work for benchmarking. + */ +class TracezAggregator : public benchmark::Fixture +{ +protected: + void SetUp(const ::benchmark::State &state) + { + std::shared_ptr processor(new TracezSpanProcessor()); + tracer_ = std::shared_ptr(new Tracer(processor)); + aggregator_peer_ = + std::unique_ptr(new TracezDataAggregatorPeer(processor)); + } + + std::unique_ptr aggregator_peer_; + // Tracer for creating spans + std::shared_ptr tracer_; +}; + +//////////////////////////// BENCHMARK DEFINITIONS ///////////////////////////////// + +/* + * Aggregator handling many spans where minimal sorting of spans into different + * latency bands is required, as all spans should be sorted in the same bucket. + * It also means there's minimal memory usage for the Tracez data structure holding + * sampled spans. + */ +BENCHMARK_DEFINE_F(TracezAggregator, BM_SingleBucket)(benchmark::State &state) +{ + const unsigned int num_spans = state.range(0); + const bool is_unique = state.range(1); + const bool run_periodic_query = state.range(2); + + if (run_periodic_query) + aggregator_peer_->StartPeriodicQueryThread(); + + for (auto _ : state) + { + // Do not time span creation, as we're only benchmarking aggregation work + state.PauseTiming(); + StartEndSpans(tracer_, num_spans, is_unique); + state.ResumeTiming(); + aggregator_peer_->Aggregate(); + } +} + +/* + * Aggregator handling many spans who may fall under error, running, and any + * latency group. This requires the aggregator to sorting of spans into their + * respective buckets (running, error, latency [including within latency bands]), + * which also utilizes more memory. + */ +BENCHMARK_DEFINE_F(TracezAggregator, BM_ManyBuckets)(benchmark::State &state) +{ + const unsigned int num_spans = state.range(0); + const bool is_unique = state.range(1); + const bool run_periodic_query = state.range(2); + + // Ensure spans get added to each latency bucket + unsigned int latency_index_offset = 0; + + if (run_periodic_query) + aggregator_peer_->StartPeriodicQueryThread(); + + for (auto _ : state) + { + state.PauseTiming(); + std::vector> running_spans = + MakeManySpans(tracer_, num_spans, is_unique, latency_index_offset); + state.ResumeTiming(); + aggregator_peer_->Aggregate(); + } +} + +//////////////////////////// RUN BENCHMARKS /////////////////////////////// + +/* Args: spans, is_unique, run_periodic_query + * + * @num_spans int number of spans created for each iteration + * @is_unique bool whether spans are unique or not + * @run_periodic_query bool whether to run a background periodic thread of not + */ + +BENCHMARK_REGISTER_F(TracezAggregator, BM_SingleBucket) + /* + * Same name spans, with different number of spans and periodic query status + * + * Spans all have the same name, which means there's work to clear stored spans + * from memory fairly often so that the number of sampled spans won't go over + * the max and memory use is minimal, which are performance factors + */ + ->Args({10, false, false}) + ->Args({10, false, true}) + ->Args({1000, false, false}) + ->Args({1000, false, true}) + /* + * Many name spans, with different number of spans and periodic query status + * + * Spans have num_spans unique names, so many more spans are kept stored in memory + * and not cleared as often, which affects performance + */ + ->Args({10, true, false}) + ->Args({10, true, true}) + ->Args({1000, true, false}) + ->Args({1000, true, true}); + +// Do same permutation of number of spans, span uniqueness, and periodic thread +// running for many buckets too +BENCHMARK_REGISTER_F(TracezAggregator, BM_ManyBuckets) + ->Args({10, false, false}) + ->Args({10, false, true}) + ->Args({1000, false, false}) + ->Args({1000, false, true}) + ->Args({10, true, false}) + ->Args({10, true, true}) + ->Args({1000, true, false}) + ->Args({1000, true, true}); + +} // namespace zpages +} // namespace ext +OPENTELEMETRY_END_NAMESPACE + +BENCHMARK_MAIN(); diff --git a/ext/test/zpages/tracez_data_aggregator_test.cc b/ext/zpages/test/tracez_data_aggregator_test.cc similarity index 100% rename from ext/test/zpages/tracez_data_aggregator_test.cc rename to ext/zpages/test/tracez_data_aggregator_test.cc diff --git a/ext/zpages/test/tracez_processor_benchmark.cc b/ext/zpages/test/tracez_processor_benchmark.cc new file mode 100644 index 0000000000..297c6fffa2 --- /dev/null +++ b/ext/zpages/test/tracez_processor_benchmark.cc @@ -0,0 +1,284 @@ +#include "opentelemetry/ext/zpages/tracez_processor.h" + +#include +#include + +#include "opentelemetry/context/threadlocal_context.h" + +using namespace opentelemetry::sdk::trace; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext +{ +namespace zpages +{ + +/////////////////////////////// BENCHMARK HELPER FUNCTIONS ////////////////////////////// + +/* + * Helper function that creates num_spans threadsafe span data, adding them to the spans + * vector during setup to benchmark OnStart and OnEnd times for the processor. + */ +void CreateRecordables(std::vector> &spans, + unsigned int num_spans) +{ + for (unsigned int i = 0; i < num_spans; i++) + spans.push_back(std::unique_ptr(new ThreadsafeSpanData())); +} + +/* + * Helper function calls GetSpanSnapshot() num_snapshots times, does nothing otherwise. + * Snapshots are significant and contribute to performance differences because + * completed spans are cleared from the processor memory. This function + * simulates an aggregator querying the processor many times, but doesn't + * aggregate or store spans. + */ +void GetManySnapshots(std::shared_ptr &processor, unsigned int num_snapshots) +{ + for (unsigned int i = 0; i < num_snapshots; i++) + processor->GetSpanSnapshot(); +} + +/////////////////////////// PROCESSOR PEER ////////////////////////////// + +/* + * Friend class allows us to access processor private variables for + * benchmarking. It also reduces allows isolation of processor functions + * to minimize benchmarking noise, since we don't do the extra computation + * of setting variables the tracer would do with StartSpan and EndSpan + */ + +class TracezProcessorPeer +{ +public: + TracezProcessorPeer(std::shared_ptr &processor) { processor_ = processor; } + + /* + * Calls TracezProcessor::OnStart on each span in spans. + */ + void StartAllSpans(std::vector> &spans) + { + for (auto &span : spans) + processor_->OnStart(*(span.get())); + } + + /* + * Calls TracezProcessor::OnEnd on each span in spans, which gives ownership + * of those spans to the processor. Clears the spans vector since everything in + * the spans vector is now garbage. + */ + void EndAllSpans(std::vector> &spans) + { + for (auto &span : spans) + processor_->OnEnd(std::move(span)); + spans.clear(); + } + + /* + * Clears running span pointers in processor memory, which is used in between + * iterations so that processor->spans_.running doesn't hold nullptr + */ + void ClearRunning() { processor_->spans_.running.clear(); } + +private: + std::shared_ptr processor_; +}; + +//////////////////////// FIXTURE FOR SHARED SETUP CODE /////////////////// + +class TracezProcessor : public benchmark::Fixture +{ +protected: + void SetUp(const ::benchmark::State &state) + { + processor_ = std::shared_ptr(new TracezSpanProcessor()); + processor_peer_ = std::unique_ptr(new TracezProcessorPeer(processor_)); + } + std::vector> spans_; + std::unique_ptr processor_peer_; + std::shared_ptr processor_; +}; + +//////////////////////////// BENCHMARK DEFINITIONS ///////////////////////////////// + +/* + * Make many empty spans. This checks the scenario where the processor holds many + * running spans but never gets queried. + */ +BENCHMARK_DEFINE_F(TracezProcessor, BM_MakeRunning)(benchmark::State &state) +{ + const unsigned int num_spans = state.range(0); + for (auto _ : state) + { + state.PauseTiming(); + // Clear span vector if EndAllSpans for spans isn't called to avoid double + // starting spans + spans_.clear(); + // Clear processor memory, which ensures no nullptrs are in running spans are + // set from previous iterations when EndAllSpans isn't called on span/spans2 + processor_peer_->ClearRunning(); + CreateRecordables(spans_, num_spans); + state.ResumeTiming(); + + processor_peer_->StartAllSpans(spans_); + } +} + +/* + * End many empty spans. This checks the scenario where the processor holds many + * completed spans but never gets queried. + */ +BENCHMARK_DEFINE_F(TracezProcessor, BM_MakeComplete)(benchmark::State &state) +{ + const unsigned int num_spans = state.range(0); + for (auto _ : state) + { + state.PauseTiming(); + CreateRecordables(spans_, num_spans); + processor_peer_->StartAllSpans(spans_); + state.ResumeTiming(); + + processor_peer_->EndAllSpans(spans_); + } +} + +/* + * Make many snapshots. This checks the scenario where the processor holds no spans + * but gets queried many times. + */ +BENCHMARK_DEFINE_F(TracezProcessor, BM_GetSpans)(benchmark::State &state) +{ + const unsigned int num_snapshots = state.range(0); + for (auto _ : state) + GetManySnapshots(processor_, num_snapshots); +} + +/* + * Make and end many empty spans. This checks the scenario where the processor holds + * many running and completed spans but never gets queried. + */ +BENCHMARK_DEFINE_F(TracezProcessor, BM_MakeRunningMakeComplete)(benchmark::State &state) +{ + const unsigned int num_spans = state.range(0); + for (auto _ : state) + { + state.PauseTiming(); + processor_peer_->ClearRunning(); + std::vector> spans2; + CreateRecordables(spans_, num_spans); + CreateRecordables(spans2, num_spans); + processor_peer_->StartAllSpans(spans_); + state.ResumeTiming(); + + std::thread start(&TracezProcessorPeer::StartAllSpans, processor_peer_.get(), std::ref(spans2)); + processor_peer_->EndAllSpans(spans_); + + start.join(); + } +} + +/* + * Make many empty spans while snapshots grabbed. This checks the scenario where the + * processor holds many running spans and gets queried. + */ +BENCHMARK_DEFINE_F(TracezProcessor, BM_MakeRunningGetSpans)(benchmark::State &state) +{ + const unsigned int num_spans_snaps = state.range(0); // number of spans and snapshots + for (auto _ : state) + { + state.PauseTiming(); + spans_.clear(); + processor_peer_->ClearRunning(); + CreateRecordables(spans_, num_spans_snaps); + state.ResumeTiming(); + + std::thread start(&TracezProcessorPeer::StartAllSpans, processor_peer_.get(), std::ref(spans_)); + GetManySnapshots(processor_, num_spans_snaps); + + start.join(); + } +} + +/* + * Make many empty spans end while snapshots are being grabbed. This checks the scenario + * where the processor doesn't make new spans, but existing spans complete while they're + * queried. + */ +BENCHMARK_DEFINE_F(TracezProcessor, BM_GetSpansMakeComplete)(benchmark::State &state) +{ + const unsigned int num_spans_snaps = state.range(0); + for (auto _ : state) + { + state.PauseTiming(); + CreateRecordables(spans_, num_spans_snaps); + processor_peer_->StartAllSpans(spans_); + state.ResumeTiming(); + + std::thread end(&TracezProcessorPeer::EndAllSpans, processor_peer_.get(), std::ref(spans_)); + GetManySnapshots(processor_, num_spans_snaps); + + end.join(); + } +} + +/* + * Make many empty spans and end some, all while snapshots are being grabbed. This + * checks the scenario where the processor makes new spans, other spans complete, + * and all spans are queried. This is the case most similar to real situations. + */ +BENCHMARK_DEFINE_F(TracezProcessor, BM_MakeRunningGetSpansMakeComplete)(benchmark::State &state) +{ + const unsigned int num_spans_snaps = state.range(0); + for (auto _ : state) + { + state.PauseTiming(); + processor_peer_->ClearRunning(); + std::vector> spans2; + CreateRecordables(spans_, num_spans_snaps); + CreateRecordables(spans2, num_spans_snaps); + processor_peer_->StartAllSpans(spans_); + state.ResumeTiming(); + + std::thread end(&TracezProcessorPeer::EndAllSpans, processor_peer_.get(), std::ref(spans_)); + std::thread start(&TracezProcessorPeer::StartAllSpans, processor_peer_.get(), std::ref(spans2)); + GetManySnapshots(processor_, num_spans_snaps); + + start.join(); + end.join(); + } +} + +/////////////////////// RUN BENCHMARKS /////////////////////////// + +// Arg is the number of spans created for each iteration +BENCHMARK_REGISTER_F(TracezProcessor, BM_MakeRunning)->Arg(10)->Arg(1000); +BENCHMARK_REGISTER_F(TracezProcessor, BM_MakeComplete)->Arg(10)->Arg(1000); +BENCHMARK_REGISTER_F(TracezProcessor, BM_GetSpans)->Arg(10)->Arg(1000); + +// These use multiple threads, so that CPU usage and thread works needs to be measured +BENCHMARK_REGISTER_F(TracezProcessor, BM_MakeRunningMakeComplete) + ->Arg(10) + ->Arg(1000) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK_REGISTER_F(TracezProcessor, BM_MakeRunningGetSpans) + ->Arg(10) + ->Arg(1000) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK_REGISTER_F(TracezProcessor, BM_GetSpansMakeComplete) + ->Arg(10) + ->Arg(1000) + ->MeasureProcessCPUTime() + ->UseRealTime(); +BENCHMARK_REGISTER_F(TracezProcessor, BM_MakeRunningGetSpansMakeComplete) + ->Arg(10) + ->Arg(1000) + ->MeasureProcessCPUTime() + ->UseRealTime(); + +} // namespace zpages +} // namespace ext +OPENTELEMETRY_END_NAMESPACE + +BENCHMARK_MAIN(); diff --git a/ext/test/zpages/tracez_processor_test.cc b/ext/zpages/test/tracez_processor_test.cc similarity index 100% rename from ext/test/zpages/tracez_processor_test.cc rename to ext/zpages/test/tracez_processor_test.cc