diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fbfc1c059..94b6c44354 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,3 +56,4 @@ add_subdirectory(sdk) include_directories(.) add_subdirectory(exporters) add_subdirectory(examples) +add_subdirectory(ext) diff --git a/ext/BUILD b/ext/BUILD new file mode 100644 index 0000000000..cc62431b53 --- /dev/null +++ b/ext/BUILD @@ -0,0 +1,7 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "headers", + hdrs = glob(["include/**/*.h"]), + strip_include_prefix = "include", +) diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt new file mode 100644 index 0000000000..75205ac71e --- /dev/null +++ b/ext/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(src) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/ext/include/opentelemetry/ext/zpages/tracez_processor.h b/ext/include/opentelemetry/ext/zpages/tracez_processor.h new file mode 100644 index 0000000000..aca13d72fa --- /dev/null +++ b/ext/include/opentelemetry/ext/zpages/tracez_processor.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "opentelemetry/sdk/trace/processor.h" +#include "opentelemetry/sdk/trace/recordable.h" +#include "opentelemetry/sdk/trace/span_data.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext +{ +namespace zpages +{ +/* + * The span processor passes and stores running and completed recordables (casted as span_data) + * to be used by the TraceZ Data Aggregator. + */ +class TracezSpanProcessor : public opentelemetry::sdk::trace::SpanProcessor { + public: + + struct CollectedSpans { + std::unordered_set running; + std::vector> completed; + }; + + /* + * Initialize a span processor. + */ + explicit TracezSpanProcessor() noexcept {} + + /* + * Create a span recordable, which is span_data + * @return a newly initialized recordable + */ + std::unique_ptr MakeRecordable() noexcept override + { + return std::unique_ptr(new opentelemetry::sdk::trace::SpanData); + } + + /* + * OnStart is called when a span starts; the recordable is cast to span_data and added to running_spans. + * @param span a recordable for a span that was just started + */ + void OnStart(opentelemetry::sdk::trace::Recordable &span) noexcept override; + + /* + * OnEnd is called when a span ends; that span_data is moved from running_spans to + * completed_spans + * @param span a recordable for a span that was ended + */ + void OnEnd(std::unique_ptr &&span) noexcept override; + + /* + * Returns a snapshot of all spans stored. This snapshot has a copy of the + * stored running_spans and gives ownership of completed spans to the caller. + * Stored completed_spans are cleared from the processor. Currently, + * copy-on-write is utilized where possible to minimize contention, but locks + * may be added in the future. + * @return snapshot of all currently running spans and newly completed spans + * (spans never sent while complete) at the time that the function is called + */ + CollectedSpans GetSpanSnapshot() noexcept; + + /* + * For now, does nothing. In the future, it + * may send all ended spans that have not yet been sent to the aggregator. + * @param timeout an optional timeout, the default timeout of 0 means that no + * timeout is applied. Currently, timeout does nothing. + */ + void ForceFlush( + std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override {} + + /* + * Shut down the processor and do any cleanup required, which is none. + * After the call to Shutdown, subsequent calls to OnStart, OnEnd, ForceFlush + * or Shutdown will return immediately without doing anything. + * @param timeout an optional timeout, the default timeout of 0 means that no + * timeout is applied. Currently, timeout does nothing. + */ + void Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override {} + + private: + mutable std::mutex mtx_; + CollectedSpans spans_; +}; +} // namespace zpages +} // namespace ext +OPENTELEMETRY_END_NAMESPACE diff --git a/ext/src/CMakeLists.txt b/ext/src/CMakeLists.txt new file mode 100644 index 0000000000..189a03f69c --- /dev/null +++ b/ext/src/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(zpages) diff --git a/ext/src/zpages/BUILD b/ext/src/zpages/BUILD new file mode 100644 index 0000000000..4b1a51df5c --- /dev/null +++ b/ext/src/zpages/BUILD @@ -0,0 +1,27 @@ +# 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", + "//sdk:headers", + "//ext:headers", + ], +) diff --git a/ext/src/zpages/CMakeLists.txt b/ext/src/zpages/CMakeLists.txt new file mode 100644 index 0000000000..7d912f195e --- /dev/null +++ b/ext/src/zpages/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(opentelemetry_zpages + tracez_processor.cc + ../../include/opentelemetry/ext/zpages/tracez_processor.h) + +target_include_directories(opentelemetry_zpages PUBLIC ../../include) + +target_link_libraries(opentelemetry_zpages opentelemetry_api opentelemetry_trace) + diff --git a/ext/src/zpages/README.md b/ext/src/zpages/README.md index f1bad98516..8e8fed394a 100644 --- a/ext/src/zpages/README.md +++ b/ext/src/zpages/README.md @@ -1,7 +1,6 @@ # zPages -> Last updated 6/26/20 -# Table of Contents +## Table of Contents - [Summary](#summary) - [TraceZ](#tracez) - [RPCz](#rpcz) @@ -9,14 +8,14 @@ - [Links of Interest](#links-of-interest) ## Summary -zPages allow easy viewing of tracing information. When included for a process, zPages will display basic information about that process on a webpage. There are currently two types of zPages: TraceZ and RPCz. +zPages allow easy viewing of tracing information. When included for a process, zPages will display basic information about that process on a webpage. Two types of zPages include TraceZ and RPCz. -Including a zPage within a page is useful for developers because it's quicker to get running than adding extra code and installing external exporters like Jaeger and Zipkin. zPages tend to be more lightweight than these external exporters, but are also helpful for debugging latency issues and deadlocks. +Including a zPage within a page is useful for developers because it's quicker to get running than adding extra code and installing external exporters like Jaeger and Zipkin. zPages tend to be more lightweight than these external exporters, but are also helpful for debugging latency issues (slow parts of applications) and deadlocks (running spans that don't end). The idea of "zPages" originates from one of OpenTelemetry's predecessors, [OpenCensus](https://opencensus.io/). You can read more about it [here](https://opencensus.io/zpages). OpenCensus has different zPage implementations in [Java](https://opencensus.io/zpages/java/), [Go](https://opencensus.io/zpages/go/), and [Node](https://opencensus.io/zpages/node/) and there has been similar internal solutions developed at companies like Uber, but *this is the first major open-source implementation of zPages in C++*. Within OpenTelemetry, zPages are also being developed in [Java](https://github.com/open-telemetry/opentelemetry-java). #### How It Works -On a high level, zPages work by reading a process' spans using a SpanProcessor, which exports spans to the appropriate DataAggregator that a HttpServer uses. +On a high level, an application creates spans using a Tracer Provider/Tracer who passes them to a Span Processor, which exports spans to the appropriate Data Aggregator that a Http Server displays information for. > TODO: Add picture examples for span overview and individual span view @@ -24,11 +23,11 @@ On a high level, zPages work by reading a process' spans using a SpanProcessor, TraceZ is a type of zPage that shows information on tracing spans, and allows users to look closer at specific and individual spans. Details a user would view include span id, name, status, and timestamps. The individual components of TraceZ are as follows: - TracezSpanProcessor (TSP) - - Contact point for TraceZ to connect with a process, which collects tracing information and provides an interface for TDA. + - A tracer/tracer provider (which the user chooses) creates spans, which connects to TSP so that TraceZ to detect spans. The TSP then stores tracing information in running and completed containers and provides an interface for TDA to access their information. - TracezDataAggregator (TDA) - - Intermediary between the TSP and THS, which also performs various functions and calculations to send the correct tracing information to the THS. + - Intermediary between the TSP and THS, which also performs various functions and calculations (mainly grouping spans by their names and latency times) to send the correct tracing information to the THS. - TracezHttpServer (THS) - - User-facing web page generator, which creates HTML pages using TDA that display 1) overall information on all of the process's spans and 2) more detailed information on specific spans when clicked. + - User-facing web page generator, which creates HTML pages using TDA that display 1) overall information and trends on all of the process's spans and 2) more detailed information on specific spans when clicked. ### RPCz RPCz is a type of zPage that provides details on instrumented sent and received RPC messages. Although there is currently no ongoing development of RPCz for OpenTelemetry, OpenCensus zPages have implementations of RPCz (linked above). @@ -41,6 +40,6 @@ RPCz is a type of zPage that provides details on instrumented sent and received - [TracezSpanProcessor Design Doc](https://docs.google.com/document/d/1kO4iZARYyr-EGBlY2VNM3ELU3iw6ZrC58Omup_YT-fU/edit#) (pending review) - [TracezDataAggregator Design Doc](https://docs.google.com/document/d/1ziKFgvhXFfRXZjOlAHQRR-TzcNcTXzg1p2I9oPCEIoU/edit?ts=5ef0d177#heading=h.5irk4csrpu0y) (pending review) - [TracezHttpServer Design Doc](https://docs.google.com/document/d/1U1V8QZ5LtGl4Mich-aJ6KZGLHrMIE8pWyspmzvnIefI/edit#) (draft) -- [Contribution Guidelines](https://github.com/open-telemetry/opentelemetry-cpp/blob/master/CONTRIBUTING.md) - - +- [zPages General Direction Spec](https://github.com/open-telemetry/oteps/blob/master/text/0110-z-pages.md) +- [Prospective Fields Displayed by + TraceZ](https://github.com/open-telemetry/opentelemetry-cpp/blob/master/sdk/include/opentelemetry/sdk/trace/span_data.h) diff --git a/ext/src/zpages/tracez_processor.cc b/ext/src/zpages/tracez_processor.cc new file mode 100644 index 0000000000..b76f49d72a --- /dev/null +++ b/ext/src/zpages/tracez_processor.cc @@ -0,0 +1,38 @@ +#include "opentelemetry/ext/zpages/tracez_processor.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace ext { +namespace zpages { + + void TracezSpanProcessor::OnStart(opentelemetry::sdk::trace::Recordable &span) noexcept { + std::lock_guard lock(mtx_); + spans_.running.insert(static_cast(&span)); + } + + void TracezSpanProcessor::OnEnd(std::unique_ptr &&span) noexcept { + if (span == nullptr) return; + auto span_raw = static_cast(span.get()); + std::lock_guard lock(mtx_); + auto span_it = spans_.running.find(span_raw); + if (span_it != spans_.running.end()) { + spans_.running.erase(span_it); + spans_.completed.push_back( + std::unique_ptr( + static_cast(span.release()))); + } + } + + + TracezSpanProcessor::CollectedSpans TracezSpanProcessor::GetSpanSnapshot() noexcept { + CollectedSpans snapshot; + std::lock_guard lock(mtx_); + snapshot.running = spans_.running; + snapshot.completed = std::move(spans_.completed); + spans_.completed.clear(); + return snapshot; + } + + +} // namespace zpages +} // namespace ext +OPENTELEMETRY_END_NAMESPACE diff --git a/ext/test/CMakeLists.txt b/ext/test/CMakeLists.txt new file mode 100644 index 0000000000..189a03f69c --- /dev/null +++ b/ext/test/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(zpages) diff --git a/ext/test/zpages/BUILD b/ext/test/zpages/BUILD new file mode 100644 index 0000000000..50d027dbbd --- /dev/null +++ b/ext/test/zpages/BUILD @@ -0,0 +1,11 @@ +cc_test( + name = "tracez_processor_tests", + srcs = [ + "tracez_processor_test.cc", + ], + deps = [ + "//sdk/src/trace", + "//ext/src/zpages", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/ext/test/zpages/CMakeLists.txt b/ext/test/zpages/CMakeLists.txt new file mode 100644 index 0000000000..c0e1ddc15b --- /dev/null +++ b/ext/test/zpages/CMakeLists.txt @@ -0,0 +1,13 @@ +foreach(testname + tracez_processor_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries( + ${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_zpages) + + gtest_add_tests( + TARGET ${testname} + TEST_PREFIX ext. + TEST_LIST ${testname}) +endforeach() + diff --git a/ext/test/zpages/tracez_processor_test.cc b/ext/test/zpages/tracez_processor_test.cc new file mode 100644 index 0000000000..53fa1dba6b --- /dev/null +++ b/ext/test/zpages/tracez_processor_test.cc @@ -0,0 +1,586 @@ +#include "opentelemetry/ext/zpages/tracez_processor.h" + +#include + +#include + +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/sdk/trace/tracer.h" + +using namespace opentelemetry::sdk::trace; +using namespace opentelemetry::ext::zpages; + +//////////////////////////////////// TEST HELPER FUNCTIONS ////////////////////////////// + +/* + * Helper function uses the current processor to update spans contained in completed_spans + * and running_spans. completed_spans contains all spans (cumulative), unless marked otherwise + */ +void UpdateSpans(std::shared_ptr& processor, + std::vector>& completed, + std::unordered_set& running, + bool store_only_new_completed = false) { + auto spans = processor->GetSpanSnapshot(); + running = spans.running; + if (store_only_new_completed) { + completed.clear(); + completed = std::move(spans.completed); + } else { + std::move(spans.completed.begin(), spans.completed.end(), + std::inserter(completed, completed.end())); + } + spans.completed.clear(); +} + + +/* + * Returns true if all the span names in the name vector within the given range appears in + * at least the same frequency as they do in running_spans. + * + * If no start value is given, start at index 0 + * If no end value is given, end at name vector end + * If 1-1 correspondance marked, return true if completed has all names in same frequency, + * no more or less + */ +bool ContainsNames(const std::vector& names, + std::unordered_set& running, + unsigned int name_start = 0, unsigned int name_end = 0, + bool one_to_one_correspondence = false) { + if (name_end == 0) name_end = names.size(); + + unsigned int num_names = name_end - name_start; + + if (num_names > running.size() || // More names than spans, can't have all names + (one_to_one_correspondence && num_names != running.size())) { + return false; + } + std::vector is_contained(num_names, false); + + // Mark all names that are contained only once + // in the order they appear + for (auto &span : running) { + for (unsigned int i = 0; i < num_names; i++) { + if (span->GetName() == names[name_start + i] && !is_contained[i]) { + is_contained[i] = true; + break; + } + } + } + + for (auto &&b : is_contained) if (!b) return false; + + return true; +} + + +/* + * Returns true if all the span names in the nam vector within the given range appears in + * at least the same frequency as they do in completed_spans + * + * If no start value is given, start at index 0 + * If no end value is given, end at name vector end + * If 1-1 correspondance marked, return true if completed has all names in same frequency, + * no more or less + */ +bool ContainsNames(const std::vector& names, + std::vector>& completed, + unsigned int name_start = 0, unsigned int name_end = 0, + bool one_to_one_correspondence = false) { + + if (name_end == 0) name_end = names.size(); + + unsigned int num_names = name_end - name_start; + + if (num_names > completed.size() || + (one_to_one_correspondence && num_names != completed.size())) { + return false; + } + std::vector is_contained(num_names, false); + + for (auto &span : completed) { + for (unsigned int i = 0; i < num_names; i++) { + if (span->GetName() == names[name_start + i] && !is_contained[i]) { + is_contained[i] = true; + break; + } + } + } + + for (auto &&b : is_contained) if (!b) return false; + + return true; +} + + +/* + * Helper function calls GetSpanSnapshot() i times and does nothing with it + * otherwise. Used for testing thread safety + */ +void GetManySnapshots(std::shared_ptr& processor, int i) { + for (; i > 0; i--) processor->GetSpanSnapshot(); +} + + +/* + * Helper function that creates i spans, which are added into the passed + * in vector. Used for testing thread safety + */ +void StartManySpans(std::vector> &spans, + std::shared_ptr tracer, int i) { + for (; i > 0; i--) spans.push_back(tracer->StartSpan("span")); +} + + +/* + * Helper function that ends all spans in the passed in span vector. Used + * for testing thread safety + */ +void EndAllSpans(std::vector> &spans) { + for (auto &span : spans) span->End(); +} + + +//////////////////////////////// TEST FIXTURE ////////////////////////////////////// + +/* + * Reduce code duplication by having single area with shared setup code + */ +class TracezProcessor : public ::testing::Test { + protected: + void SetUp() override { + processor = std::shared_ptr(new TracezSpanProcessor()); + tracer = std::shared_ptr(new Tracer(processor)); + auto spans = processor->GetSpanSnapshot(); + running = spans.running; + completed = std::move(spans.completed); + + span_names = {"s0", "s2", "s1", "s1", "s"}; + + } + + std::shared_ptr processor; + std::shared_ptr tracer; + + std::unordered_set running; + std::vector> completed; + + std::vector span_names; + std::vector> span_vars; + +}; + + +///////////////////////////////////////// TESTS /////////////////////////////////// + +/* + * Test if both span containers are empty when no spans exist or are added. + * Ensures no rogue spans appear in the containers somehow. + */ +TEST_F(TracezProcessor, NoSpans) { + auto recordable = processor->MakeRecordable(); + + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 0); +} + + +/* + * Test if a single span moves from running to completed at expected times. + * All completed spans are stored. Ensures basic functionality and that accumulation + * can happen +*/ +TEST_F(TracezProcessor, OneSpanCumulative) { + auto span = tracer->StartSpan(span_names[0]); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 1, true)); + EXPECT_EQ(running.size(), 1); + EXPECT_EQ(completed.size(), 0); + + span->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1, true)); + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 1); +} + + +/* + * Test if multiple spans move from running to completed at expected times. Check if + * all are in a container, either running/completed during checks. Ensures basic functionality + * and that accumulation can happen for many spans + * All completed spans are stored. +*/ +TEST_F(TracezProcessor, MultipleSpansCumulative) { + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 0); + + // Start and store spans using span_names + for (const auto &name : span_names) span_vars.push_back(tracer->StartSpan(name)); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running)); // s0 s2 s1 s1 s + EXPECT_EQ(running.size(), span_names.size()); + EXPECT_EQ(completed.size(), 0); + + // End all spans + for (auto &span : span_vars) span->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, completed)); // s0 s2 s1 s1 s + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), span_names.size()); +} + + +/* + * Test if multiple spans move from running to completed at expected times, + * running/completed spans are split. Middle spans end first. Ensures basic functionality + * and that accumulation can happen for many spans even spans that start and end non- + * sequentially. All completed spans are stored. +*/ +TEST_F(TracezProcessor, MultipleSpansMiddleSplitCumulative) { + for (const auto &name : span_names) span_vars.push_back(tracer->StartSpan(name)); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running)); // s0 s2 s1 s1 s + EXPECT_EQ(running.size(), span_names.size()); + EXPECT_EQ(completed.size(), 0); + + // End 4th span + span_vars[3]->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 3)); // s0 s2 s1 + EXPECT_TRUE(ContainsNames(span_names, running, 4)); // + s + EXPECT_TRUE(ContainsNames(span_names, completed, 3, 4)); // s1 + EXPECT_EQ(running.size(), 4); + EXPECT_EQ(completed.size(), 1); + + // End 2nd span + span_vars[1]->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 1)); // s0 + EXPECT_TRUE(ContainsNames(span_names, running, 2, 3)); // + s1 + EXPECT_TRUE(ContainsNames(span_names, running, 4)); // + s + EXPECT_TRUE(ContainsNames(span_names, completed, 1, 2)); // s2 + EXPECT_TRUE(ContainsNames(span_names, completed, 3, 4)); // s1 + EXPECT_EQ(running.size(), 3); + EXPECT_EQ(completed.size(), 2); + + // End 3rd span (last middle span) + span_vars[2]->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 1)); // s0 + EXPECT_TRUE(ContainsNames(span_names, running, 4)); // + s + EXPECT_TRUE(ContainsNames(span_names, completed, 1, 4)); // s2 s1 s1 + EXPECT_EQ(running.size(), 2); + EXPECT_EQ(completed.size(), 3); + + // End remaining Spans + span_vars[0]->End(); + span_vars[4]->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, completed)); // s0 s2 s1 s1 s + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 5); +} + + +/* + * Test if multiple spans move from running to completed at expected times, + * running/completed spans are split. Ensures basic functionality and that + * accumulation can happen for many spans even spans that start and end non- + * sequentially. All completed spans are stored. +*/ +TEST_F(TracezProcessor, MultipleSpansOuterSplitCumulative) { + for (const auto &name : span_names) span_vars.push_back(tracer->StartSpan(name)); + + // End last span + span_vars[4]->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 4)); // s0 s2 s1 s1 + EXPECT_TRUE(ContainsNames(span_names, completed, 4)); // s + EXPECT_EQ(running.size(), 4); + EXPECT_EQ(completed.size(), 1); + + // End first span + span_vars[0]->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running, 1, 4)); // s2 s1 s1 + EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1)); // s0 + EXPECT_TRUE(ContainsNames(span_names, completed, 4)); // s + EXPECT_EQ(running.size(), 3); + EXPECT_EQ(completed.size(), 2); + + // End remaining Spans + for (int i = 1; i < 4; i++) span_vars[i]->End(); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, completed)); // s0 s2 s1 s1 s + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 5); +} + + +/* + * Test if a single span moves from running to completed at expected times. + * Ensure correct behavior even when spans are discarded. Only new completed + * spans are stored. +*/ +TEST_F(TracezProcessor, OneSpanNewOnly) { + auto span = tracer->StartSpan(span_names[0]); + UpdateSpans(processor, completed, running, true); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 1, true)); + EXPECT_EQ(running.size(), 1); + EXPECT_EQ(completed.size(), 0); + + span->End(); + UpdateSpans(processor, completed, running, true); + + EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1, true)); + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 1); + + UpdateSpans(processor, completed, running, true); + + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 0); +} + + +/* + * Test if multiple spans move from running to completed at expected times, + * running/completed spans are split. Middle spans end first. Ensure correct + * behavior even when multiple spans are discarded, even when span starting and + * ending is non-sequential. Only new completed spans are stored. + */ +TEST_F(TracezProcessor, MultipleSpansMiddleSplitNewOnly) { + for (const auto &name : span_names) span_vars.push_back(tracer->StartSpan(name)); + UpdateSpans(processor, completed, running); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 5, true)); // s0 s2 s1 s1 s + EXPECT_EQ(running.size(), span_names.size()); + EXPECT_EQ(completed.size(), 0); + + // End 4th span + span_vars[3]->End(); + UpdateSpans(processor, completed, running, true); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 3)); // s0 s2 s1 + EXPECT_TRUE(ContainsNames(span_names, running, 4)); // + s + EXPECT_TRUE(ContainsNames(span_names, completed, 3, 4, true)); // s1 + EXPECT_EQ(running.size(), 4); + EXPECT_EQ(completed.size(), 1); + + // End 2nd and 3rd span + span_vars[1]->End(); + span_vars[2]->End(); + UpdateSpans(processor, completed, running, true); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 1)); // s0 + EXPECT_TRUE(ContainsNames(span_names, running, 4)); // + s + EXPECT_TRUE(ContainsNames(span_names, completed, 1, 3, true)); // s2 s1 + EXPECT_EQ(running.size(), 2); + EXPECT_EQ(completed.size(), 2); + + // End remaining Spans + span_vars[0]->End(); + span_vars[4]->End(); + UpdateSpans(processor, completed, running, true); + + EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1)); // s0 + EXPECT_TRUE(ContainsNames(span_names, completed, 4)); // s + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 2); + + UpdateSpans(processor, completed, running, true); + + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 0); +} + + +/* + * Test if multiple spans move from running to completed at expected times, + * running/completed spans are split. Ensure correct behavior even when + * multiple spans are discarded, even when span starting and ending is + * non-sequential. Only new completed spans are stored. +*/ +TEST_F(TracezProcessor, MultipleSpansOuterSplitNewOnly) { + for (const auto &name : span_names) span_vars.push_back(tracer->StartSpan(name)); + + // End last span + span_vars[4]->End(); + UpdateSpans(processor, completed, running, true); + + EXPECT_TRUE(ContainsNames(span_names, running, 0, 4, true)); // s0 s2 s1 s1 + EXPECT_TRUE(ContainsNames(span_names, completed, 4, 5, true)); // s + EXPECT_EQ(running.size(), 4); + EXPECT_EQ(completed.size(), 1); + + // End first span + span_vars[0]->End(); + UpdateSpans(processor, completed, running, true); + + EXPECT_TRUE(ContainsNames(span_names, running, 1, 4, true)); // s2 s1 s1 + EXPECT_TRUE(ContainsNames(span_names, completed, 0, 1, true)); // s0 + EXPECT_EQ(running.size(), 3); + EXPECT_EQ(completed.size(), 1); + + // End remaining middle spans + for (int i = 1; i < 4; i++) span_vars[i]->End(); + UpdateSpans(processor, completed, running, true); + + EXPECT_TRUE(ContainsNames(span_names, completed, 1, 4, true)); // s2 s1 s1 + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 3); + + UpdateSpans(processor, completed, running, true); + + EXPECT_EQ(running.size(), 0); + EXPECT_EQ(completed.size(), 0); +} + + +/* + * Test for ForceFlush and Shutdown code coverage, which do nothing. + */ +TEST_F(TracezProcessor, FlushShutdown) { + auto pre_running_sz = running.size(); + auto pre_completed_sz = completed.size(); + + processor->ForceFlush(); + processor->Shutdown(); + + UpdateSpans(processor, completed, running); + + EXPECT_EQ(pre_running_sz, running.size()); + EXPECT_EQ(pre_completed_sz, completed.size()); +} + + +/* + * Test for thread safety when many spans start at the same time. + */ +TEST_F(TracezProcessor, RunningThreadSafety) { + std::vector> spans1; + std::vector> spans2; + + std::thread start1(StartManySpans, std::ref(spans1), tracer, 500); + std::thread start2(StartManySpans, std::ref(spans2), tracer, 500); + + start1.join(); + start2.join(); +} + + +/* + * Test for thread safety when many spans end at the same time + */ +TEST_F(TracezProcessor, CompletedThreadSafety) { + std::vector> spans1; + std::vector> spans2; + StartManySpans(spans1, tracer, 500); + StartManySpans(spans2, tracer, 500); + + std::thread end1(EndAllSpans, std::ref(spans1)); + std::thread end2(EndAllSpans, std::ref(spans2)); + + end1.join(); + end2.join(); +} + + +/* + * Test for thread safety when many snapshots are grabbed at the same time. + */ +TEST_F(TracezProcessor, SnapshotThreadSafety) { + std::vector> spans; + + std::thread snap1(GetManySnapshots, std::ref(processor), 500); + std::thread snap2(GetManySnapshots, std::ref(processor), 500); + + snap1.join(); + snap2.join(); + + StartManySpans(spans, tracer, 500); + + std::thread snap3(GetManySnapshots, std::ref(processor), 500); + std::thread snap4(GetManySnapshots, std::ref(processor), 500); + + snap3.join(); + snap4.join(); +} + + +/* + * Test for thread safety when many spans start while others are ending. + */ +TEST_F(TracezProcessor, RunningCompletedThreadSafety) { + std::vector> spans1; + std::vector> spans2; + StartManySpans(spans1, tracer, 500); + + std::thread start(StartManySpans, std::ref(spans2), tracer, 500); + std::thread end(EndAllSpans, std::ref(spans1)); + + start.join(); + end.join(); +} + + +/* + * Test for thread safety when many span start while snapshots are being grabbed. + */ +TEST_F(TracezProcessor, RunningSnapshotThreadSafety) { + std::vector> spans; + + std::thread start(StartManySpans, std::ref(spans), tracer, 500); + std::thread snapshots(GetManySnapshots, std::ref(processor), 500); + + start.join(); + snapshots.join(); +} + + +/* + * Test for thread safety when many spans end while snapshots are being grabbed. + */ +TEST_F(TracezProcessor, SnapshotCompletedThreadSafety) { + std::vector> spans; + StartManySpans(spans, tracer, 500); + + std::thread snapshots(GetManySnapshots, std::ref(processor), 500); + std::thread end(EndAllSpans, std::ref(spans)); + + snapshots.join(); + end.join(); +} + + +/* + * Test for thread safety when many spans start and end while snapshots are being grabbed. + */ +TEST_F(TracezProcessor, RunningSnapshotCompletedThreadSafety) { + std::vector> spans1; + std::vector> spans2; + StartManySpans(spans1, tracer, 500); + + std::thread start(StartManySpans, std::ref(spans2), tracer, 500); + std::thread snapshots(GetManySnapshots, std::ref(processor), 500); + std::thread end(EndAllSpans, std::ref(spans1)); + + start.join(); + snapshots.join(); + end.join(); +} +