diff --git a/.bazelrc b/.bazelrc index 58140be3c9..e893a2e2fa 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,6 +1,9 @@ # bazel configurations for running tests under sanitizers. # Based on https://github.com/bazelment/trunk/blob/master/tools/bazel.rc +# Needed by gRPC to build on some platforms. +build --copt -DGRPC_BAZEL_BUILD + # --config=asan : Address Sanitizer. common:asan --copt -fsanitize=address common:asan --copt -DADDRESS_SANITIZER diff --git a/.bazelversion b/.bazelversion index 944880fa15..bea438e9ad 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -3.2.0 +3.3.1 diff --git a/.github/.codecov.yaml b/.github/.codecov.yaml new file mode 100644 index 0000000000..354ed22dd4 --- /dev/null +++ b/.github/.codecov.yaml @@ -0,0 +1,28 @@ +codecov: + require_ci_to_pass: yes + max_report_age: off + +coverage: + precision: 2 + round: down + range: "80...100" + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach,diff,flags,tree" + behavior: default + require_changes: no + +# Relative file path fixing. +# CI file paths must match Git file paths. +# This fix removes the "/home/runner/" prefix +# to coverage report file paths. +fixes: + - "/home/runner/::" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e872f965e0..9852bcdf5a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,4 +2,4 @@ # This file controls who is tagged for review for any given pull request. # For anything not explicitly taken by someone else: -* @g-easy @jmacd @maxgolov @reyang @rnburn @tigrannajaryan +* @open-telemetry/cpp-approvers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..a5d36c4a61 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,200 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + cmake_test: + name: CMake test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + - name: run tests + run: ./ci/do_ci.sh cmake.test + + cmake_test_cxx20: + name: CMake C++20 test + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_ci_environment.sh + sudo ./ci/setup_cmake.sh + - name: run tests + run: ./ci/do_ci.sh cmake.c++20.test + + plugin_test: + name: Plugin -> CMake + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + - name: run tests + run: ./ci/do_ci.sh cmake.test_example_plugin + + gcc_48_test: + name: Legacy Bazel + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_ci_environment.sh + sudo ./ci/install_bazelisk.sh + sudo ./ci/install_gcc48.sh + - name: run tests + run: ./ci/do_ci.sh bazel.legacy.test + + bazel_test: + name: Bazel + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + sudo ./ci/install_bazelisk.sh + - name: run tests + run: ./ci/do_ci.sh bazel.test + + bazel_noexcept: + name: Bazel noexcept + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + sudo ./ci/install_bazelisk.sh + - name: run tests + run: ./ci/do_ci.sh bazel.noexcept + + bazel_asan: + name: Bazel asan config + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + sudo ./ci/install_bazelisk.sh + - name: run tests + run: ./ci/do_ci.sh bazel.asan + + bazel_tsan: + name: Bazel tsan config + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + sudo ./ci/install_bazelisk.sh + - name: run tests + run: ./ci/do_ci.sh bazel.tsan + + benchmark: + name: Benchmark + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + sudo ./ci/install_bazelisk.sh + - name: run tests + run: | + env BENCHMARK_DIR=/benchmark + ./ci/do_ci.sh benchmark + - name: Upload benchmark results + uses: actions/upload-artifact@v2 + with: + name: benchmark_reports + path: /home/runner/benchmark + + format: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: sudo ./ci/install_format_tools.sh + - name: run tests + run: ./ci/do_ci.sh format + + osx_test: + name: Bazel on MacOS + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: run tests + run: ./ci/do_ci.sh bazel.test + + windows: + name: CMake -> exporter proto + runs-on: windows-2019 + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + ./ci/setup_windows_cmake.ps1 + ./ci/setup_windows_ci_environment.ps1 + ./ci/install_windows_protobuf.ps1 + - name: run cmake test + run: ./ci/do_ci.ps1 cmake.test + - name: run otprotocol test + run: ./ci/do_ci.ps1 cmake.exporter.otprotocol.test + + windows_bazel: + name: Bazel Windows + runs-on: windows-2019 + steps: + - uses: actions/checkout@v2 + - name: setup + run: ./ci/install_windows_bazelisk.ps1 + - name: run tests + run: ./ci/do_ci.ps1 bazel.build + + windows_plugin_test: + name: Plugin -> CMake Windows + runs-on: windows-2019 + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + ./ci/setup_windows_cmake.ps1 + ./ci/setup_windows_ci_environment.ps1 + - name: run tests + run: ./ci/do_ci.ps1 cmake.test_example_plugin + + code_coverage: + name: Code coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: setup + run: | + sudo ./ci/setup_cmake.sh + sudo ./ci/setup_ci_environment.sh + - name: run tests and generate report + run: ./ci/do_ci.sh code.coverage + - name: upload report + uses: codecov/codecov-action@v1 + with: + file: /home/runner/build/coverage.info diff --git a/CMakeLists.txt b/CMakeLists.txt index 264d58992d..9fbfc1c059 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,9 @@ cmake_policy(SET CMP0057 NEW) project(opentelemetry-cpp) -set(CMAKE_CXX_STANDARD 11) +if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 11) +endif() option(WITH_OTPROTOCOL "Whether to include the OpenTelemetry Protocol in the SDK" OFF) @@ -20,6 +22,13 @@ include(CTest) find_package(Threads) +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # Options for Visual C++ compiler: /Zc:__cplusplus - report an updated value + # for recent C++ language standards. Without this option MSVC returns the + # value of __cplusplus="199711L" + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:__cplusplus") +endif() + if(WITH_PROTOBUF) set(protobuf_MODULE_COMPATIBLE ON) find_package(Protobuf CONFIG NAMES protobuf) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3b18e248a..e101e058d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,40 @@ convention](https://google.github.io/styleguide/cppguide.html#Naming). Code is formatted automatically and enforced by CI. +### Build and Run Code Examples + +Note: these instructions apply to examples configured with Bazel, see +example-specific documentation for other build automation tools. + +Install the latest bazel version by following the steps listed +[here](https://docs.bazel.build/versions/master/install.html). + +Select an example of interest from the [examples folder](https://github.com/open-telemetry/opentelemetry-cpp/tree/master/examples). +Inside each example directory is a `BUILD` file containing instructions for +Bazel. Find the binary name of your example by inspecting the contents of this +`BUILD` file. + +Build the example from the root of the opentelemetry-cpp directory using Bazel. +Replace `` with the identifier found in the previous step: + +```sh +bazel build //examples/: +``` + +Run the resulting executable to see telemetry from the application as it calls +the instrumented library: + +```sh +bazel-bin/examples// +``` + +For instance, building and running the `simple` example can be done as follows: + +```sh +bazel build //examples/simple:example_simple +bazel-bin/examples/simple/example_simple +``` + ## Pull Requests ### How to Send Pull Requests @@ -30,23 +64,23 @@ requests (PRs). To create a new PR, fork the project in GitHub and clone the upstream repo: ```sh -$ git clone https://github.com/open-telemetry/opentelemetry-cpp.git +git clone https://github.com/open-telemetry/opentelemetry-cpp.git ``` Add your fork as a remote: ```sh -$ git remote add fork https://github.com/YOUR_GITHUB_USERNAME/opentelemetry-cpp.git +git remote add fork https://github.com/YOUR_GITHUB_USERNAME/opentelemetry-cpp.git ``` Check out a new branch, make modifications and push the branch to your fork: ```sh -$ git checkout -b feature +git checkout -b feature # edit files -$ tools/format.sh -$ git commit -$ git push fork feature +tools/format.sh +git commit +git push fork feature ``` Open a pull request against the main `opentelemetry-cpp` repo. @@ -57,11 +91,13 @@ To run tests locally, please read the [CI instructions](ci/README.md). * If the PR is not ready for review, please put `[WIP]` in the title, tag it as `work-in-progress`, or mark it as [`draft`](https://github.blog/2019-02-14-introducing-draft-pull-requests/). -* Make sure [CLA](https://identity.linuxfoundation.org/projects/cncf) is signed and CI is clear. +* Make sure [CLA](https://identity.linuxfoundation.org/projects/cncf) is + signed and CI is clear. ### How to Get PRs Merged A PR is considered to be **ready to merge** when: + * It has received two approvals from [Approvers](https://github.com/open-telemetry/community/blob/master/community-membership.md#approver) / [Maintainers](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer) (at different companies). @@ -72,3 +108,57 @@ A PR is considered to be **ready to merge** when: * Urgent fixes can take exceptions as long as it has been actively communicated. Any Approver / Maintainer can merge the PR once it is **ready to merge**. + +## Useful Resources + +Hi! If you’re looking at this document, these resources will provide you the +knowledge to get started as a newcomer to the OpenTelemetry project. They will +help you understand the OpenTelemetry Project, its components, and +specifically the C++ repository. + +### Reading Resources + +* Medium [article](https://medium.com/opentelemetry/how-to-start-contributing-to-opentelemetry-b23991ad91f4) + (October 2019) on how to start contributing to the OpenTelemetry project. +* Medium [article](https://medium.com/opentelemetry/opentelemetry-beyond-getting-started-5ac43cd0fe26) + (January 2020) describing the overarching goals and use cases for OpenTelemetry. + +### Relevant Documentation + +* [OpenTelemetry Specification](https://github.com/open-telemetry/opentelemetry-specification) + * The OpenTelemetry Specification describes the requirements and + expectations of for all OpenTelemetry implementations. + +* Read through the [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector) + GitHub repository. + * This repository has a lot of good information surrounding the + OpenTelemetry ecosystem. At the top of the **[readme](https://github.com/open-telemetry/opentelemetry-collector/blob/master/README.md)**, + there are multiple links that give newcomers a good idea of what the + project is about and how to get involved in it. +* Read through the OpenTelemetry Python documentation + * The [API](https://opentelemetry-python.readthedocs.io/en/stable/api/api.html) + and [SDK](https://opentelemetry-python.readthedocs.io/en/stable/sdk/sdk.html) + documentation provides a lot of information on what the classes and their + functions are used for. Since there is currently minimal documentation for + C++, use the Python repository’s extensive documentation to learn more + about how the API and SDK work. + +### Code Examples + +* Follow the [simple trace example](https://github.com/open-telemetry/opentelemetry-cpp/pull/92) + for an introduction to basic OpenTelemetry functionality in C++. Currently + the example can be found in [PR #94](https://github.com/open-telemetry/opentelemetry-cpp/pull/94). + +* Read through the [Java Quick-Start + Guide](https://github.com/open-telemetry/opentelemetry-java/blob/master/QUICKSTART.md). + This shows you how the classes and functions will interact in simple and + easy to digest examples. +* Take a look at this [Java SDK + example](https://github.com/open-telemetry/opentelemetry-java/tree/master/examples/sdk-usage). + This shows a good use case of the SDK using stdout exporter. +* Take a look at the [Java Jaeger + example](https://github.com/open-telemetry/opentelemetry-java/tree/master/examples/jaeger). + This provides a brief introduction to the Jaeger exporter, its interface, + and how to interact with the service. + +Please contribute! You’re welcome to add more information if you come across any helpful resources. diff --git a/README.md b/README.md index 5fcc76b553..4cc0df677b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -# OpenTelemetry C/C++ -[![Gitter chat][gitter-image]][gitter-url] +# OpenTelemetry C++ -[gitter-image]: https://badges.gitter.im/open-telemetry/opentelemetry-cpp.svg -[gitter-url]: https://gitter.im/open-telemetry/opentelemetry-cpp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +[![Gitter chat](https://badges.gitter.im/open-telemetry/opentelemetry-cpp.svg)](https://gitter.im/open-telemetry/opentelemetry-cpp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![codecov.io](https://codecov.io/gh/open-telemetry/opentelemetry-cpp/branch/master/graphs/badge.svg?)](https://codecov.io/gh/open-telemetry/opentelemetry-cpp/) +[![Build Status](https://action-badges.now.sh/open-telemetry/opentelemetry-cpp)](https://github.com/open-telemetry/opentelemetry-cpp/actions) +[![Release](https://img.shields.io/github/v/release/open-telemetry/opentelemetry-cpp?include_prereleases&style=)](https://github.com/open-telemetry/opentelemetry-cpp/releases/) -The C/C++ [OpenTelemetry](https://opentelemetry.io/) client. +The C++ [OpenTelemetry](https://opentelemetry.io/) client. ## Installation @@ -18,10 +19,42 @@ TBD See [CONTRIBUTING.md](CONTRIBUTING.md) +We meet weekly on Mondays at 3:00PM PT. Check the [OpenTelemetry community calendar](https://calendar.google.com/calendar/embed?src=google.com_b79e3e90j7bbsa2n2p5an5lf60%40group.calendar.google.com) for specific dates. + +Meetings take place via [Zoom video conference](https://zoom.us/j/8203130519). + +Meeting notes are available as a public [Google doc](https://docs.google.com/document/d/1i1E4-_y4uJ083lCutKGDhkpi3n4_e774SBLi9hPLocw/edit?usp=sharing). For edit access, get in touch on [Gitter](https://gitter.im/open-telemetry/opentelemetry-cpp). + +Approvers ([@open-telemetry/cpp-approvers](https://github.com/orgs/open-telemetry/teams/cpp-approvers)): + +- [Max Golovanov](https://github.com/maxgolov), Microsoft +- [Johannes Tax](https://github.com/pyohannes), New Relic +- [Ryan Burn](https://github.com/rnburn), Lightstep + +*Find more about the approver role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#approver).* + +Maintainers ([@open-telemetry/cpp-maintainers](https://github.com/orgs/open-telemetry/teams/cpp-maintainers)): + +- [Emil Mikulic](https://github.com/g-easy), Google +- [Reiley Yang](https://github.com/reyang), Microsoft + +*Find more about the maintainer role in [community repository](https://github.com/open-telemetry/community/blob/master/community-membership.md#maintainer).* + ## Release Schedule -OpenTelemetry C/C++ is under active development. +OpenTelemetry C++ is under active development. The library is not yet _generally available_, and releases aren't guaranteed to -conform to a specific version of the specification. Future releases will not -attempt to maintain backwards compatibility with current releases. +conform to a specific version of the specification. Future releases will not +attempt to maintain backwards compatibility with previous releases. Each alpha +and beta release includes significant changes to the API and SDK packages, +making them incompatible with each other. + +See the [release +notes](https://github.com/open-telemetry/opentelemetry-cpp/releases) +for existing releases. + +See the [project +milestones](https://github.com/open-telemetry/opentelemetry-cpp/milestones) +for details on upcoming releases. The dates and features described in issues +and milestones are estimates, and subject to change. diff --git a/WORKSPACE b/WORKSPACE index 1dd2ea37fc..84ab876bc7 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -16,6 +16,30 @@ workspace(name = "io_opentelemetry_cpp") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +# Load gRPC dependency +# Note that this dependency needs to be loaded first due to +# https://github.com/bazelbuild/bazel/issues/6664 +http_archive( + name = "com_github_grpc_grpc", + strip_prefix = "grpc-1.28.0", + urls = [ + "https://github.com/grpc/grpc/archive/v1.28.0.tar.gz", + ], +) + +load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") + +grpc_deps() + +# Load extra gRPC dependencies due to https://github.com/grpc/grpc/issues/20511 +load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") + +grpc_extra_deps() + +load("@upb//bazel:repository_defs.bzl", "bazel_version_repository") + +bazel_version_repository(name = "upb_bazel_version") + # Uses older protobuf version because of # https://github.com/protocolbuffers/protobuf/issues/7179 http_archive( diff --git a/api/include/opentelemetry/context/TBD b/api/include/opentelemetry/context/TBD deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/api/include/opentelemetry/context/context.h b/api/include/opentelemetry/context/context.h new file mode 100644 index 0000000000..35b7920c09 --- /dev/null +++ b/api/include/opentelemetry/context/context.h @@ -0,0 +1,92 @@ +#pragma once + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/context/context_value.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/trace/key_value_iterable_view.h" + +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{ + +// The context class provides a context identifier. +// This is a dummy class that is meant to be overridden, +// the methods return default values. +class Context +{ + +public: + Context() = default; + + // Contructor, creates a context object from a map of keys + // and identifiers. + template ::value> * = nullptr> + Context(const T &keys_and_values) + { + trace::KeyValueIterableView iterable{keys_and_values}; + iterable.ForEachKeyValue([&](nostd::string_view key, context::ContextValue value) noexcept { + context_map_[std::string(key)] = value; + return true; + }); + } + + // Accepts a key and a value and then returns a new context that + // contains both the original pairs and the new pair. + template + Context SetValue(nostd::string_view key, T &value) noexcept + { + std::map context_map_copy; + trace::KeyValueIterableView> context_map_iterable{ + context_map_}; + + context_map_iterable.ForEachKeyValue([&](nostd::string_view key, + context::ContextValue value) noexcept { + context_map_copy[std::string(key)] = value; + return true; + }); + + context_map_copy[std::string(key)] = value; + + return Context(context_map_copy); + } + + // Accepts a new iterable and then returns a new context that + // contains both the original pairs and the new pair. + template ::value> * = nullptr> + Context SetValues(T &keys_and_values) noexcept + { + std::map context_map_copy; + trace::KeyValueIterableView> context_map_iterable{ + context_map_}; + + context_map_iterable.ForEachKeyValue([&](nostd::string_view key, + context::ContextValue value) noexcept { + context_map_copy[std::string(key)] = value; + return true; + }); + + trace::KeyValueIterableView iterable{keys_and_values}; + + iterable.ForEachKeyValue([&](nostd::string_view key, context::ContextValue value) noexcept { + context_map_copy[std::string(key)] = value; + return true; + }); + + return Context(context_map_copy); + } + + // Returns the value associated with the passed in key. + context::ContextValue GetValue(nostd::string_view key) { return context_map_[std::string(key)]; } + + // Copy Constructors. + Context(const Context &other) = default; + Context &operator=(const Context &other) = default; + +private: + std::map context_map_; +}; +} // namespace context +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/context/context_value.h b/api/include/opentelemetry/context/context_value.h new file mode 100644 index 0000000000..bccdac46d1 --- /dev/null +++ b/api/include/opentelemetry/context/context_value.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/nostd/string_view.h" +#include "opentelemetry/nostd/variant.h" +#include "opentelemetry/version.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace context +{ +using ContextValue = nostd::variant, + nostd::span, + nostd::span, + nostd::span, + nostd::span, + nostd::span, + nostd::span>; +} // namespace context +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/core/timestamp.h b/api/include/opentelemetry/core/timestamp.h index 5fea58d535..c76edd76f4 100644 --- a/api/include/opentelemetry/core/timestamp.h +++ b/api/include/opentelemetry/core/timestamp.h @@ -42,6 +42,16 @@ class SystemTimestamp return std::chrono::nanoseconds{nanos_since_epoch_}; } + bool operator==(const SystemTimestamp &other) const noexcept + { + return nanos_since_epoch_ == other.nanos_since_epoch_; + } + + bool operator!=(const SystemTimestamp &other) const noexcept + { + return nanos_since_epoch_ != other.nanos_since_epoch_; + } + private: int64_t nanos_since_epoch_; }; @@ -79,6 +89,16 @@ class SteadyTimestamp return std::chrono::nanoseconds{nanos_since_epoch_}; } + bool operator==(const SteadyTimestamp &other) const noexcept + { + return nanos_since_epoch_ == other.nanos_since_epoch_; + } + + bool operator!=(const SteadyTimestamp &other) const noexcept + { + return nanos_since_epoch_ != other.nanos_since_epoch_; + } + private: int64_t nanos_since_epoch_; }; diff --git a/api/include/opentelemetry/nostd/function_ref.h b/api/include/opentelemetry/nostd/function_ref.h index 33c94d00ab..de61c7040a 100644 --- a/api/include/opentelemetry/nostd/function_ref.h +++ b/api/include/opentelemetry/nostd/function_ref.h @@ -63,7 +63,13 @@ class function_ref typename std::enable_if::type>::value, int>::type = 0, typename std::enable_if< +#if (__cplusplus >= 201703L) + // std::result_of deprecated in C++17, removed in C++20 + std::is_convertible::type, R>::value, +#else + // std::result_of since C++11 std::is_convertible::type, R>::value, +#endif int>::type = 0> function_ref(F &&f) { diff --git a/api/include/opentelemetry/nostd/string_view.h b/api/include/opentelemetry/nostd/string_view.h index 02f6b326a7..e947d23259 100644 --- a/api/include/opentelemetry/nostd/string_view.h +++ b/api/include/opentelemetry/nostd/string_view.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -11,6 +12,9 @@ OPENTELEMETRY_BEGIN_NAMESPACE namespace nostd { + +using Traits = std::char_traits; + /** * Back port of std::string_view to work with pre-cpp-17 compilers. * @@ -20,7 +24,9 @@ namespace nostd class string_view { public: - static constexpr std::size_t npos = static_cast(-1); + typedef std::size_t size_type; + + static constexpr size_type npos = static_cast(-1); string_view() noexcept : length_(0), data_(nullptr) {} @@ -30,7 +36,7 @@ class string_view : length_(str.length()), data_(str.c_str()) {} - string_view(const char *str, size_t len) noexcept : length_(len), data_(str) {} + string_view(const char *str, size_type len) noexcept : length_(len), data_(str) {} explicit operator std::string() const { return {data_, length_}; } @@ -38,17 +44,17 @@ class string_view bool empty() const noexcept { return length_ == 0; } - size_t length() const noexcept { return length_; } + size_type length() const noexcept { return length_; } - size_t size() const noexcept { return length_; } + size_type size() const noexcept { return length_; } const char *begin() const noexcept { return data(); } const char *end() const noexcept { return data() + length(); } - const char &operator[](std::size_t i) { return *(data() + i); } + const char &operator[](size_type i) { return *(data() + i); } - string_view substr(std::size_t pos, std::size_t n = npos) const + string_view substr(size_type pos, size_type n = npos) const { if (pos > length_) { @@ -62,11 +68,55 @@ class string_view return string_view(data_ + pos, n); } + int compare(string_view v) const noexcept + { + size_type len = std::min(size(), v.size()); + int result = Traits::compare(data(), v.data(), len); + if (result == 0) + result = size() == v.size() ? 0 : (size() < v.size() ? -1 : 1); + return result; + }; + + int compare(size_type pos1, size_type count1, string_view v) const + { + return substr(pos1, count1).compare(v); + }; + + int compare(size_type pos1, size_type count1, string_view v, size_type pos2, size_type count2) const + { + return substr(pos1, count1).compare(v.substr(pos2, count2)); + }; + + int compare(const char *s) const + { + return compare(string_view(s)); + }; + + int compare(size_type pos1, size_type count1, const char *s) const + { + return substr(pos1, count1).compare(string_view(s)); + }; + + int compare(size_type pos1, size_type count1, const char *s, size_type count2) const + { + return substr(pos1, count1).compare(string_view(s, count2)); + }; + + bool operator<(const string_view v) const noexcept + { + return compare(v) < 0; + } + + bool operator>(const string_view v) const noexcept + { + return compare(v) > 0; + } + private: // Note: uses the same binary layout as libstdc++'s std::string_view // See // https://github.com/gcc-mirror/gcc/blob/e0c554e4da7310df83bb1dcc7b8e6c4c9c5a2a4f/libstdc%2B%2B-v3/include/std/string_view#L466-L467 - size_t length_; + size_type length_; const char *data_; }; diff --git a/api/include/opentelemetry/plugin/factory.h b/api/include/opentelemetry/plugin/factory.h index dd849335e7..abec73867a 100644 --- a/api/include/opentelemetry/plugin/factory.h +++ b/api/include/opentelemetry/plugin/factory.h @@ -36,8 +36,9 @@ class Factory final * @param error_message on failure this will contain an error message. * @return a Tracer on success or nullptr on failure. */ - std::shared_ptr MakeTracer(nostd::string_view tracer_config, - std::string &error_message) const noexcept + std::shared_ptr MakeTracer(nostd::string_view tracer_config, + std::string &error_message) const + noexcept { nostd::unique_ptr plugin_error_message; auto tracer_handle = factory_impl_->MakeTracerHandle(tracer_config, plugin_error_message); @@ -46,8 +47,8 @@ class Factory final detail::CopyErrorMessage(plugin_error_message.get(), error_message); return nullptr; } - return std::shared_ptr{new (std::nothrow) - Tracer{library_handle_, std::move(tracer_handle)}}; + return std::shared_ptr{ + new (std::nothrow) Tracer{library_handle_, std::move(tracer_handle)}}; } private: diff --git a/api/include/opentelemetry/plugin/tracer.h b/api/include/opentelemetry/plugin/tracer.h index b2199a07bf..e2f8bc79f8 100644 --- a/api/include/opentelemetry/plugin/tracer.h +++ b/api/include/opentelemetry/plugin/tracer.h @@ -18,6 +18,11 @@ class Span final : public trace::Span {} // trace::Span + void SetAttribute(nostd::string_view name, const common::AttributeValue &value) noexcept override + { + span_->SetAttribute(name, value); + } + void AddEvent(nostd::string_view name) noexcept override { span_->AddEvent(name); } void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override @@ -39,7 +44,7 @@ class Span final : public trace::Span void UpdateName(nostd::string_view name) noexcept override { span_->UpdateName(name); } - void End() noexcept override { span_->End(); } + void End(const trace::EndSpanOptions &options = {}) noexcept override { span_->End(options); } bool IsRecording() const noexcept override { return span_->IsRecording(); } @@ -61,9 +66,10 @@ class Tracer final : public trace::Tracer, public std::enable_shared_from_this StartSpan( nostd::string_view name, + const trace::KeyValueIterable &attributes, const trace::StartSpanOptions &options = {}) noexcept override { - auto span = tracer_handle_->tracer().StartSpan(name, options); + auto span = tracer_handle_->tracer().StartSpan(name, attributes, options); if (span == nullptr) { return nullptr; diff --git a/api/include/opentelemetry/trace/noop.h b/api/include/opentelemetry/trace/noop.h index 0c3ef15c69..2d353489d0 100644 --- a/api/include/opentelemetry/trace/noop.h +++ b/api/include/opentelemetry/trace/noop.h @@ -24,6 +24,10 @@ class NoopSpan final : public Span public: explicit NoopSpan(const std::shared_ptr &tracer) noexcept : tracer_{tracer} {} + void SetAttribute(nostd::string_view /*key*/, + const common::AttributeValue & /*value*/) noexcept override + {} + void AddEvent(nostd::string_view /*name*/) noexcept override {} void AddEvent(nostd::string_view /*name*/, core::SystemTimestamp /*timestamp*/) noexcept override @@ -38,7 +42,7 @@ class NoopSpan final : public Span void UpdateName(nostd::string_view /*name*/) noexcept override {} - void End() noexcept override {} + void End(const EndSpanOptions & /*options*/) noexcept override {} bool IsRecording() const noexcept override { return false; } @@ -56,6 +60,7 @@ class NoopTracer final : public Tracer, public std::enable_shared_from_this StartSpan(nostd::string_view /*name*/, + const KeyValueIterable & /*attributes*/, const StartSpanOptions & /*options*/) noexcept override { return nostd::unique_ptr{new (std::nothrow) NoopSpan{this->shared_from_this()}}; diff --git a/api/include/opentelemetry/trace/span.h b/api/include/opentelemetry/trace/span.h index 7d323b7207..1f8ae415a9 100644 --- a/api/include/opentelemetry/trace/span.h +++ b/api/include/opentelemetry/trace/span.h @@ -2,6 +2,7 @@ #include +#include "opentelemetry/common/attribute_value.h" #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/nostd/string_view.h" @@ -40,9 +41,17 @@ struct StartSpanOptions // Span(Context?) parent; // SpanContext remote_parent; // Links - // Attributes SpanKind kind = SpanKind::kInternal; }; +/** + * StartEndOptions provides options to set properties of a Span when it is + * ended. + */ +struct EndSpanOptions +{ + // Optionally sets the end time of a Span. + core::SteadyTimestamp end_steady_time; +}; class Tracer; @@ -65,13 +74,10 @@ class Span Span &operator=(const Span &) = delete; Span &operator=(Span &&) = delete; - // TODO // Sets an attribute on the Span. If the Span previously contained a mapping for // the key, the old value is replaced. - // - // If an empty string is used as the value, the attribute will be silently - // dropped. Note: this behavior could change in the future. - // virtual void SetAttribute(nostd::string_view key, AttributeValue&& value) = 0; + virtual void SetAttribute(nostd::string_view key, + const common::AttributeValue &value) noexcept = 0; // Adds an event to the Span. virtual void AddEvent(nostd::string_view name) noexcept = 0; @@ -130,12 +136,14 @@ class Span // during creation. virtual void UpdateName(nostd::string_view name) noexcept = 0; - // Mark the end of the Span. Only the timing of the first End call for a given Span will - // be recorded, and implementations are free to ignore all further calls. - virtual void End() noexcept = 0; - - // TODO - // virtual void End(EndSpanOptions&& opts) noexcept = 0; + /** + * Mark the end of the Span. + * Only the timing of the first End call for a given Span will be recorded, + * and implementations are free to ignore all further calls. + * @param options can be used to manually define span properties like the end + * timestamp + */ + virtual void End(const EndSpanOptions &options = {}) noexcept = 0; // TODO // SpanContext context() const noexcept = 0; diff --git a/api/include/opentelemetry/trace/span_context.h b/api/include/opentelemetry/trace/span_context.h new file mode 100644 index 0000000000..fc04ce17e6 --- /dev/null +++ b/api/include/opentelemetry/trace/span_context.h @@ -0,0 +1,59 @@ +// 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. + +#pragma once + +#include "opentelemetry/nostd/unique_ptr.h" +#include "opentelemetry/trace/span_id.h" +#include "opentelemetry/trace/trace_flags.h" +#include "opentelemetry/trace/trace_id.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace trace +{ +namespace trace_api = opentelemetry::trace; + +/* SpanContext contains the state that must propagate to child Spans and across + * process boundaries. It contains the identifiers TraceId and SpanId, + * TraceFlags, TraceState, and whether it has a remote parent. + * + * TODO: This is currently a placeholder class and requires revisiting + */ +class SpanContext final +{ +public: + /* A temporary constructor for an invalid SpanContext. + * @param sampled_flag a required parameter specifying if child spans should be + * sampled + * @param has_remote_parent a required parameter specifying if this context has + * a remote parent + */ + SpanContext(bool sampled_flag, bool has_remote_parent) : + trace_flags_(trace_api::TraceFlags((uint8_t) sampled_flag)), remote_parent_(has_remote_parent) {}; + + // @returns the trace_flags associated with this span_context + const trace_api::TraceFlags &trace_flags() const noexcept { return trace_flags_; } + + // @returns whether this context has the sampled flag set or not + bool IsSampled() const noexcept { return trace_flags_.IsSampled(); } + + // @returns whether this context has a remote parent or not + bool HasRemoteParent() const noexcept { return remote_parent_; } + +private: + const trace_api::TraceFlags trace_flags_; + const bool remote_parent_ = false; +}; +} // namespace trace +OPENTELEMETRY_END_NAMESPACE diff --git a/api/include/opentelemetry/trace/tracer.h b/api/include/opentelemetry/trace/tracer.h index 29aa288123..825ff39fb1 100644 --- a/api/include/opentelemetry/trace/tracer.h +++ b/api/include/opentelemetry/trace/tracer.h @@ -22,10 +22,41 @@ class Tracer virtual ~Tracer() = default; /** * Starts a span. + * + * Optionally sets attributes at Span creation from the given key/value pairs. + * + * Attributes will be processed in order, previous attributes with the same + * key will be overwritten. */ virtual nostd::unique_ptr StartSpan(nostd::string_view name, + const KeyValueIterable &attributes, const StartSpanOptions &options = {}) noexcept = 0; + nostd::unique_ptr StartSpan(nostd::string_view name, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan(name, {}, options); + } + + template ::value> * = nullptr> + nostd::unique_ptr StartSpan(nostd::string_view name, + const T &attributes, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan(name, KeyValueIterableView(attributes), options); + } + + nostd::unique_ptr StartSpan( + nostd::string_view name, + std::initializer_list> attributes, + const StartSpanOptions &options = {}) noexcept + { + return this->StartSpan(name, + nostd::span>{ + attributes.begin(), attributes.end()}, + options); + } + /** * Force any buffered spans to flush. * @param timeout to complete the flush diff --git a/api/test/context/BUILD b/api/test/context/BUILD new file mode 100644 index 0000000000..26728542b8 --- /dev/null +++ b/api/test/context/BUILD @@ -0,0 +1,10 @@ +cc_test( + name = "context_test", + srcs = [ + "context_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/api/test/context/CMakeLists.txt b/api/test/context/CMakeLists.txt new file mode 100644 index 0000000000..38d48ef0e8 --- /dev/null +++ b/api/test/context/CMakeLists.txt @@ -0,0 +1,8 @@ +include(GoogleTest) + +foreach(testname context_test) + add_executable(${testname} "${testname}.cc") + target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) + gtest_add_tests(TARGET ${testname} TEST_PREFIX context. TEST_LIST ${testname}) +endforeach() diff --git a/api/test/context/context_test.cc b/api/test/context/context_test.cc new file mode 100644 index 0000000000..ee366d1a1a --- /dev/null +++ b/api/test/context/context_test.cc @@ -0,0 +1,104 @@ +#include "opentelemetry/context/context.h" + +#include + +#include + +using namespace opentelemetry; + +// Tests that the context constructor accepts an std::map. +TEST(ContextTest, ContextIterableAcceptsMap) +{ + std::map map_test = {{"test_key", "123"}}; + context::Context context_test = context::Context(map_test); +} + +// Tests that the GetValue method returns the expected value. +TEST(ContextTest, ContextGetValueReturnsExpectedValue) +{ + std::map map_test = {{"test_key", "123"}, {"foo_key", "456"}}; + + context::Context context_test = context::Context(map_test); + EXPECT_EQ(nostd::get(context_test.GetValue("test_key")), "123"); + EXPECT_EQ(nostd::get(context_test.GetValue("foo_key")), "456"); +} + +// Tests that the SetValues method accepts an std::map. +TEST(ContextTest, ContextSetValuesAcceptsMap) +{ + std::map map_test = {{"test_key", "123"}}; + std::map map_test_write = {{"foo_key", "456"}}; + context::Context context_test = context::Context(map_test); + context::Context foo_test = context_test.SetValues(map_test_write); + EXPECT_EQ(nostd::get(foo_test.GetValue("test_key")), "123"); + EXPECT_EQ(nostd::get(foo_test.GetValue("foo_key")), "456"); +} + +// Tests that the SetValues method accepts a nostd::string_view and +// context::ContextValue. +TEST(ContextTest, ContextSetValuesAcceptsStringViewContextValue) +{ + nostd::string_view string_view_test = "string_view"; + context::ContextValue context_value_test = "123"; + context::Context context_test = context::Context(); + context::Context context_foo = context_test.SetValue(string_view_test, context_value_test); + EXPECT_EQ(nostd::get(context_foo.GetValue(string_view_test)), "123"); +} + +// Tests that the original context does not change when a value is +// written to it. +TEST(ContextTest, ContextImmutability) +{ + std::map map_test = {{"test_key", "123"}}; + context::Context context_test = context::Context(map_test); + + context::Context context_foo = context_test.SetValue("foo_key", "456"); +#if __EXCEPTIONS + EXPECT_THROW(nostd::get(context_test.GetValue("foo_key")), + nostd::bad_variant_access); +#else + EXPECT_DEATH({ nostd::get(context_test.GetValue("foo_key")); }, ""); +#endif +} + +// Tests that writing the same to a context overwrites the original value. +TEST(ContextTest, ContextKeyOverwrite) +{ + std::map map_test = {{"test_key", "123"}}; + context::Context context_test = context::Context(map_test); + context::Context context_foo = context_test.SetValue("test_key", "456"); + + EXPECT_EQ(nostd::get(context_foo.GetValue("test_key")), "456"); +} + +// Tests that the new Context Objects inherits the keys and values +// of the original context object. +TEST(ContextTest, ContextInheritance) +{ + + using M = std::map; + context::Context test_context = context::Context(); + + M m1 = {{"test_key", "123"}, {"foo_key", "456"}}; + M m2 = {{"other_key", "789"}}; + + context::Context foo_context = test_context.SetValues(m1); + context::Context other_context = foo_context.SetValues(m2); + + EXPECT_EQ(nostd::get(other_context.GetValue("test_key")), "123"); + EXPECT_EQ(nostd::get(other_context.GetValue("foo_key")), "456"); +} + +// Tests that copying a context copies the key value pairs as expected. +TEST(ContextTest, ContextCopyOperator) +{ + std::map test_map = { + {"test_key", "123"}, {"foo_key", "456"}, {"other_key", "789"}}; + + context::Context test_context = context::Context(test_map); + context::Context copied_context = test_context; + + EXPECT_EQ(nostd::get(copied_context.GetValue("test_key")), "123"); + EXPECT_EQ(nostd::get(copied_context.GetValue("foo_key")), "456"); + EXPECT_EQ(nostd::get(copied_context.GetValue("other_key")), "789"); +} diff --git a/api/test/core/timestamp_test.cc b/api/test/core/timestamp_test.cc index 140092942a..7ea4484321 100644 --- a/api/test/core/timestamp_test.cc +++ b/api/test/core/timestamp_test.cc @@ -24,6 +24,19 @@ TEST(SystemTimestampTest, Construction) t2.time_since_epoch()); } +TEST(SystemTimestampTest, Comparison) +{ + SystemTimestamp t1; + SystemTimestamp t2; + SystemTimestamp t3{std::chrono::nanoseconds{2}}; + + EXPECT_EQ(t1, t1); + EXPECT_EQ(t1, t2); + EXPECT_EQ(t2, t1); + EXPECT_NE(t1, t3); + EXPECT_NE(t3, t1); +} + TEST(SteadyTimestampTest, Construction) { auto now_steady = std::chrono::steady_clock::now(); @@ -36,3 +49,16 @@ TEST(SteadyTimestampTest, Construction) EXPECT_EQ(std::chrono::duration_cast(now_steady.time_since_epoch()), t2.time_since_epoch()); } + +TEST(SteadyTimestampTest, Comparison) +{ + SteadyTimestamp t1; + SteadyTimestamp t2; + SteadyTimestamp t3{std::chrono::nanoseconds{2}}; + + EXPECT_EQ(t1, t1); + EXPECT_EQ(t1, t2); + EXPECT_EQ(t2, t1); + EXPECT_NE(t1, t3); + EXPECT_NE(t3, t1); +} diff --git a/api/test/nostd/string_view_test.cc b/api/test/nostd/string_view_test.cc index 17c52bf6cb..074d3bee49 100644 --- a/api/test/nostd/string_view_test.cc +++ b/api/test/nostd/string_view_test.cc @@ -2,6 +2,8 @@ #include +#include "map" + using opentelemetry::nostd::string_view; TEST(StringViewTest, DefaultConstruction) @@ -72,3 +74,31 @@ TEST(StringViewTest, SubstrOutOfRange) EXPECT_DEATH({ s.substr(10); }, ""); #endif } + +TEST(StringViewTest, Compare) +{ + string_view s1 = "aaa"; + string_view s2 = "bbb"; + string_view s3 = "aaa"; + + // Equals + EXPECT_EQ(s1, s3); + EXPECT_EQ(s1, s1); + + // Less then + EXPECT_LT(s1, s2); + + // Greater then + EXPECT_GT(s2, s1); +} + +TEST(StringViewTest, MapKeyOrdering) +{ + std::map m = {{"bbb", 2}, {"aaa", 1}, {"ccc", 3}}; + size_t i = 1; + for (const auto &kv : m) + { + EXPECT_EQ(kv.second, i); + i++; + } +} diff --git a/api/test/trace/BUILD b/api/test/trace/BUILD index ed3d584712..b3f194baa0 100644 --- a/api/test/trace/BUILD +++ b/api/test/trace/BUILD @@ -71,3 +71,14 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "span_context_test", + srcs = [ + "span_context_test.cc", + ], + deps = [ + "//api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/api/test/trace/CMakeLists.txt b/api/test/trace/CMakeLists.txt index f722ae4557..3439146f7a 100644 --- a/api/test/trace/CMakeLists.txt +++ b/api/test/trace/CMakeLists.txt @@ -1,5 +1,5 @@ foreach(testname key_value_iterable_view_test noop_test provider_test - span_id_test trace_id_test trace_flags_test) + span_id_test trace_id_test trace_flags_test span_context_test) add_executable(${testname} "${testname}.cc") target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) diff --git a/api/test/trace/noop_test.cc b/api/test/trace/noop_test.cc index 6dad65323b..945a333f33 100644 --- a/api/test/trace/noop_test.cc +++ b/api/test/trace/noop_test.cc @@ -25,4 +25,6 @@ TEST(NoopTest, UseNoopTracers) std::vector>> attributes3; s1->AddEvent("abc", attributes3); + + s1->SetAttribute("abc", 4); } diff --git a/api/test/trace/span_context_test.cc b/api/test/trace/span_context_test.cc new file mode 100644 index 0000000000..661bba00bc --- /dev/null +++ b/api/test/trace/span_context_test.cc @@ -0,0 +1,38 @@ +#include "opentelemetry/trace/span_context.h" + +#include + +using opentelemetry::trace::SpanContext; + +TEST(SpanContextTest, IsSampled) +{ + SpanContext s1(true, true); + + ASSERT_EQ(s1.IsSampled(), true); + + SpanContext s2(false, true); + + ASSERT_EQ(s2.IsSampled(), false); +} + +TEST(SpanContextTest, HasRemoteParent) +{ + SpanContext s1(true, true); + + ASSERT_EQ(s1.HasRemoteParent(), true); + + SpanContext s2(true, false); + + ASSERT_EQ(s2.HasRemoteParent(), false); +} + +TEST(SpanContextTest, TraceFlags) +{ + SpanContext s1(true, true); + + ASSERT_EQ(s1.trace_flags().flags(), 1); + + SpanContext s2(false, true); + + ASSERT_EQ(s2.trace_flags().flags(), 0); +} diff --git a/bazel/opentelemetry_proto.BUILD b/bazel/opentelemetry_proto.BUILD index ddb9915bf2..9c91edc2d0 100644 --- a/bazel/opentelemetry_proto.BUILD +++ b/bazel/opentelemetry_proto.BUILD @@ -15,6 +15,7 @@ package(default_visibility = ["//visibility:public"]) load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_github_grpc_grpc//bazel:cc_grpc_library.bzl", "cc_grpc_library") proto_library( name = "common_proto", @@ -58,3 +59,26 @@ cc_proto_library( name = "trace_proto_cc", deps = [":trace_proto"], ) + +proto_library( + name = "trace_service_proto", + srcs = [ + "opentelemetry/proto/collector/trace/v1/trace_service.proto", + ], + deps = [ + ":trace_proto", + ], +) + +cc_proto_library( + name = "trace_service_proto_cc", + deps = [":trace_service_proto"], +) + +cc_grpc_library( + name = "trace_service_grpc_cc", + srcs = [":trace_service_proto"], + grpc_only = True, + deps = [":trace_service_proto_cc"], + generate_mocks = True, +) diff --git a/ci/README.md b/ci/README.md index f755d3f54a..6695c8b134 100644 --- a/ci/README.md +++ b/ci/README.md @@ -3,6 +3,7 @@ CI tests can be run on docker by invoking the script `./ci/run_docker.sh ./ci/do_ci.sh ` where the targets are: * `cmake.test`: build cmake targets and run tests. +* `cmake.c++20.test`: build cmake targets with the C++20 standard and run tests. * `cmake.test_example_plugin`: build and test an example OpenTelemetry plugin. * `cmake.exporter.otprotocol.test`: build and test the otprotocol exporter * `bazel.test`: build bazel targets and run tests. @@ -12,6 +13,7 @@ CI tests can be run on docker by invoking the script `./ci/run_docker.sh ./ci/do * `bazel.tsan`: build bazel targets and run tests with ThreadSanitizer. * `benchmark`: run all benchmarks. * `format`: use `tools/format.sh` to enforce text formatting. +* `code.coverage`: build cmake targets and run tests. Then upload coverage report to [codecov.io](https://codecov.io/). Additionally, `./ci/run_docker.sh` can be invoked with no arguments to get a docker shell where tests can be run manually. diff --git a/ci/do_ci.ps1 b/ci/do_ci.ps1 index 1056225ea2..367d76e080 100644 --- a/ci/do_ci.ps1 +++ b/ci/do_ci.ps1 @@ -22,7 +22,7 @@ $VCPKG_DIR="$SRC_DIR\vcpkg" switch ($action) { "bazel.build" { - bazel build $BAZEL_OPTIONS -- //... -//api/test/... -//sdk/test/... + bazel build $BAZEL_OPTIONS -- //... //api/test/... //sdk/test/... $exit = $LASTEXITCODE if ($exit -ne 0) { exit $exit diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 6b3205843b..b6d23f763f 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -20,6 +20,16 @@ if [[ "$1" == "cmake.test" ]]; then make make test exit 0 +elif [[ "$1" == "cmake.c++20.test" ]]; then + cd "${BUILD_DIR}" + rm -rf * + cmake -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_FLAGS="-Werror" \ + -DCMAKE_CXX_STANDARD=20 \ + "${SRC_DIR}" + make + make test + exit 0 elif [[ "$1" == "cmake.exporter.otprotocol.test" ]]; then cd "${BUILD_DIR}" rm -rf * @@ -97,6 +107,8 @@ elif [[ "$1" == "benchmark" ]]; then exit 0 elif [[ "$1" == "format" ]]; then tools/format.sh + # normalize file endings according to .gitattributes + git add --renormalize . CHANGED="$(git ls-files --modified)" if [[ ! -z "$CHANGED" ]]; then echo "The following files have changes:" @@ -104,6 +116,16 @@ elif [[ "$1" == "format" ]]; then exit 1 fi exit 0 +elif [[ "$1" == "code.coverage" ]]; then + cd "${BUILD_DIR}" + rm -rf * + cmake -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_CXX_FLAGS="-Werror --coverage" \ + "${SRC_DIR}" + make + make test + lcov --directory $PWD --capture --output-file coverage.info + exit 0 fi echo "Invalid do_ci.sh target, see ci/README.md for valid targets." diff --git a/ci/setup_ci_environment.sh b/ci/setup_ci_environment.sh index 50c7b373e9..9d7df6a1d5 100755 --- a/ci/setup_ci_environment.sh +++ b/ci/setup_ci_environment.sh @@ -7,3 +7,4 @@ apt-get install --no-install-recommends --no-install-suggests -y \ ca-certificates \ wget \ git +apt-get install -y lcov diff --git a/ci/setup_cmake.sh b/ci/setup_cmake.sh index 1e22f39f81..0ab9be37fd 100755 --- a/ci/setup_cmake.sh +++ b/ci/setup_cmake.sh @@ -12,5 +12,5 @@ apt-get install --no-install-recommends --no-install-suggests -y \ pushd /usr/src/gtest cmake CMakeLists.txt make -cp *.a /usr/lib +cp *.a /usr/lib || cp lib/*.a /usr/lib popd diff --git a/ci/setup_windows_ci_environment.ps1 b/ci/setup_windows_ci_environment.ps1 index a1b1b89d9c..a2134646ea 100755 --- a/ci/setup_windows_ci_environment.ps1 +++ b/ci/setup_windows_ci_environment.ps1 @@ -2,9 +2,10 @@ $ErrorActionPreference = "Stop" trap { $host.SetShouldExit(1) } git clone https://github.com/Microsoft/vcpkg.git -cd vcpkg +Push-Location -Path vcpkg $VCPKG_DIR=(Get-Item -Path ".\").FullName ./bootstrap-vcpkg.bat ./vcpkg integrate install ./vcpkg install benchmark:x64-windows ./vcpkg install gtest:x64-windows +Pop-Location diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7d5453f66d..dcb04ccf4e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(plugin) +add_subdirectory(simple) diff --git a/examples/plugin/plugin/tracer.cc b/examples/plugin/plugin/tracer.cc index 6cf63af118..f054da7f03 100644 --- a/examples/plugin/plugin/tracer.cc +++ b/examples/plugin/plugin/tracer.cc @@ -2,9 +2,10 @@ #include -namespace nostd = opentelemetry::nostd; -namespace core = opentelemetry::core; -namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace common = opentelemetry::common; +namespace core = opentelemetry::core; +namespace trace = opentelemetry::trace; namespace { @@ -13,7 +14,8 @@ class Span final : public trace::Span public: Span(std::shared_ptr &&tracer, nostd::string_view name, - const trace::StartSpanOptions &options) noexcept + const opentelemetry::trace::KeyValueIterable & /*attributes*/, + const trace::StartSpanOptions & /*options*/) noexcept : tracer_{std::move(tracer)}, name_{name} { std::cout << "StartSpan: " << name << "\n"; @@ -22,6 +24,10 @@ class Span final : public trace::Span ~Span() { std::cout << "~Span\n"; } // opentelemetry::trace::Span + void SetAttribute(nostd::string_view /*name*/, + const common::AttributeValue & /*value*/) noexcept override + {} + void AddEvent(nostd::string_view /*name*/) noexcept override {} void AddEvent(nostd::string_view /*name*/, core::SystemTimestamp /*timestamp*/) noexcept override @@ -38,7 +44,7 @@ class Span final : public trace::Span void UpdateName(nostd::string_view /*name*/) noexcept override {} - void End() noexcept override {} + void End(const trace::EndSpanOptions & /*options*/) noexcept override {} bool IsRecording() const noexcept override { return true; } @@ -52,9 +58,11 @@ class Span final : public trace::Span Tracer::Tracer(nostd::string_view /*output*/) {} -nostd::unique_ptr Tracer::StartSpan(nostd::string_view name, - const trace::StartSpanOptions &options) noexcept +nostd::unique_ptr Tracer::StartSpan( + nostd::string_view name, + const opentelemetry::trace::KeyValueIterable &attributes, + const trace::StartSpanOptions &options) noexcept { return nostd::unique_ptr{ - new (std::nothrow) Span{this->shared_from_this(), name, options}}; + new (std::nothrow) Span{this->shared_from_this(), name, attributes, options}}; } diff --git a/examples/plugin/plugin/tracer.h b/examples/plugin/plugin/tracer.h index 4d653f32e9..ad1b98633d 100644 --- a/examples/plugin/plugin/tracer.h +++ b/examples/plugin/plugin/tracer.h @@ -13,7 +13,8 @@ class Tracer final : public opentelemetry::trace::Tracer, // opentelemetry::trace::Tracer opentelemetry::nostd::unique_ptr StartSpan( opentelemetry::nostd::string_view name, - const opentelemetry::trace::StartSpanOptions &options) noexcept override; + const opentelemetry::trace::KeyValueIterable & /*attributes*/, + const opentelemetry::trace::StartSpanOptions & /*options */) noexcept override; void ForceFlushWithMicroseconds(uint64_t /*timeout*/) noexcept override {} diff --git a/examples/simple/BUILD b/examples/simple/BUILD new file mode 100644 index 0000000000..34583e2eac --- /dev/null +++ b/examples/simple/BUILD @@ -0,0 +1,25 @@ +cc_library( + name = "foo_library", + srcs = [ + "foo_library/foo_library.cc", + ], + hdrs = [ + "foo_library/foo_library.h", + ], + deps = [ + "//api", + ], +) + +cc_binary( + name = "example_simple", + srcs = [ + "main.cc", + "stdout_exporter.h", + ], + deps = [ + ":foo_library", + "//api", + "//sdk/src/trace", + ], +) diff --git a/examples/simple/CMakeLists.txt b/examples/simple/CMakeLists.txt new file mode 100644 index 0000000000..4b0ab35f74 --- /dev/null +++ b/examples/simple/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(foo_library foo_library/foo_library.cc) +target_link_libraries(foo_library ${CMAKE_THREAD_LIBS_INIT} opentelemetry_api) + +add_executable(example_simple main.cc) +target_link_libraries(example_simple ${CMAKE_THREAD_LIBS_INIT} foo_library + opentelemetry_trace) diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 0000000000..d59a1423cd --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,10 @@ + +# Simple Trace Example + +In this example, the application in `main.cc` initializes and registers a tracer +provider from the [OpenTelemetry SDK](https://github.com/open-telemetry/opentelemetry-cpp). +The application then calls a `foo_library` which has been instrumented using +the [OpenTelemetry API](https://github.com/open-telemetry/opentelemetry-cpp/tree/master/api). +Resulting telemetry is directed to stdout through a custom exporter. + +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for instructions on building and running the example. \ No newline at end of file diff --git a/examples/simple/foo_library/foo_library.cc b/examples/simple/foo_library/foo_library.cc new file mode 100644 index 0000000000..0990b85ced --- /dev/null +++ b/examples/simple/foo_library/foo_library.cc @@ -0,0 +1,33 @@ +#include "opentelemetry/trace/provider.h" + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; + +namespace +{ +nostd::shared_ptr get_tracer() +{ + auto provider = trace::Provider::GetTracerProvider(); + return provider->GetTracer("foo_library"); +} + +void f1() +{ + auto span = get_tracer()->StartSpan("f1"); +} + +void f2() +{ + auto span = get_tracer()->StartSpan("f2"); + + f1(); + f1(); +} +} // namespace + +void foo_library() +{ + auto span = get_tracer()->StartSpan("library"); + + f2(); +} diff --git a/examples/simple/foo_library/foo_library.h b/examples/simple/foo_library/foo_library.h new file mode 100644 index 0000000000..7ac75c0e50 --- /dev/null +++ b/examples/simple/foo_library/foo_library.h @@ -0,0 +1,3 @@ +#pragma once + +void foo_library(); diff --git a/examples/simple/main.cc b/examples/simple/main.cc new file mode 100644 index 0000000000..756c764e89 --- /dev/null +++ b/examples/simple/main.cc @@ -0,0 +1,29 @@ +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/trace/provider.h" + +// Using an exporter that simply dumps span data to stdout. +#include "stdout_exporter.h" + +#include "foo_library/foo_library.h" + +namespace +{ +void initTracer() +{ + auto exporter = std::unique_ptr(new StdoutExporter); + auto processor = std::shared_ptr( + new sdktrace::SimpleSpanProcessor(std::move(exporter))); + auto provider = nostd::shared_ptr(new sdktrace::TracerProvider(processor)); + // Set the global trace provider + trace::Provider::SetTracerProvider(provider); +} +} // namespace + +int main() +{ + // Removing this line will leave the default noop TracerProvider in place. + initTracer(); + + foo_library(); +} diff --git a/examples/simple/stdout_exporter.h b/examples/simple/stdout_exporter.h new file mode 100644 index 0000000000..6a58c5b8d6 --- /dev/null +++ b/examples/simple/stdout_exporter.h @@ -0,0 +1,52 @@ +#pragma once + +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/sdk/trace/span_data.h" + +#include + +namespace trace = opentelemetry::trace; +namespace nostd = opentelemetry::nostd; +namespace sdktrace = opentelemetry::sdk::trace; + +class StdoutExporter final : public sdktrace::SpanExporter +{ + std::unique_ptr MakeRecordable() noexcept + { + return std::unique_ptr(new sdktrace::SpanData); + } + + sdktrace::ExportResult Export( + const nostd::span> &spans) noexcept + { + for (auto &recordable : spans) + { + auto span = std::unique_ptr( + static_cast(recordable.release())); + + if (span != nullptr) + { + char trace_id[32] = {0}; + char span_id[16] = {0}; + char parent_span_id[16] = {0}; + + span->GetTraceId().ToLowerBase16(trace_id); + span->GetSpanId().ToLowerBase16(span_id); + span->GetParentSpanId().ToLowerBase16(parent_span_id); + + std::cout << "{" + << "\n name : " << span->GetName() + << "\n trace_id : " << std::string(trace_id, 32) + << "\n span_id : " << std::string(span_id, 16) + << "\n parent_span_id: " << std::string(parent_span_id, 16) + << "\n start : " << span->GetStartTime().time_since_epoch().count() + << "\n duration : " << span->GetDuration().count() << "\n}" + << "\n"; + } + } + + return sdktrace::ExportResult::kSuccess; + } + + void Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept {} +}; diff --git a/exporters/otlp/BUILD b/exporters/otlp/BUILD index 09b1516ab5..2132ee99c9 100644 --- a/exporters/otlp/BUILD +++ b/exporters/otlp/BUILD @@ -14,6 +14,8 @@ package(default_visibility = ["//visibility:public"]) +load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark") + cc_library( name = "recordable", srcs = [ @@ -28,3 +30,48 @@ cc_library( "@com_github_opentelemetry_proto//:trace_proto_cc", ], ) + +cc_library( + name = "otlp_exporter", + srcs = [ + 'otlp_exporter.cc', + ], + hdrs = [ + 'otlp_exporter.h', + ], + deps = [ + ":recordable", + "//sdk/src/trace", + + # For gRPC + "@com_github_opentelemetry_proto//:trace_service_grpc_cc", + "@com_github_grpc_grpc//:grpc++", + ], +) + +cc_test( + name = "recordable_test", + srcs = ["recordable_test.cc"], + deps = [ + ":recordable", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "otlp_exporter_test", + srcs = ["otlp_exporter_test.cc"], + deps = [ + ":otlp_exporter", + "//api", + "@com_google_googletest//:gtest_main", + ], +) + +otel_cc_benchmark( + name = "otlp_exporter_benchmark", + srcs = ["otlp_exporter_benchmark.cc"], + deps = [ + ":otlp_exporter", + ], +) diff --git a/exporters/otlp/CMakeLists.txt b/exporters/otlp/CMakeLists.txt index 07311ce909..3cfc1a5832 100644 --- a/exporters/otlp/CMakeLists.txt +++ b/exporters/otlp/CMakeLists.txt @@ -1,3 +1,11 @@ add_library(opentelemetry_exporter_otprotocol recordable.cc) target_link_libraries(opentelemetry_exporter_otprotocol $) + +add_executable(recordable_test recordable_test.cc) +target_link_libraries(recordable_test + ${GTEST_BOTH_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + opentelemetry_exporter_otprotocol + protobuf::libprotobuf) +gtest_add_tests(TARGET recordable_test TEST_PREFIX exporter. TEST_LIST recordable_test) diff --git a/exporters/otlp/otlp_exporter.cc b/exporters/otlp/otlp_exporter.cc new file mode 100644 index 0000000000..951597044a --- /dev/null +++ b/exporters/otlp/otlp_exporter.cc @@ -0,0 +1,81 @@ +#include "otlp_exporter.h" +#include "recordable.h" + +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +const std::string kCollectorAddress = "localhost:55678"; + +// ----------------------------- Helper functions ------------------------------ + +/** + * Add span protobufs contained in recordables to request. + * @param spans the spans to export + * @param request the current request + */ +void PopulateRequest(const nostd::span> &spans, + proto::collector::trace::v1::ExportTraceServiceRequest *request) +{ + auto resource_span = request->add_resource_spans(); + auto instrumentation_lib = resource_span->add_instrumentation_library_spans(); + + for (auto &recordable : spans) + { + auto rec = std::unique_ptr(static_cast(recordable.release())); + *instrumentation_lib->add_spans() = std::move(rec->span()); + } +} + +/** + * Create service stub to communicate with the OpenTelemetry Collector. + */ +std::unique_ptr MakeServiceStub() +{ + auto channel = grpc::CreateChannel(kCollectorAddress, grpc::InsecureChannelCredentials()); + return proto::collector::trace::v1::TraceService::NewStub(channel); +} + +// -------------------------------- Contructors -------------------------------- + +OtlpExporter::OtlpExporter() : OtlpExporter(MakeServiceStub()) {} + +OtlpExporter::OtlpExporter( + std::unique_ptr stub) + : trace_service_stub_(std::move(stub)) +{} + +// ----------------------------- Exporter methods ------------------------------ + +std::unique_ptr OtlpExporter::MakeRecordable() noexcept +{ + return std::unique_ptr(new Recordable); +} + +sdk::trace::ExportResult OtlpExporter::Export( + const nostd::span> &spans) noexcept +{ + proto::collector::trace::v1::ExportTraceServiceRequest request; + + PopulateRequest(spans, &request); + + grpc::ClientContext context; + proto::collector::trace::v1::ExportTraceServiceResponse response; + + grpc::Status status = trace_service_stub_->Export(&context, request, &response); + + if (!status.ok()) + { + std::cerr << "[OTLP Exporter] Export() failed: " << status.error_message() << "\n"; + return sdk::trace::ExportResult::kFailure; + } + return sdk::trace::ExportResult::kSuccess; +} +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/otlp_exporter.h b/exporters/otlp/otlp_exporter.h new file mode 100644 index 0000000000..4ac324f3ca --- /dev/null +++ b/exporters/otlp/otlp_exporter.h @@ -0,0 +1,59 @@ +#pragma once + +#include "opentelemetry/sdk/trace/exporter.h" +#include "opentelemetry/proto/collector/trace/v1/trace_service.grpc.pb.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +/** + * The OTLP exporter exports span data in OpenTelemetry Protocol (OTLP) format. + */ +class OtlpExporter final : public opentelemetry::sdk::trace::SpanExporter +{ +public: + /** + * Create an OtlpExporter. This constructor initializes a service stub to be + * used for exporting. + */ + OtlpExporter(); + + /** + * Create a span recordable. + * @return a newly initialized Recordable object + */ + std::unique_ptr MakeRecordable() noexcept override; + + /** + * Export a batch of span recordables in OTLP format. + * @param spans a span of unique pointers to span recordables + */ + sdk::trace::ExportResult Export( + const nostd::span> &spans) noexcept override; + + /** + * Shut down the exporter. + * @param timeout an optional timeout, the default timeout of 0 means that no + * timeout is applied. + */ + void Shutdown(std::chrono::microseconds timeout = std::chrono::microseconds(0)) noexcept override {}; + +private: + // For testing + friend class OtlpExporterTestPeer; + + // Store service stub internally. Useful for testing. + std::unique_ptr trace_service_stub_; + + /** + * Create an OtlpExporter using the specified service stub. + * Only tests can call this constructor directly. + * @param stub the service stub to be used for exporting + */ + OtlpExporter(std::unique_ptr stub); +}; +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/otlp_exporter_benchmark.cc b/exporters/otlp/otlp_exporter_benchmark.cc new file mode 100644 index 0000000000..81205dd1b3 --- /dev/null +++ b/exporters/otlp/otlp_exporter_benchmark.cc @@ -0,0 +1,165 @@ +#include +#include "otlp_exporter.h" +#include "recordable.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +const int kBatchSize = 200; +const int kNumAttributes = 5; +const int kNumIterations = 1000; + +const trace::TraceId kTraceId(std::array( + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})); +const trace::SpanId kSpanId(std::array( + {0, 0, 0, 0, 0, 0, 0, 2})); +const trace::SpanId kParentSpanId(std::array( + {0, 0, 0, 0, 0, 0, 0, 3})); + +// ----------------------- Helper classes and functions ------------------------ + +// Create a fake service stub to avoid dependency on gmock +class FakeServiceStub : public proto::collector::trace::v1::TraceService::StubInterface +{ + grpc::Status Export(grpc::ClientContext *, + const proto::collector::trace::v1::ExportTraceServiceRequest &, + proto::collector::trace::v1::ExportTraceServiceResponse *) override + { + return grpc::Status::OK; + } + + grpc::ClientAsyncResponseReaderInterface + *AsyncExportRaw(grpc::ClientContext *, + const proto::collector::trace::v1::ExportTraceServiceRequest &, + grpc::CompletionQueue *) override + { + return nullptr; + } + + grpc::ClientAsyncResponseReaderInterface + *PrepareAsyncExportRaw(grpc::ClientContext *, + const proto::collector::trace::v1::ExportTraceServiceRequest &, + grpc::CompletionQueue *) override + { + return nullptr; + } +}; + +// OtlpExporterTestPeer is a friend class of OtlpExporter +class OtlpExporterTestPeer +{ +public: + std::unique_ptr GetExporter() + { + auto mock_stub = new FakeServiceStub(); + std::unique_ptr stub_interface( + mock_stub); + return std::unique_ptr( + new exporter::otlp::OtlpExporter(std::move(stub_interface))); + } +}; + +// Helper function to create empty spans +void CreateEmptySpans(std::array, kBatchSize> &recordables) +{ + for (int i = 0; i < kBatchSize; i++) + { + auto recordable = std::unique_ptr(new Recordable); + recordables[i] = std::move(recordable); + } +} + +// Helper function to create sparse spans +void CreateSparseSpans(std::array, kBatchSize> &recordables) +{ + for (int i = 0; i < kBatchSize; i++) + { + auto recordable = std::unique_ptr(new Recordable); + + recordable->SetIds(kTraceId, kSpanId, kParentSpanId); + recordable->SetName("TestSpan"); + recordable->SetStartTime(core::SystemTimestamp(std::chrono::system_clock::now())); + recordable->SetDuration(std::chrono::nanoseconds(10)); + + recordables[i] = std::move(recordable); + } +} + +// Helper function to create dense spans +void CreateDenseSpans(std::array, kBatchSize> &recordables) +{ + for (int i = 0; i < kBatchSize; i++) + { + auto recordable = std::unique_ptr(new Recordable); + + recordable->SetIds(kTraceId, kSpanId, kParentSpanId); + recordable->SetName("TestSpan"); + recordable->SetStartTime(core::SystemTimestamp(std::chrono::system_clock::now())); + recordable->SetDuration(std::chrono::nanoseconds(10)); + + for (int i = 0; i < kNumAttributes; i++) + { + recordable->SetAttribute("int_key_" + i, static_cast(i)); + recordable->SetAttribute("str_key_" + i, "string_val_" + i); + recordable->SetAttribute("bool_key_" + i, true); + } + + recordables[i] = std::move(recordable); + } +} + +// ------------------------------ Benchmark tests ------------------------------ + +// Benchmark Export() with empty spans +void BM_OtlpExporterEmptySpans(benchmark::State &state) +{ + std::unique_ptr testpeer(new OtlpExporterTestPeer()); + auto exporter = testpeer->GetExporter(); + + while(state.KeepRunningBatch(kNumIterations)) + { + std::array, kBatchSize> recordables; + CreateEmptySpans(recordables); + exporter->Export(nostd::span>(recordables)); + } +} +BENCHMARK(BM_OtlpExporterEmptySpans); + +// Benchmark Export() with sparse spans +void BM_OtlpExporterSparseSpans(benchmark::State &state) +{ + std::unique_ptr testpeer(new OtlpExporterTestPeer()); + auto exporter = testpeer->GetExporter(); + + while(state.KeepRunningBatch(kNumIterations)) + { + std::array, kBatchSize> recordables; + CreateSparseSpans(recordables); + exporter->Export(nostd::span>(recordables)); + } +} +BENCHMARK(BM_OtlpExporterSparseSpans); + +// Benchmark Export() with dense spans +void BM_OtlpExporterDenseSpans(benchmark::State &state) +{ + std::unique_ptr testpeer(new OtlpExporterTestPeer()); + auto exporter = testpeer->GetExporter(); + + while(state.KeepRunningBatch(kNumIterations)) + { + std::array, kBatchSize> recordables; + CreateDenseSpans(recordables); + exporter->Export(nostd::span>(recordables)); + } +} +BENCHMARK(BM_OtlpExporterDenseSpans); + +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE + +BENCHMARK_MAIN(); diff --git a/exporters/otlp/otlp_exporter_test.cc b/exporters/otlp/otlp_exporter_test.cc new file mode 100644 index 0000000000..ea98357054 --- /dev/null +++ b/exporters/otlp/otlp_exporter_test.cc @@ -0,0 +1,81 @@ +#include "otlp_exporter.h" +#include "opentelemetry/proto/collector/trace/v1/trace_service_mock.grpc.pb.h" +#include "opentelemetry/sdk/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/trace/provider.h" + +#include + +using namespace testing; + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ + +class OtlpExporterTestPeer : public ::testing::Test +{ +public: + std::unique_ptr GetExporter( + std::unique_ptr &stub_interface) + { + return std::unique_ptr(new OtlpExporter(std::move(stub_interface))); + } +}; + +// Call Export() directly +TEST_F(OtlpExporterTestPeer, ExportUnitTest) +{ + auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); + std::unique_ptr stub_interface( + mock_stub); + auto exporter = GetExporter(stub_interface); + + auto recordable_1 = exporter->MakeRecordable(); + recordable_1->SetName("Test span 1"); + auto recordable_2 = exporter->MakeRecordable(); + recordable_2->SetName("Test span 2"); + + // Test successful RPC + nostd::span> batch_1(&recordable_1, 1); + EXPECT_CALL(*mock_stub, Export(_, _, _)).Times(Exactly(1)).WillOnce(Return(grpc::Status::OK)); + auto result = exporter->Export(batch_1); + EXPECT_EQ(sdk::trace::ExportResult::kSuccess, result); + + // Test failed RPC + nostd::span> batch_2(&recordable_2, 1); + EXPECT_CALL(*mock_stub, Export(_, _, _)) + .Times(Exactly(1)) + .WillOnce(Return(grpc::Status::CANCELLED)); + result = exporter->Export(batch_2); + EXPECT_EQ(sdk::trace::ExportResult::kFailure, result); +} + +// Create spans, let processor call Export() +TEST_F(OtlpExporterTestPeer, ExportIntegrationTest) +{ + auto mock_stub = new proto::collector::trace::v1::MockTraceServiceStub(); + std::unique_ptr stub_interface( + mock_stub); + + auto exporter = GetExporter(stub_interface); + + auto processor = std::shared_ptr( + new sdk::trace::SimpleSpanProcessor(std::move(exporter))); + auto provider = + nostd::shared_ptr(new sdk::trace::TracerProvider(processor)); + auto tracer = provider->GetTracer("test"); + + EXPECT_CALL(*mock_stub, Export(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Return(grpc::Status::OK)); + + auto parent_span = tracer->StartSpan("Test parent span"); + auto child_span = tracer->StartSpan("Test child span"); + child_span->End(); + parent_span->End(); +} +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/recordable.cc b/exporters/otlp/recordable.cc index d38f284577..6092c0f005 100644 --- a/exporters/otlp/recordable.cc +++ b/exporters/otlp/recordable.cc @@ -5,6 +5,23 @@ namespace exporter { namespace otlp { +void Recordable::SetIds(trace::TraceId trace_id, + trace::SpanId span_id, + trace::SpanId parent_span_id) noexcept +{ + span_.set_trace_id(reinterpret_cast(trace_id.Id().data()), trace::TraceId::kSize); + span_.set_span_id(reinterpret_cast(span_id.Id().data()), trace::SpanId::kSize); + span_.set_parent_span_id(reinterpret_cast(parent_span_id.Id().data()), + trace::SpanId::kSize); +} + +void Recordable::SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept +{ + (void)key; + (void)value; +} + void Recordable::AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept { (void)name; @@ -20,6 +37,18 @@ void Recordable::SetName(nostd::string_view name) noexcept { span_.set_name(name.data(), name.size()); } + +void Recordable::SetStartTime(opentelemetry::core::SystemTimestamp start_time) noexcept +{ + const uint64_t nano_unix_time = start_time.time_since_epoch().count(); + span_.set_start_time_unix_nano(nano_unix_time); +} + +void Recordable::SetDuration(std::chrono::nanoseconds duration) noexcept +{ + const uint64_t unix_end_time = span_.start_time_unix_nano() + duration.count(); + span_.set_end_time_unix_nano(unix_end_time); +} } // namespace otlp } // namespace exporter OPENTELEMETRY_END_NAMESPACE diff --git a/exporters/otlp/recordable.h b/exporters/otlp/recordable.h index 5ea1edf497..9d55dfd404 100644 --- a/exporters/otlp/recordable.h +++ b/exporters/otlp/recordable.h @@ -14,13 +14,23 @@ class Recordable final : public sdk::trace::Recordable public: const proto::trace::v1::Span &span() const noexcept { return span_; } - // sdk::trace::Recordable + void SetIds(trace::TraceId trace_id, + trace::SpanId span_id, + trace::SpanId parent_span_id) noexcept override; + + void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept override; + void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override; void SetStatus(trace::CanonicalCode code, nostd::string_view description) noexcept override; void SetName(nostd::string_view name) noexcept override; + void SetStartTime(opentelemetry::core::SystemTimestamp start_time) noexcept override; + + void SetDuration(std::chrono::nanoseconds duration) noexcept override; + private: proto::trace::v1::Span span_; }; diff --git a/exporters/otlp/recordable_test.cc b/exporters/otlp/recordable_test.cc new file mode 100644 index 0000000000..b086c46b36 --- /dev/null +++ b/exporters/otlp/recordable_test.cc @@ -0,0 +1,73 @@ +#include "exporters/otlp/recordable.h" + +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace exporter +{ +namespace otlp +{ +TEST(Recordable, SetIds) +{ + const trace::TraceId trace_id( + std::array( + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})); + + const trace::SpanId span_id( + std::array( + {0, 0, 0, 0, 0, 0, 0, 2})); + + const trace::SpanId parent_span_id( + std::array( + {0, 0, 0, 0, 0, 0, 0, 3})); + + Recordable rec; + + rec.SetIds(trace_id, span_id, parent_span_id); + + EXPECT_EQ(rec.span().trace_id(), + std::string(reinterpret_cast(trace_id.Id().data()), trace::TraceId::kSize)); + EXPECT_EQ(rec.span().span_id(), + std::string(reinterpret_cast(span_id.Id().data()), trace::SpanId::kSize)); + EXPECT_EQ(rec.span().parent_span_id(), + std::string(reinterpret_cast(parent_span_id.Id().data()), trace::SpanId::kSize)); +} + +TEST(Recordable, SetName) +{ + Recordable rec; + nostd::string_view name = "TestSpan"; + rec.SetName(name); + EXPECT_EQ(rec.span().name(), name); +} + +TEST(Recordable, SetStartTime) +{ + Recordable rec; + std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now(); + core::SystemTimestamp start_timestamp(start_time); + + uint64_t unix_start = + std::chrono::duration_cast(start_time.time_since_epoch()).count(); + + rec.SetStartTime(start_timestamp); + EXPECT_EQ(rec.span().start_time_unix_nano(), unix_start); +} + +TEST(Recordable, SetDuration) +{ + Recordable rec; + // Start time is 0 + core::SystemTimestamp start_timestamp; + + std::chrono::nanoseconds duration(10); + uint64_t unix_end = duration.count(); + + rec.SetStartTime(start_timestamp); + rec.SetDuration(duration); + + EXPECT_EQ(rec.span().end_time_unix_nano(), unix_end); +} +} // namespace otlp +} // namespace exporter +OPENTELEMETRY_END_NAMESPACE diff --git a/ext/src/zpages/README.md b/ext/src/zpages/README.md new file mode 100644 index 0000000000..f1bad98516 --- /dev/null +++ b/ext/src/zpages/README.md @@ -0,0 +1,46 @@ +# zPages +> Last updated 6/26/20 + +# Table of Contents +- [Summary](#summary) + - [TraceZ](#tracez) + - [RPCz](#rpcz) +- [Usage](#usage) +- [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. + +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. + +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. + +> TODO: Add picture examples for span overview and individual span view + +### TraceZ +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. +- TracezDataAggregator (TDA) + - Intermediary between the TSP and THS, which also performs various functions and calculations 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. + +### 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). + +# Usage + +> TODO: Add instructions to add zPages + +## Links of Interest +- [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) + + diff --git a/sdk/include/opentelemetry/sdk/common/atomic_shared_ptr.h b/sdk/include/opentelemetry/sdk/common/atomic_shared_ptr.h index 968167aea8..f3630cce67 100644 --- a/sdk/include/opentelemetry/sdk/common/atomic_shared_ptr.h +++ b/sdk/include/opentelemetry/sdk/common/atomic_shared_ptr.h @@ -11,28 +11,10 @@ namespace sdk /** * A wrapper to provide atomic shared pointers. * - * This wrapper relies on std::atomic for C++20, a mutex for gcc 4.8, and - * specializations of std::atomic_store and std::atomic_load for all other - * instances. + * This wrapper relies on a mutex for gcc 4.8, and specializations of + * std::atomic_store and std::atomic_load for all other instances. */ -#if __cplusplus > 201703L -template -class AtomicSharedPtr -{ -public: - explicit AtomicSharedPtr(std::shared_ptr ptr) noexcept : ptr_{std::move(ptr)} {} - - void store(const std::shared_ptr &other) noexcept - { - ptr_.store(other, std::memory_order_release); - } - - std::shared_ptr load() const noexcept { return ptr_.load(std::memory_order_acquire); } - -private: - std::atomic> ptr_; -}; -#elif (__GNUC__ == 4 && (__GNUC_MINOR__ >= 8)) +#if (__GNUC__ == 4 && (__GNUC_MINOR__ >= 8)) template class AtomicSharedPtr { diff --git a/sdk/include/opentelemetry/sdk/trace/exporter.h b/sdk/include/opentelemetry/sdk/trace/exporter.h index d517c572b2..ec5473edb8 100644 --- a/sdk/include/opentelemetry/sdk/trace/exporter.h +++ b/sdk/include/opentelemetry/sdk/trace/exporter.h @@ -48,7 +48,8 @@ class SpanExporter * @param spans a span of unique pointers to span recordables */ virtual ExportResult Export( - nostd::span> &spans) noexcept = 0; + const nostd::span> + &spans) noexcept = 0; /** * Shut down the exporter. diff --git a/sdk/include/opentelemetry/sdk/trace/recordable.h b/sdk/include/opentelemetry/sdk/trace/recordable.h index c102958fe3..a2ac8984dc 100644 --- a/sdk/include/opentelemetry/sdk/trace/recordable.h +++ b/sdk/include/opentelemetry/sdk/trace/recordable.h @@ -1,5 +1,6 @@ #pragma once +#include "opentelemetry/common/attribute_value.h" #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/trace/canonical_code.h" @@ -33,6 +34,14 @@ class Recordable opentelemetry::trace::SpanId span_id, opentelemetry::trace::SpanId parent_span_id) noexcept = 0; + /** + * Set an attribute of a span. + * @param name the name of the attribute + * @param value the attribute value + */ + virtual void SetAttribute(nostd::string_view key, + const opentelemetry::common::AttributeValue &value) noexcept = 0; + /** * Add an event to a span. * @param name the name of the event diff --git a/sdk/include/opentelemetry/sdk/trace/sampler.h b/sdk/include/opentelemetry/sdk/trace/sampler.h new file mode 100644 index 0000000000..35a0927404 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/sampler.h @@ -0,0 +1,84 @@ +#pragma once + +#include "opentelemetry/common/attribute_value.h" +#include "opentelemetry/trace/span.h" +#include "opentelemetry/trace/trace_id.h" +#include "opentelemetry/version.h" +#include "opentelemetry/trace/span_context.h" + +#include +#include +#include + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +namespace trace_api = opentelemetry::trace; + +/** + * A sampling Decision for a Span to be created. + */ +enum class Decision +{ + // IsRecording() == false, span will not be recorded and all events and attributes will be + // dropped. + NOT_RECORD, + // IsRecording() == true, but Sampled flag MUST NOT be set. + RECORD, + // IsRecording() == true AND Sampled flag` MUST be set. + RECORD_AND_SAMPLE +}; + +/** + * The output of ShouldSample. + * It contains a sampling Decision and a set of Span Attributes. + */ +struct SamplingResult +{ + Decision decision; + // A set of span Attributes that will also be added to the Span. Can be nullptr. + std::unique_ptr> attributes; +}; + +/** + * The Sampler interface allows users to create custom samplers which will return a + * SamplingResult based on information that is typically available just before the Span was created. + */ +class Sampler +{ +public: + virtual ~Sampler() = default; + /** + * Called during Span creation to make a sampling decision. + * + * @param parent_context a const pointer of the SpanContext of a parent Span. + * null if this is a root span. + * @param trace_id the TraceId for the new Span. This will be identical to that in + * the parentContext, unless this is a root span. + * @param name the name of the new Span. + * @param spanKind the trace_api::SpanKind of the Span. + * @param attributes list of AttributeValue with their keys. + * @param links TODO: Collection of links that will be associated with the Span to be created. + * @return sampling result whether span should be sampled or not. + * @since 0.1.0 + */ + + virtual SamplingResult ShouldSample(const trace_api::SpanContext *parent_context, + trace_api::TraceId trace_id, + nostd::string_view name, + trace_api::SpanKind span_kind, + const trace_api::KeyValueIterable &attributes) noexcept = 0; + + /** + * Returns the sampler name or short description with the configuration. + * This may be displayed on debug pages or in the logs. + * + * @return the description of this Sampler. + */ + virtual std::string GetDescription() const noexcept = 0; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/always_off.h b/sdk/include/opentelemetry/sdk/trace/samplers/always_off.h new file mode 100644 index 0000000000..252cd9e4fd --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/samplers/always_off.h @@ -0,0 +1,42 @@ +#pragma once + +#include "opentelemetry/sdk/trace/sampler.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +namespace trace_api = opentelemetry::trace; + +/** + * The always off sampler always returns NOT_RECORD, effectively disabling + * tracing functionality. + */ +class AlwaysOffSampler : public Sampler +{ +public: + /** + * @return Returns NOT_RECORD always + */ + SamplingResult ShouldSample( + const trace_api::SpanContext * /*parent_context*/, + trace_api::TraceId /*trace_id*/, + nostd::string_view /*name*/, + trace_api::SpanKind /*span_kind*/, + const trace_api::KeyValueIterable & /*attributes*/) noexcept override + { + return { Decision::NOT_RECORD, nullptr }; + } + + /** + * @return Description MUST be AlwaysOffSampler + */ + std::string GetDescription() const noexcept override + { + return "AlwaysOffSampler"; + } +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/always_on.h b/sdk/include/opentelemetry/sdk/trace/samplers/always_on.h new file mode 100644 index 0000000000..5dd5f86457 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/samplers/always_on.h @@ -0,0 +1,37 @@ +#pragma once + +#include "opentelemetry/sdk/trace/sampler.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +namespace trace_api = opentelemetry::trace; + +/** + * The always on sampler is a default sampler which always return Decision::RECORD_AND_SAMPLE + */ +class AlwaysOnSampler : public Sampler +{ +public: + /** + * @return Always return Decision RECORD_AND_SAMPLE + */ + inline SamplingResult ShouldSample(const trace_api::SpanContext * /*parent_context*/, + trace_api::TraceId /*trace_id*/, + nostd::string_view /*name*/, + trace_api::SpanKind /*span_kind*/, + const trace_api::KeyValueIterable & /*attributes*/) noexcept override + { + return {Decision::RECORD_AND_SAMPLE, nullptr}; + } + + /** + * @return Description MUST be AlwaysOnSampler + */ + inline std::string GetDescription() const noexcept override { return "AlwaysOnSampler"; } +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/include/opentelemetry/sdk/trace/samplers/parent_or_else.h b/sdk/include/opentelemetry/sdk/trace/samplers/parent_or_else.h new file mode 100644 index 0000000000..aec9934604 --- /dev/null +++ b/sdk/include/opentelemetry/sdk/trace/samplers/parent_or_else.h @@ -0,0 +1,42 @@ +#pragma once + +#include "opentelemetry/sdk/common/atomic_shared_ptr.h" +#include "opentelemetry/sdk/trace/sampler.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +namespace trace_api = opentelemetry::trace; + +/** + * The parent or else sampler is a composite sampler. ParentOrElse(delegateSampler) either respects + * the parent span's sampling decision or delegates to delegateSampler for root spans. + */ +class ParentOrElseSampler : public Sampler +{ +public: + explicit ParentOrElseSampler(std::shared_ptr delegate_sampler) noexcept; + /** The decision either respects the parent span's sampling decision or delegates to + * delegateSampler for root spans + * @return Returns NOT_RECORD always + */ + SamplingResult ShouldSample(const trace_api::SpanContext * parent_context, + trace_api::TraceId trace_id, + nostd::string_view name, + trace_api::SpanKind span_kind, + const trace_api::KeyValueIterable & attributes) noexcept override; + + /** + * @return Description MUST be ParentOrElse{delegate_sampler_.getDescription()} + */ + std::string GetDescription() const noexcept override; + +private: + const std::shared_ptr delegate_sampler_; + const std::string description_; +}; +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/simple_processor.h b/sdk/include/opentelemetry/sdk/trace/simple_processor.h similarity index 100% rename from sdk/src/trace/simple_processor.h rename to sdk/include/opentelemetry/sdk/trace/simple_processor.h diff --git a/sdk/include/opentelemetry/sdk/trace/span_data.h b/sdk/include/opentelemetry/sdk/trace/span_data.h index 8ac620ad1b..54ef9bcd02 100644 --- a/sdk/include/opentelemetry/sdk/trace/span_data.h +++ b/sdk/include/opentelemetry/sdk/trace/span_data.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "opentelemetry/core/timestamp.h" #include "opentelemetry/nostd/string_view.h" #include "opentelemetry/sdk/trace/recordable.h" @@ -67,6 +68,15 @@ class SpanData final : public Recordable */ std::chrono::nanoseconds GetDuration() const noexcept { return duration_; } + /** + * Get the attributes for this span + * @return the attributes for this span + */ + const std::unordered_map &GetAttributes() const noexcept + { + return attributes_; + } + void SetIds(opentelemetry::trace::TraceId trace_id, opentelemetry::trace::SpanId span_id, opentelemetry::trace::SpanId parent_span_id) noexcept override @@ -76,6 +86,11 @@ class SpanData final : public Recordable parent_span_id_ = parent_span_id; } + void SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept override + { + attributes_[std::string(key)] = value; + } + void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override { (void)name; @@ -106,6 +121,7 @@ class SpanData final : public Recordable std::string name_; opentelemetry::trace::CanonicalCode status_code_{opentelemetry::trace::CanonicalCode::OK}; std::string status_desc_; + std::unordered_map attributes_; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/tracer.h b/sdk/include/opentelemetry/sdk/trace/tracer.h index 06c83b1058..2d0afde498 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer.h @@ -2,6 +2,7 @@ #include "opentelemetry/sdk/common/atomic_shared_ptr.h" #include "opentelemetry/sdk/trace/processor.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/trace/tracer.h" #include "opentelemetry/version.h" @@ -20,7 +21,8 @@ class Tracer final : public trace_api::Tracer, public std::enable_shared_from_th * @param processor The span processor for this tracer. This must not be a * nullptr. */ - explicit Tracer(std::shared_ptr processor) noexcept : processor_{processor} {} + explicit Tracer(std::shared_ptr processor, + std::shared_ptr sampler = std::make_shared()) noexcept; /** * Set the span processor associated with this tracer. @@ -35,8 +37,15 @@ class Tracer final : public trace_api::Tracer, public std::enable_shared_from_th */ std::shared_ptr GetProcessor() const noexcept; + /** + * Obtain the sampler associated with this tracer. + * @return The sampler for this tracer. + */ + std::shared_ptr GetSampler() const noexcept; + nostd::unique_ptr StartSpan( nostd::string_view name, + const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options = {}) noexcept override; void ForceFlushWithMicroseconds(uint64_t timeout) noexcept override; @@ -45,6 +54,7 @@ class Tracer final : public trace_api::Tracer, public std::enable_shared_from_th private: opentelemetry::sdk::AtomicSharedPtr processor_; + const std::shared_ptr sampler_; }; } // namespace trace } // namespace sdk diff --git a/sdk/include/opentelemetry/sdk/trace/tracer_provider.h b/sdk/include/opentelemetry/sdk/trace/tracer_provider.h index 322d2fcb59..6581ad46be 100644 --- a/sdk/include/opentelemetry/sdk/trace/tracer_provider.h +++ b/sdk/include/opentelemetry/sdk/trace/tracer_provider.h @@ -6,6 +6,7 @@ #include "opentelemetry/nostd/shared_ptr.h" #include "opentelemetry/sdk/trace/processor.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" #include "opentelemetry/sdk/trace/tracer.h" #include "opentelemetry/trace/tracer_provider.h" @@ -18,11 +19,15 @@ class TracerProvider final : public opentelemetry::trace::TracerProvider { public: /** - * Initialize a new tracer provider. + * Initialize a new tracer provider with a specified sampler * @param processor The span processor for this tracer provider. This must * not be a nullptr. + * @param sampler The sampler for this tracer provider. This must + * not be a nullptr. */ - explicit TracerProvider(std::shared_ptr processor) noexcept; + explicit TracerProvider( + std::shared_ptr processor, + std::shared_ptr sampler = std::make_shared()) noexcept; opentelemetry::nostd::shared_ptr GetTracer( nostd::string_view library_name, @@ -41,9 +46,16 @@ class TracerProvider final : public opentelemetry::trace::TracerProvider */ std::shared_ptr GetProcessor() const noexcept; + /** + * Obtain the sampler associated with this tracer provider. + * @return The span processor for this tracer provider. + */ + std::shared_ptr GetSampler() const noexcept; + private: opentelemetry::sdk::AtomicSharedPtr processor_; - opentelemetry::nostd::shared_ptr tracer_; + std::shared_ptr tracer_; + const std::shared_ptr sampler_; }; } // namespace trace } // namespace sdk diff --git a/sdk/src/trace/.tracer.cc.swp b/sdk/src/trace/.tracer.cc.swp new file mode 100644 index 0000000000..6bb638cf10 Binary files /dev/null and b/sdk/src/trace/.tracer.cc.swp differ diff --git a/sdk/src/trace/CMakeLists.txt b/sdk/src/trace/CMakeLists.txt index 455665be68..98f949997e 100644 --- a/sdk/src/trace/CMakeLists.txt +++ b/sdk/src/trace/CMakeLists.txt @@ -1 +1 @@ -add_library(opentelemetry_trace tracer_provider.cc tracer.cc span.cc) +add_library(opentelemetry_trace tracer_provider.cc tracer.cc span.cc samplers/parent_or_else.cc) diff --git a/sdk/src/trace/samplers/parent_or_else.cc b/sdk/src/trace/samplers/parent_or_else.cc new file mode 100644 index 0000000000..2b56ab9d4b --- /dev/null +++ b/sdk/src/trace/samplers/parent_or_else.cc @@ -0,0 +1,41 @@ +#include "opentelemetry/sdk/trace/samplers/parent_or_else.h" + +OPENTELEMETRY_BEGIN_NAMESPACE +namespace sdk +{ +namespace trace +{ +ParentOrElseSampler::ParentOrElseSampler(std::shared_ptr delegate_sampler) noexcept + : delegate_sampler_(delegate_sampler), + description_("ParentOrElse{" + delegate_sampler->GetDescription() + "}") +{} + +SamplingResult ParentOrElseSampler::ShouldSample( + const trace_api::SpanContext *parent_context, + trace_api::TraceId trace_id, + nostd::string_view name, + trace_api::SpanKind span_kind, + const trace_api::KeyValueIterable &attributes) noexcept +{ + if (parent_context == nullptr) + { + // If no parent (root span) exists returns the result of the delegateSampler + return delegate_sampler_->ShouldSample(parent_context, trace_id, name, span_kind, attributes); + } + + // If parent exists: + if (parent_context->IsSampled()) + { + return {Decision::RECORD_AND_SAMPLE, nullptr}; + } + + return {Decision::NOT_RECORD, nullptr}; +} + +std::string ParentOrElseSampler::GetDescription() const noexcept +{ + return description_; +} +} // namespace trace +} // namespace sdk +OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/src/trace/span.cc b/sdk/src/trace/span.cc index 7b4937bb86..96154a2799 100644 --- a/sdk/src/trace/span.cc +++ b/sdk/src/trace/span.cc @@ -7,11 +7,46 @@ namespace sdk { namespace trace { + +using opentelemetry::core::SteadyTimestamp; +using opentelemetry::core::SystemTimestamp; + +namespace +{ +SystemTimestamp NowOr(const SystemTimestamp &system) +{ + if (system == SystemTimestamp()) + { + return SystemTimestamp(std::chrono::system_clock::now()); + } + else + { + return system; + } +} + +SteadyTimestamp NowOr(const SteadyTimestamp &steady) +{ + if (steady == SteadyTimestamp()) + { + return SteadyTimestamp(std::chrono::steady_clock::now()); + } + else + { + return steady; + } +} +} // namespace + Span::Span(std::shared_ptr &&tracer, std::shared_ptr processor, nostd::string_view name, + const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options) noexcept - : tracer_{std::move(tracer)}, processor_{processor}, recordable_{processor_->MakeRecordable()} + : tracer_{std::move(tracer)}, + processor_{processor}, + recordable_{processor_->MakeRecordable()}, + start_steady_time{options.start_steady_time} { (void)options; if (recordable_ == nullptr) @@ -20,6 +55,14 @@ Span::Span(std::shared_ptr &&tracer, } processor_->OnStart(*recordable_); recordable_->SetName(name); + + attributes.ForEachKeyValue([&](nostd::string_view key, common::AttributeValue value) noexcept { + recordable_->SetAttribute(key, value); + return true; + }); + + recordable_->SetStartTime(NowOr(options.start_system_time)); + start_steady_time = NowOr(options.start_steady_time); } Span::~Span() @@ -27,6 +70,13 @@ Span::~Span() End(); } +void Span::SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept +{ + std::lock_guard lock_guard{mu_}; + + recordable_->SetAttribute(key, value); +} + void Span::AddEvent(nostd::string_view name) noexcept { (void)name; @@ -67,13 +117,18 @@ void Span::UpdateName(nostd::string_view name) noexcept recordable_->SetName(name); } -void Span::End() noexcept +void Span::End(const trace_api::EndSpanOptions &options) noexcept { std::lock_guard lock_guard{mu_}; if (recordable_ == nullptr) { return; } + + auto end_steady_time = NowOr(options.end_steady_time); + recordable_->SetDuration(std::chrono::steady_clock::time_point(end_steady_time) - + std::chrono::steady_clock::time_point(start_steady_time)); + processor_->OnEnd(std::move(recordable_)); recordable_.reset(); } diff --git a/sdk/src/trace/span.h b/sdk/src/trace/span.h index 3fd1bb4af7..489833703c 100644 --- a/sdk/src/trace/span.h +++ b/sdk/src/trace/span.h @@ -18,11 +18,14 @@ class Span final : public trace_api::Span explicit Span(std::shared_ptr &&tracer, std::shared_ptr processor, nostd::string_view name, + const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options) noexcept; ~Span() override; // trace_api::Span + void SetAttribute(nostd::string_view key, const common::AttributeValue &value) noexcept override; + void AddEvent(nostd::string_view name) noexcept override; void AddEvent(nostd::string_view name, core::SystemTimestamp timestamp) noexcept override; @@ -35,7 +38,7 @@ class Span final : public trace_api::Span void UpdateName(nostd::string_view name) noexcept override; - void End() noexcept override; + void End(const trace_api::EndSpanOptions &options = {}) noexcept override; bool IsRecording() const noexcept override; @@ -46,6 +49,7 @@ class Span final : public trace_api::Span std::shared_ptr processor_; mutable std::mutex mu_; std::unique_ptr recordable_; + opentelemetry::core::SteadyTimestamp start_steady_time; }; } // namespace trace } // namespace sdk diff --git a/sdk/src/trace/tracer.cc b/sdk/src/trace/tracer.cc index 15e89ceffd..959fa35259 100644 --- a/sdk/src/trace/tracer.cc +++ b/sdk/src/trace/tracer.cc @@ -9,6 +9,10 @@ namespace sdk { namespace trace { +Tracer::Tracer(std::shared_ptr processor, std::shared_ptr sampler) noexcept + : processor_{processor}, sampler_{sampler} +{} + void Tracer::SetProcessor(std::shared_ptr processor) noexcept { processor_.store(processor); @@ -19,12 +23,18 @@ std::shared_ptr Tracer::GetProcessor() const noexcept return processor_.load(); } +std::shared_ptr Tracer::GetSampler() const noexcept +{ + return sampler_; +} + nostd::unique_ptr Tracer::StartSpan( nostd::string_view name, + const trace_api::KeyValueIterable &attributes, const trace_api::StartSpanOptions &options) noexcept { - return nostd::unique_ptr{ - new (std::nothrow) Span{this->shared_from_this(), processor_.load(), name, options}}; + return nostd::unique_ptr{new (std::nothrow) Span{ + this->shared_from_this(), processor_.load(), name, attributes, options}}; } void Tracer::ForceFlushWithMicroseconds(uint64_t timeout) noexcept diff --git a/sdk/src/trace/tracer_provider.cc b/sdk/src/trace/tracer_provider.cc index 18e3dc99f9..32144c4c6b 100644 --- a/sdk/src/trace/tracer_provider.cc +++ b/sdk/src/trace/tracer_provider.cc @@ -5,15 +5,16 @@ namespace sdk { namespace trace { -TracerProvider::TracerProvider(std::shared_ptr processor) noexcept - : processor_{processor}, tracer_(new Tracer(std::move(processor))) +TracerProvider::TracerProvider(std::shared_ptr processor, + std::shared_ptr sampler) noexcept + : processor_{processor}, tracer_(new Tracer(std::move(processor))), sampler_(sampler) {} opentelemetry::nostd::shared_ptr TracerProvider::GetTracer( nostd::string_view library_name, nostd::string_view library_version) noexcept { - return tracer_; + return opentelemetry::nostd::shared_ptr(tracer_); } void TracerProvider::SetProcessor(std::shared_ptr processor) noexcept @@ -28,6 +29,11 @@ std::shared_ptr TracerProvider::GetProcessor() const noexcept { return processor_.load(); } + +std::shared_ptr TracerProvider::GetSampler() const noexcept +{ + return sampler_; +} } // namespace trace } // namespace sdk OPENTELEMETRY_END_NAMESPACE diff --git a/sdk/test/common/random_test.cc b/sdk/test/common/random_test.cc index b3768c5a79..850e289fff 100644 --- a/sdk/test/common/random_test.cc +++ b/sdk/test/common/random_test.cc @@ -20,7 +20,7 @@ TEST(RandomTest, GenerateRandomBuffer) EXPECT_FALSE(std::equal(std::begin(buf1), std::end(buf1), std::begin(buf2))); // Edge cases. - for (auto size : {1, 7, 8, 9, 16, 17}) + for (auto size : {7, 8, 9, 16, 17}) { std::vector buf1(size); std::vector buf2(size); diff --git a/sdk/test/trace/BUILD b/sdk/test/trace/BUILD index 6748cde17f..a3c3c77f91 100644 --- a/sdk/test/trace/BUILD +++ b/sdk/test/trace/BUILD @@ -41,3 +41,36 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "always_on_sampler_test", + srcs = [ + "always_on_sampler_test.cc", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "always_off_sampler_test", + srcs = [ + "always_off_sampler_test.cc", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "parent_or_else_sampler_test", + srcs = [ + "parent_or_else_sampler_test.cc", + ], + deps = [ + "//sdk/src/trace", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sdk/test/trace/CMakeLists.txt b/sdk/test/trace/CMakeLists.txt index 5bca38440a..b4524da0f2 100644 --- a/sdk/test/trace/CMakeLists.txt +++ b/sdk/test/trace/CMakeLists.txt @@ -1,5 +1,6 @@ foreach(testname tracer_provider_test span_data_test simple_processor_test - tracer_test) + tracer_test always_off_sampler_test always_on_sampler_test + parent_or_else_sampler_test) add_executable(${testname} "${testname}.cc") target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} opentelemetry_trace) diff --git a/sdk/test/trace/always_off_sampler_test.cc b/sdk/test/trace/always_off_sampler_test.cc new file mode 100644 index 0000000000..2c892fcfa7 --- /dev/null +++ b/sdk/test/trace/always_off_sampler_test.cc @@ -0,0 +1,30 @@ +#include "opentelemetry/sdk/trace/samplers/always_off.h" + +#include + +using opentelemetry::sdk::trace::AlwaysOffSampler; +using opentelemetry::sdk::trace::Decision; + +TEST(AlwaysOffSampler, ShouldSample) +{ + AlwaysOffSampler sampler; + + opentelemetry::trace::TraceId trace_id; + opentelemetry::trace::SpanKind span_kind = opentelemetry::trace::SpanKind::kInternal; + + using M = std::map; + M m1 = {{}}; + opentelemetry::trace::KeyValueIterableView view{m1}; + + auto sampling_result = sampler.ShouldSample(nullptr, trace_id, "", span_kind, view); + + ASSERT_EQ(Decision::NOT_RECORD, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); +} + +TEST(AlwaysOffSampler, GetDescription) +{ + AlwaysOffSampler sampler; + + ASSERT_EQ("AlwaysOffSampler", sampler.GetDescription()); +} diff --git a/sdk/test/trace/always_on_sampler_test.cc b/sdk/test/trace/always_on_sampler_test.cc new file mode 100644 index 0000000000..a933cd0073 --- /dev/null +++ b/sdk/test/trace/always_on_sampler_test.cc @@ -0,0 +1,43 @@ +#include "opentelemetry/nostd/span.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" + +#include +#include + +using namespace opentelemetry::sdk::trace; +using namespace opentelemetry::nostd; + +TEST(AlwaysOnSampler, ShouldSample) +{ + AlwaysOnSampler sampler; + + // A buffer of trace_id with no specific meaning + constexpr uint8_t buf[] = {0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}; + + trace_api::TraceId trace_id_invalid; + trace_api::TraceId trace_id_valid(buf); + std::map key_value_container = {{"key", 0}}; + + // Test with invalid (empty) trace id and empty parent context + auto sampling_result = sampler.ShouldSample( + nullptr, trace_id_invalid, "invalid trace id test", trace_api::SpanKind::kServer, + trace_api::KeyValueIterableView>(key_value_container)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); + + // Test with a valid trace id and empty parent context + sampling_result = sampler.ShouldSample( + nullptr, trace_id_valid, "valid trace id test", trace_api::SpanKind::kServer, + trace_api::KeyValueIterableView>(key_value_container)); + + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result.decision); + ASSERT_EQ(nullptr, sampling_result.attributes); +} + +TEST(AlwaysOnSampler, GetDescription) +{ + AlwaysOnSampler sampler; + + ASSERT_EQ("AlwaysOnSampler", sampler.GetDescription()); +} diff --git a/sdk/test/trace/parent_or_else_sampler_test.cc b/sdk/test/trace/parent_or_else_sampler_test.cc new file mode 100644 index 0000000000..93ce56dfb1 --- /dev/null +++ b/sdk/test/trace/parent_or_else_sampler_test.cc @@ -0,0 +1,51 @@ +#include +#include +#include "opentelemetry/sdk/trace/samplers/always_off.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/samplers/parent_or_else.h" + +using opentelemetry::sdk::trace::AlwaysOffSampler; +using opentelemetry::sdk::trace::AlwaysOnSampler; +using opentelemetry::sdk::trace::Decision; +using opentelemetry::sdk::trace::ParentOrElseSampler; +using opentelemetry::trace::SpanContext; + +TEST(ParentOrElseSampler, ShouldSample) +{ + ParentOrElseSampler sampler_off(std::make_shared()); + ParentOrElseSampler sampler_on(std::make_shared()); + + // Set up parameters + opentelemetry::trace::TraceId trace_id; + opentelemetry::trace::SpanKind span_kind = opentelemetry::trace::SpanKind::kInternal; + using M = std::map; + M m1 = {{}}; + opentelemetry::trace::KeyValueIterableView view{m1}; + SpanContext parent_context_sampled(true, false); + SpanContext parent_context_nonsampled(false, false); + + // Case 1: Parent doesn't exist. Return result of delegateSampler() + auto sampling_result = sampler_off.ShouldSample(nullptr, trace_id, "", span_kind, view); + auto sampling_result2 = sampler_on.ShouldSample(nullptr, trace_id, "", span_kind, view); + + ASSERT_EQ(Decision::NOT_RECORD, sampling_result.decision); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result2.decision); + + // Case 2: Parent exists and SampledFlag is true + auto sampling_result3 = + sampler_off.ShouldSample(&parent_context_sampled, trace_id, "", span_kind, view); + ASSERT_EQ(Decision::RECORD_AND_SAMPLE, sampling_result3.decision); + + // Case 3: Parent exists and SampledFlag is false + auto sampling_result4 = + sampler_on.ShouldSample(&parent_context_nonsampled, trace_id, "", span_kind, view); + ASSERT_EQ(Decision::NOT_RECORD, sampling_result4.decision); +} + +TEST(ParentOrElseSampler, GetDescription) +{ + ParentOrElseSampler sampler(std::make_shared()); + ASSERT_EQ("ParentOrElse{AlwaysOffSampler}", sampler.GetDescription()); + ParentOrElseSampler sampler2(std::make_shared()); + ASSERT_EQ("ParentOrElse{AlwaysOnSampler}", sampler2.GetDescription()); +} diff --git a/sdk/test/trace/simple_processor_test.cc b/sdk/test/trace/simple_processor_test.cc index ddeba25fe6..b4819aa0ae 100644 --- a/sdk/test/trace/simple_processor_test.cc +++ b/sdk/test/trace/simple_processor_test.cc @@ -1,4 +1,4 @@ -#include "src/trace/simple_processor.h" +#include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/nostd/span.h" #include "opentelemetry/sdk/trace/span_data.h" @@ -23,7 +23,7 @@ class MockSpanExporter final : public SpanExporter } ExportResult Export( - opentelemetry::nostd::span> &spans) noexcept override + const opentelemetry::nostd::span> &spans) noexcept override { for (auto &span : spans) { diff --git a/sdk/test/trace/span_data_test.cc b/sdk/test/trace/span_data_test.cc index ea8aab446c..7bf6c34ec1 100644 --- a/sdk/test/trace/span_data_test.cc +++ b/sdk/test/trace/span_data_test.cc @@ -1,4 +1,5 @@ #include "opentelemetry/sdk/trace/span_data.h" +#include "opentelemetry/nostd/variant.h" #include "opentelemetry/trace/span_id.h" #include "opentelemetry/trace/trace_id.h" @@ -20,6 +21,7 @@ TEST(SpanData, DefaultValues) ASSERT_EQ(data.GetDescription(), ""); ASSERT_EQ(data.GetStartTime().time_since_epoch(), std::chrono::nanoseconds(0)); ASSERT_EQ(data.GetDuration(), std::chrono::nanoseconds(0)); + ASSERT_EQ(data.GetAttributes().size(), 0); } TEST(SpanData, Set) @@ -35,6 +37,7 @@ TEST(SpanData, Set) data.SetStatus(opentelemetry::trace::CanonicalCode::UNKNOWN, "description"); data.SetStartTime(now); data.SetDuration(std::chrono::nanoseconds(1000000)); + data.SetAttribute("attr1", 314159); data.AddEvent("event1", now); ASSERT_EQ(data.GetTraceId(), trace_id); @@ -45,4 +48,5 @@ TEST(SpanData, Set) ASSERT_EQ(data.GetDescription(), "description"); ASSERT_EQ(data.GetStartTime().time_since_epoch(), now.time_since_epoch()); ASSERT_EQ(data.GetDuration(), std::chrono::nanoseconds(1000000)); + ASSERT_EQ(opentelemetry::nostd::get(data.GetAttributes().at("attr1")), 314159); } diff --git a/sdk/test/trace/tracer_provider_test.cc b/sdk/test/trace/tracer_provider_test.cc index 8168aeb6ba..399ea6d453 100644 --- a/sdk/test/trace/tracer_provider_test.cc +++ b/sdk/test/trace/tracer_provider_test.cc @@ -1,6 +1,8 @@ #include "opentelemetry/sdk/trace/tracer_provider.h" +#include "opentelemetry/sdk/trace/samplers/always_off.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/tracer.h" -#include "src/trace/simple_processor.h" #include @@ -27,3 +29,28 @@ TEST(TracerProvider, GetTracer) ASSERT_NE(nullptr, sdkTracer); ASSERT_EQ(processor, sdkTracer->GetProcessor()); } + +TEST(TracerProvider, GetSampler) +{ + std::shared_ptr processor1(new SimpleSpanProcessor(nullptr)); + + // Create a TracerProvicer with a default AlwaysOnSampler. + TracerProvider tf1(processor1); + auto t1 = tf1.GetSampler(); + auto t2 = tf1.GetSampler(); + ASSERT_NE(nullptr, t1); + ASSERT_NE(nullptr, t2); + + // Should return the same sampler each time. + ASSERT_EQ(t1, t2); + + // Should be AlwaysOnSampler + ASSERT_EQ("AlwaysOnSampler", t2->GetDescription()); + + // Create a TracerProvicer with a custom AlwaysOffSampler. + std::shared_ptr processor2(new SimpleSpanProcessor(nullptr)); + TracerProvider tf2(processor2, std::make_shared()); + auto t3 = tf2.GetSampler(); + + ASSERT_EQ("AlwaysOffSampler", t3->GetDescription()); +} diff --git a/sdk/test/trace/tracer_test.cc b/sdk/test/trace/tracer_test.cc index 99a18ba99d..75011db536 100644 --- a/sdk/test/trace/tracer_test.cc +++ b/sdk/test/trace/tracer_test.cc @@ -1,10 +1,16 @@ #include "opentelemetry/sdk/trace/tracer.h" +#include "opentelemetry/sdk/trace/samplers/always_off.h" +#include "opentelemetry/sdk/trace/samplers/always_on.h" +#include "opentelemetry/sdk/trace/simple_processor.h" #include "opentelemetry/sdk/trace/span_data.h" -#include "src/trace/simple_processor.h" #include using namespace opentelemetry::sdk::trace; +using opentelemetry::core::SteadyTimestamp; +using opentelemetry::core::SystemTimestamp; +namespace nostd = opentelemetry::nostd; +namespace common = opentelemetry::common; /** * A mock exporter that switches a flag once a valid recordable was received. @@ -12,7 +18,7 @@ using namespace opentelemetry::sdk::trace; class MockSpanExporter final : public SpanExporter { public: - MockSpanExporter(std::shared_ptr> spans_received) noexcept + MockSpanExporter(std::shared_ptr>> spans_received) noexcept : spans_received_(spans_received) {} @@ -21,15 +27,14 @@ class MockSpanExporter final : public SpanExporter return std::unique_ptr(new SpanData); } - ExportResult Export( - opentelemetry::nostd::span> &recordables) noexcept override + ExportResult Export(const nostd::span> &recordables) noexcept override { for (auto &recordable : recordables) { auto span = std::unique_ptr(static_cast(recordable.release())); if (span != nullptr) { - spans_received_->push_back(std::string(span->GetName())); + spans_received_->push_back(std::move(span)); } } @@ -40,15 +45,25 @@ class MockSpanExporter final : public SpanExporter {} private: - std::shared_ptr> spans_received_; + std::shared_ptr>> spans_received_; }; -TEST(Tracer, ToMockSpanExporter) +namespace +{ +std::shared_ptr initTracer( + std::shared_ptr>> &received) { - std::shared_ptr> spans_received(new std::vector); - std::unique_ptr exporter(new MockSpanExporter(spans_received)); + std::unique_ptr exporter(new MockSpanExporter(received)); std::shared_ptr processor(new SimpleSpanProcessor(std::move(exporter))); - std::shared_ptr tracer(new Tracer(processor)); + return std::shared_ptr(new Tracer(processor)); +} +} // namespace + +TEST(Tracer, ToMockSpanExporter) +{ + std::shared_ptr>> spans_received( + new std::vector>); + auto tracer = initTracer(spans_received); auto span_first = tracer->StartSpan("span 1"); auto span_second = tracer->StartSpan("span 2"); @@ -57,9 +72,106 @@ TEST(Tracer, ToMockSpanExporter) span_second->End(); ASSERT_EQ(1, spans_received->size()); - ASSERT_EQ("span 2", spans_received->at(0)); + ASSERT_EQ("span 2", spans_received->at(0)->GetName()); span_first->End(); ASSERT_EQ(2, spans_received->size()); - ASSERT_EQ("span 1", spans_received->at(1)); + ASSERT_EQ("span 1", spans_received->at(1)->GetName()); +} + +TEST(Tracer, StartSpan) +{ + std::shared_ptr>> spans_received( + new std::vector>); + auto tracer = initTracer(spans_received); + + tracer->StartSpan("span 1")->End(); + + ASSERT_EQ(1, spans_received->size()); + + auto &span_data = spans_received->at(0); + ASSERT_LT(std::chrono::nanoseconds(0), span_data->GetStartTime().time_since_epoch()); + ASSERT_LT(std::chrono::nanoseconds(0), span_data->GetDuration()); +} + +TEST(Tracer, StartSpanWithOptionsTime) +{ + std::shared_ptr>> spans_received( + new std::vector>); + auto tracer = initTracer(spans_received); + + opentelemetry::trace::StartSpanOptions start; + start.start_system_time = SystemTimestamp(std::chrono::nanoseconds(300)); + start.start_steady_time = SteadyTimestamp(std::chrono::nanoseconds(10)); + + opentelemetry::trace::EndSpanOptions end; + end.end_steady_time = SteadyTimestamp(std::chrono::nanoseconds(40)); + + tracer->StartSpan("span 1", start)->End(end); + + ASSERT_EQ(1, spans_received->size()); + + auto &span_data = spans_received->at(0); + ASSERT_EQ(std::chrono::nanoseconds(300), span_data->GetStartTime().time_since_epoch()); + ASSERT_EQ(std::chrono::nanoseconds(30), span_data->GetDuration()); +} + +TEST(Tracer, StartSpanWithAttributes) +{ + std::shared_ptr>> spans_received( + new std::vector>); + auto tracer = initTracer(spans_received); + + { + tracer->StartSpan("span 1", {{"attr1", 314159}, {"attr2", false}, {"attr1", "string"}}); + + std::map m; + m["attr3"] = 3.0; + tracer->StartSpan("span 2", m); + } + + ASSERT_EQ(2, spans_received->size()); + + auto &span_data = spans_received->at(0); + ASSERT_EQ(2, span_data->GetAttributes().size()); + ASSERT_EQ("string", nostd::get(span_data->GetAttributes().at("attr1"))); + ASSERT_EQ(false, nostd::get(span_data->GetAttributes().at("attr2"))); + + auto &span_data2 = spans_received->at(1); + ASSERT_EQ(1, span_data2->GetAttributes().size()); + ASSERT_EQ(3.0, nostd::get(span_data2->GetAttributes().at("attr3"))); +} + +TEST(Tracer, GetSampler) +{ + // Create a Tracer with a default AlwaysOnSampler + std::shared_ptr processor_1(new SimpleSpanProcessor(nullptr)); + std::shared_ptr tracer_on(new Tracer(std::move(processor_1))); + + auto t1 = tracer_on->GetSampler(); + ASSERT_EQ("AlwaysOnSampler", t1->GetDescription()); + + // Create a Tracer with a AlwaysOffSampler + std::shared_ptr processor_2(new SimpleSpanProcessor(nullptr)); + std::shared_ptr tracer_off( + new Tracer(std::move(processor_2), std::make_shared())); + + auto t2 = tracer_off->GetSampler(); + ASSERT_EQ("AlwaysOffSampler", t2->GetDescription()); +} + +TEST(Tracer, SpanSetAttribute) +{ + std::shared_ptr>> spans_received( + new std::vector>); + auto tracer = initTracer(spans_received); + + auto span = tracer->StartSpan("span 1"); + + span->SetAttribute("abc", 3.1); + + span->End(); + ASSERT_EQ(1, spans_received->size()); + auto &span_data = spans_received->at(0); + ASSERT_EQ(3.1, nostd::get(span_data->GetAttributes().at("abc"))); } diff --git a/third_party/opentelemetry-proto/README b/third_party/opentelemetry-proto/README index dfd3a68e21..e32d7de183 100644 --- a/third_party/opentelemetry-proto/README +++ b/third_party/opentelemetry-proto/README @@ -1,2 +1,2 @@ From: https://github.com/open-telemetry/opentelemetry-proto -Commit: d496c80b353bc4a4f754ae686b59ca3c41de0946 +Commit: e43e1abc40428a6ee98e3bfd79bec1dfa2ed18cd diff --git a/third_party/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service_http.yaml b/third_party/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service_http.yaml new file mode 100644 index 0000000000..bc5f9ff241 --- /dev/null +++ b/third_party/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service_http.yaml @@ -0,0 +1,9 @@ +# This is an API configuration to generate an HTTP/JSON -> gRPC gateway for the +# OpenTelemetry service using github.com/grpc-ecosystem/grpc-gateway. +type: google.api.Service +config_version: 3 +http: + rules: + - selector: opentelemetry.proto.collector.metrics.v1.MetricsService.Export + post: /v1/metrics + body: "*" diff --git a/third_party/opentelemetry-proto/opentelemetry/proto/collector/trace/v1/trace_service_http.yaml b/third_party/opentelemetry-proto/opentelemetry/proto/collector/trace/v1/trace_service_http.yaml index 7754e5ff12..10eae48d51 100644 --- a/third_party/opentelemetry-proto/opentelemetry/proto/collector/trace/v1/trace_service_http.yaml +++ b/third_party/opentelemetry-proto/opentelemetry/proto/collector/trace/v1/trace_service_http.yaml @@ -7,6 +7,3 @@ http: - selector: opentelemetry.proto.collector.trace.v1.TraceService.Export post: /v1/trace body: "*" - - selector: opentelemetry.proto.collector.metrics.v1.MetricsService.Export - post: /v1/trace - body: "*" \ No newline at end of file diff --git a/third_party/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto b/third_party/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto index b3b1852459..dc67e43fb6 100644 --- a/third_party/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto +++ b/third_party/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto @@ -21,35 +21,57 @@ option java_package = "io.opentelemetry.proto.common.v1"; option java_outer_classname = "CommonProto"; option go_package = "github.com/open-telemetry/opentelemetry-proto/gen/go/common/v1"; -// AttributeKeyValue is a key-value pair that is used to store Span attributes, Link -// attributes, etc. -message AttributeKeyValue { - // ValueType is the enumeration of possible types that value can have. - enum ValueType { - STRING = 0; - INT = 1; - DOUBLE = 2; - BOOL = 3; - }; - - // key part of the key-value pair. - string key = 1; +// AnyValue is used to represent any type of attribute value. AnyValue may contain a +// primitive value such as a string or integer or it may contain an arbitrary nested +// object containing arrays, key-value lists and primitives. +message AnyValue { + // The value is one of the listed fields. It is valid for all values to be unspecified + // in which case this AnyValue is considered to be "null". + oneof value { + string string_value = 1; + bool bool_value = 2; + int64 int_value = 3; + double double_value = 4; + ArrayValue array_value = 5; + KeyValueList kvlist_value = 6; + } +} - // type of the value. - ValueType type = 2; +// ArrayValue is a list of AnyValue messages. We need ArrayValue as a message +// since oneof in AnyValue does not allow repeated fields. +message ArrayValue { + // Array of values. The array may be empty (contain 0 elements). + repeated AnyValue values = 1; +} - // Only one of the following fields is supposed to contain data (determined by `type` field). - // This is deliberately not using Protobuf `oneof` for performance reasons (verified by benchmarks). +// KeyValueList is a list of KeyValue messages. We need KeyValueList as a message +// since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need +// a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to +// avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches +// are semantically equivalent. +message KeyValueList { + // A collection of key/value pairs of key-value pairs. The list may be empty (may + // contain 0 elements). + repeated KeyValue values = 1; +} - string string_value = 3; - int64 int_value = 4; - double double_value = 5; - bool bool_value = 6; +// KeyValue is a key-value pair that is used to store Span attributes, Link +// attributes, etc. +message KeyValue { + string key = 1; + AnyValue value = 2; } // StringKeyValue is a pair of key/value strings. This is the simpler (and faster) version -// of AttributeKeyValue that only supports string values. +// of KeyValue that only supports string values. message StringKeyValue { string key = 1; string value = 2; } + +// InstrumentationLibrary is a message representing the instrumentation library information +// such as the fully qualified name and version. +message InstrumentationLibrary { + string name = 1; + string version = 2; +} diff --git a/third_party/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto b/third_party/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto index a15a7b6c2b..1d458824fe 100644 --- a/third_party/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto +++ b/third_party/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto @@ -24,14 +24,24 @@ option java_package = "io.opentelemetry.proto.metrics.v1"; option java_outer_classname = "MetricsProto"; option go_package = "github.com/open-telemetry/opentelemetry-proto/gen/go/metrics/v1"; -// A collection of metrics from a Resource. +// A collection of InstrumentationLibraryMetrics from a Resource. message ResourceMetrics { - // A list of metrics that originate from a resource. - repeated Metric metrics = 1; - // The resource for the metrics in this message. // If this field is not set then no resource info is known. - opentelemetry.proto.resource.v1.Resource resource = 2; + opentelemetry.proto.resource.v1.Resource resource = 1; + + // A list of metrics that originate from a resource. + repeated InstrumentationLibraryMetrics instrumentation_library_metrics = 2; +} + +// A collection of Metrics produced by an InstrumentationLibrary. +message InstrumentationLibraryMetrics { + // The instrumentation library information for the metrics in this message. + // If this field is not set then no library info is known. + opentelemetry.proto.common.v1.InstrumentationLibrary instrumentation_library = 1; + + // A list of metrics that originate from an instrumentation library. + repeated Metric metrics = 2; } // Defines a Metric which has one or more timeseries. @@ -90,10 +100,10 @@ message Metric { // Data is a list of one or more DataPoints for a single metric. Only one of the // following fields is used for the data, depending on the type of the metric defined // by MetricDescriptor.type field. - repeated Int64DataPoint int64_datapoints = 2; - repeated DoubleDataPoint double_datapoints = 3; - repeated HistogramDataPoint histogram_datapoints = 4; - repeated SummaryDataPoint summary_datapoints = 5; + repeated Int64DataPoint int64_data_points = 2; + repeated DoubleDataPoint double_data_points = 3; + repeated HistogramDataPoint histogram_data_points = 4; + repeated SummaryDataPoint summary_data_points = 5; } // Defines a metric type and its schema. @@ -108,49 +118,36 @@ message MetricDescriptor { // described by http://unitsofmeasure.org/ucum.html. string unit = 3; - // Type of the metric. It describes how the data is reported. - // - // A gauge is an instantaneous measurement of a value. - // - // A counter/cumulative measurement is a value accumulated over a time - // interval. In a time series, cumulative measurements should have the same - // start time, increasing values, until an event resets the cumulative value - // to zero and sets a new start time for the subsequent points. + // Type is the type of values a metric has. enum Type { - // Do not use this default value. - UNSPECIFIED = 0; - - // Integer gauge. The value can go both up and down over time. - // Corresponding values are stored in Int64DataPoint. - GAUGE_INT64 = 1; - - // Floating point gauge. The value can go both up and down over time. - // Corresponding values are stored in DoubleDataPoint. - GAUGE_DOUBLE = 2; - - // Histogram gauge measurement. - // Used in scenarios like a snapshot of time that current items in a queue - // have spent there. - // Corresponding values are stored in HistogramDataPoint. The count and sum of the - // histogram can go both up and down over time. Recorded values are always >= 0. - GAUGE_HISTOGRAM = 3; - - // Integer counter measurement. The value cannot decrease; if value is reset then - // start_time_unixnano should also be reset. - // Corresponding values are stored in Int64DataPoint. - COUNTER_INT64 = 4; - - // Floating point counter measurement. The value cannot decrease, if - // resets then the start_time_unixnano should also be reset. - // Recorded values are always >= 0. - // Corresponding values are stored in DoubleDataPoint. - COUNTER_DOUBLE = 5; - - // Histogram cumulative measurement. - // Corresponding values are stored in HistogramDataPoint. The count and sum of the - // histogram cannot decrease; if values are reset then start_time_unixnano - // should also be reset to the new start timestamp. - CUMULATIVE_HISTOGRAM = 6; + // INVALID_TYPE is the default Type, it MUST not be used. + INVALID_TYPE = 0; + + // INT64 values are signed 64-bit integers. + // + // A Metric of this Type MUST store its values as Int64DataPoint. + INT64 = 1; + + // MONOTONIC_INT64 values are monotonically increasing signed 64-bit + // integers. + // + // A Metric of this Type MUST store its values as Int64DataPoint. + MONOTONIC_INT64 = 2; + + // DOUBLE values are double-precision floating-point numbers. + // + // A Metric of this Type MUST store its values as DoubleDataPoint. + DOUBLE = 3; + + // MONOTONIC_DOUBLE values are monotonically increasing double-precision + // floating-point numbers. + // + // A Metric of this Type MUST store its values as DoubleDataPoint. + MONOTONIC_DOUBLE = 4; + + // Histogram measurement. + // Corresponding values are stored in HistogramDataPoint. + HISTOGRAM = 5; // Summary value. Some frameworks implemented Histograms as a summary of observations // (usually things like request durations and response sizes). While it @@ -158,13 +155,88 @@ message MetricDescriptor { // values, it calculates configurable percentiles over a sliding time // window. // Corresponding values are stored in SummaryDataPoint. - SUMMARY = 7; + SUMMARY = 6; } + + // type is the type of values this metric has. Type type = 4; - // The set of labels associated with the metric descriptor. Labels in this list apply to - // all data points. - repeated opentelemetry.proto.common.v1.StringKeyValue labels = 5; + // Temporality is the temporal quality values of a metric have. It + // describes how those values relate to the time interval over which they + // are reported. + enum Temporality { + // INVALID_TEMPORALITY is the default Temporality, it MUST not be + // used. + INVALID_TEMPORALITY = 0; + + // INSTANTANEOUS is a metric whose values are measured at a particular + // instant. The values are not aggregated over any time interval and are + // unique per timestamp. As such, these metrics are not expected to have + // an associated start time. + INSTANTANEOUS = 1; + + // DELTA is a metric whose values are the aggregation of measurements + // made over a time interval. Successive metrics contain aggregation of + // values from continuous and non-overlapping intervals. + // + // The values for a DELTA metric are based only on the time interval + // associated with one measurement cycle. There is no dependency on + // previous measurements like is the case for CUMULATIVE metrics. + // + // For example, consider a system measuring the number of requests that + // it receives and reports the sum of these requests every second as a + // DELTA metric: + // + // 1. The system starts receiving at time=t_0. + // 2. A request is received, the system measures 1 request. + // 3. A request is received, the system measures 1 request. + // 4. A request is received, the system measures 1 request. + // 5. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_0 to + // t_0+1 with a value of 3. + // 6. A request is received, the system measures 1 request. + // 7. A request is received, the system measures 1 request. + // 8. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_0+1 to + // t_0+2 with a value of 2. + DELTA = 2; + + // CUMULATIVE is a metric whose values are the aggregation of + // successively made measurements from a fixed start time until the last + // reported measurement. This means that current values of a CUMULATIVE + // metric depend on all previous measurements since the start time. + // Because of this, the sender is required to retain this state in some + // form. If this state is lost or invalidated, the CUMULATIVE metric + // values MUST be reset and a new fixed start time following the last + // reported measurement time sent MUST be used. + // + // For example, consider a system measuring the number of requests that + // it receives and reports the sum of these requests every second as a + // CUMULATIVE metric: + // + // 1. The system starts receiving at time=t_0. + // 2. A request is received, the system measures 1 request. + // 3. A request is received, the system measures 1 request. + // 4. A request is received, the system measures 1 request. + // 5. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_0 to + // t_0+1 with a value of 3. + // 6. A request is received, the system measures 1 request. + // 7. A request is received, the system measures 1 request. + // 8. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_0 to + // t_0+2 with a value of 5. + // 9. The system experiences a fault and loses state. + // 10. The system recovers and resumes receiving at time=t_1. + // 11. A request is received, the system measures 1 request. + // 12. The 1 second collection cycle ends. A metric is exported for the + // number of requests received over the interval of time t_1 to + // t_0+1 with a value of 1. + CUMULATIVE = 3; + } + + // temporality is the Temporality of values this metric has. + Temporality temporality = 5; } // Int64DataPoint is a single data point in a timeseries that describes the time-varying @@ -173,20 +245,20 @@ message Int64DataPoint { // The set of labels that uniquely identify this timeseries. repeated opentelemetry.proto.common.v1.StringKeyValue labels = 1; - // start_time_unixnano is the time when the cumulative value was reset to zero. + // start_time_unix_nano is the time when the cumulative value was reset to zero. // This is used for Counter type only. For Gauge the value is not specified and // defaults to 0. // - // The cumulative value is over the time interval (start_time_unixnano, timestamp_unixnano]. + // The cumulative value is over the time interval (start_time_unix_nano, time_unix_nano]. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // Value of 0 indicates that the timestamp is unspecified. In that case the timestamp // may be decided by the backend. - fixed64 start_time_unixnano = 2; + fixed64 start_time_unix_nano = 2; - // timestamp_unixnano is the moment when this value was recorded. + // time_unix_nano is the moment when this value was recorded. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - fixed64 timestamp_unixnano = 3; + fixed64 time_unix_nano = 3; // value itself. int64 value = 4; @@ -198,20 +270,20 @@ message DoubleDataPoint { // The set of labels that uniquely identify this timeseries. repeated opentelemetry.proto.common.v1.StringKeyValue labels = 1; - // start_time_unixnano is the time when the cumulative value was reset to zero. + // start_time_unix_nano is the time when the cumulative value was reset to zero. // This is used for Counter type only. For Gauge the value is not specified and // defaults to 0. // - // The cumulative value is over the time interval (start_time_unixnano, timestamp_unixnano]. + // The cumulative value is over the time interval (start_time_unix_nano, time_unix_nano]. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // Value of 0 indicates that the timestamp is unspecified. In that case the timestamp // may be decided by the backend. - fixed64 start_time_unixnano = 2; + fixed64 start_time_unix_nano = 2; - // timestamp_unixnano is the moment when this value was recorded. + // time_unix_nano is the moment when this value was recorded. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - fixed64 timestamp_unixnano = 3; + fixed64 time_unix_nano = 3; // value itself. double value = 4; @@ -224,19 +296,19 @@ message HistogramDataPoint { // The set of labels that uniquely identify this timeseries. repeated opentelemetry.proto.common.v1.StringKeyValue labels = 1; - // start_time_unixnano is the time when the cumulative value was reset to zero. + // start_time_unix_nano is the time when the cumulative value was reset to zero. // - // The cumulative value is over the time interval (start_time_unixnano, timestamp_unixnano]. + // The cumulative value is over the time interval (start_time_unix_nano, time_unix_nano]. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // Value of 0 indicates that the timestamp is unspecified. In that case the timestamp // may be decided by the backend. // Note: this field is always unspecified and ignored if MetricDescriptor.type==GAUGE_HISTOGRAM. - fixed64 start_time_unixnano = 2; + fixed64 start_time_unix_nano = 2; - // timestamp_unixnano is the moment when this value was recorded. + // time_unix_nano is the moment when this value was recorded. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - fixed64 timestamp_unixnano = 3; + fixed64 time_unix_nano = 3; // count is the number of values in the population. Must be non-negative. This value // must be equal to the sum of the "count" fields in buckets if a histogram is provided. @@ -262,9 +334,9 @@ message HistogramDataPoint { // the defined bounds. double value = 1; - // timestamp_unixnano is the moment when this exemplar was recorded. + // time_unix_nano is the moment when this exemplar was recorded. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - fixed64 timestamp_unixnano = 2; + fixed64 time_unix_nano = 2; // exemplar_attachments are contextual information about the example value. // Keys in this list must be unique. @@ -318,18 +390,18 @@ message SummaryDataPoint { // The set of labels that uniquely identify this timeseries. repeated opentelemetry.proto.common.v1.StringKeyValue labels = 1; - // start_time_unixnano is the time when the cumulative value was reset to zero. + // start_time_unix_nano is the time when the cumulative value was reset to zero. // - // The cumulative value is over the time interval (start_time_unixnano, timestamp_unixnano]. + // The cumulative value is over the time interval (start_time_unix_nano, time_unix_nano]. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // Value of 0 indicates that the timestamp is unspecified. In that case the timestamp // may be decided by the backend. - fixed64 start_time_unixnano = 2; + fixed64 start_time_unix_nano = 2; - // timestamp_unixnano is the moment when this value was recorded. + // time_unix_nano is the moment when this value was recorded. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. - fixed64 timestamp_unixnano = 3; + fixed64 time_unix_nano = 3; // The total number of recorded values since start_time. Optional since // some systems don't expose this. @@ -340,6 +412,13 @@ message SummaryDataPoint { double sum = 5; // Represents the value at a given percentile of a distribution. + // + // To record Min and Max values following conventions are used: + // - The 100th percentile is equivalent to the maximum value observed. + // - The 0th percentile is equivalent to the minimum value observed. + // + // See the following issue for more context: + // https://github.com/open-telemetry/opentelemetry-proto/issues/125 message ValueAtPercentile { // The percentile of a distribution. Must be in the interval // [0.0, 100.0]. diff --git a/third_party/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto b/third_party/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto index a9e1711af4..fa5d97c6f8 100644 --- a/third_party/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto +++ b/third_party/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto @@ -26,7 +26,7 @@ option go_package = "github.com/open-telemetry/opentelemetry-proto/gen/go/resour // Resource information. message Resource { // Set of labels that describe the resource. - repeated opentelemetry.proto.common.v1.AttributeKeyValue attributes = 1; + repeated opentelemetry.proto.common.v1.KeyValue attributes = 1; // dropped_attributes_count is the number of dropped attributes. If the value is 0, then // no attributes were dropped. diff --git a/third_party/opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto b/third_party/opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto index 7f0e4a75c0..5a2350fdee 100644 --- a/third_party/opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto +++ b/third_party/opentelemetry-proto/opentelemetry/proto/trace/v1/trace.proto @@ -24,13 +24,23 @@ option java_package = "io.opentelemetry.proto.trace.v1"; option java_outer_classname = "TraceProto"; option go_package = "github.com/open-telemetry/opentelemetry-proto/gen/go/trace/v1"; -// A collection of spans from a Resource. +// A collection of InstrumentationLibrarySpans from a Resource. message ResourceSpans { // The resource for the spans in this message. // If this field is not set then no resource info is known. opentelemetry.proto.resource.v1.Resource resource = 1; - // A list of Spans that originate from a resource. + // A list of InstrumentationLibrarySpans that originate from a resource. + repeated InstrumentationLibrarySpans instrumentation_library_spans = 2; +} + +// A collection of Spans produced by an InstrumentationLibrary. +message InstrumentationLibrarySpans { + // The instrumentation library information for the spans in this message. + // If this field is not set then no library info is known. + opentelemetry.proto.common.v1.InstrumentationLibrary instrumentation_library = 1; + + // A list of Spans that originate from an instrumentation library. repeated Span spans = 2; } @@ -123,21 +133,21 @@ message Span { // and `SERVER` (callee) to identify queueing latency associated with the span. SpanKind kind = 6; - // start_time_unixnano is the start time of the span. On the client side, this is the time + // start_time_unix_nano is the start time of the span. On the client side, this is the time // kept by the local machine where the span execution starts. On the server side, this // is the time when the server's application handler starts running. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // This field is semantically required and it is expected that end_time >= start_time. - fixed64 start_time_unixnano = 7; + fixed64 start_time_unix_nano = 7; - // end_time_unixnano is the end time of the span. On the client side, this is the time + // end_time_unix_nano is the end time of the span. On the client side, this is the time // kept by the local machine where the span execution ends. On the server side, this // is the time when the server application handler stops running. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // This field is semantically required and it is expected that end_time >= start_time. - fixed64 end_time_unixnano = 8; + fixed64 end_time_unix_nano = 8; // attributes is a collection of key/value pairs. The value can be a string, // an integer, a double or the Boolean values `true` or `false`. Note, global attributes @@ -147,7 +157,7 @@ message Span { // "/http/server_latency": 300 // "abc.com/myattribute": true // "abc.com/score": 10.239 - repeated opentelemetry.proto.common.v1.AttributeKeyValue attributes = 9; + repeated opentelemetry.proto.common.v1.KeyValue attributes = 9; // dropped_attributes_count is the number of attributes that were discarded. Attributes // can be discarded because their keys are too long or because there are too many @@ -157,15 +167,15 @@ message Span { // Event is a time-stamped annotation of the span, consisting of user-supplied // text description and key-value pairs. message Event { - // time_unixnano is the time the event occurred. - fixed64 time_unixnano = 1; + // time_unix_nano is the time the event occurred. + fixed64 time_unix_nano = 1; // name of the event. // This field is semantically required to be set to non-empty string. string name = 2; // attributes is a collection of attribute key/value pairs on the event. - repeated opentelemetry.proto.common.v1.AttributeKeyValue attributes = 3; + repeated opentelemetry.proto.common.v1.KeyValue attributes = 3; // dropped_attributes_count is the number of dropped attributes. If the value is 0, // then no attributes were dropped. @@ -195,7 +205,7 @@ message Span { string trace_state = 3; // attributes is a collection of attribute key/value pairs on the link. - repeated opentelemetry.proto.common.v1.AttributeKeyValue attributes = 4; + repeated opentelemetry.proto.common.v1.KeyValue attributes = 4; // dropped_attributes_count is the number of dropped attributes. If the value is 0, // then no attributes were dropped.