mirror of
https://github.com/Icinga/icinga2.git
synced 2026-05-28 04:12:13 -04:00
Merge pull request #10685 from Icinga/otel
Some checks failed
Container Image / Container Image (push) Has been cancelled
Linux / alpine:bash (push) Has been cancelled
Linux / amazonlinux:2 (push) Has been cancelled
Linux / amazonlinux:2023 (push) Has been cancelled
Linux / debian:11 (linux/386) (push) Has been cancelled
Linux / debian:11 (push) Has been cancelled
Linux / debian:12 (linux/386) (push) Has been cancelled
Linux / debian:12 (push) Has been cancelled
Linux / debian:13 (push) Has been cancelled
Linux / fedora:41 (push) Has been cancelled
Linux / fedora:42 (push) Has been cancelled
Linux / fedora:43 (push) Has been cancelled
Linux / opensuse/leap:15.6 (push) Has been cancelled
Linux / opensuse/leap:16.0 (push) Has been cancelled
Linux / registry.suse.com/bci/bci-base:16.0 (push) Has been cancelled
Linux / registry.suse.com/suse/sle15:15.6 (push) Has been cancelled
Linux / registry.suse.com/suse/sle15:15.7 (push) Has been cancelled
Linux / rockylinux/rockylinux:10 (push) Has been cancelled
Linux / rockylinux:8 (push) Has been cancelled
Linux / rockylinux:9 (push) Has been cancelled
Linux / ubuntu:22.04 (push) Has been cancelled
Linux / ubuntu:24.04 (push) Has been cancelled
Linux / ubuntu:25.04 (push) Has been cancelled
Linux / ubuntu:25.10 (push) Has been cancelled
Windows / Windows (push) Has been cancelled
Some checks failed
Container Image / Container Image (push) Has been cancelled
Linux / alpine:bash (push) Has been cancelled
Linux / amazonlinux:2 (push) Has been cancelled
Linux / amazonlinux:2023 (push) Has been cancelled
Linux / debian:11 (linux/386) (push) Has been cancelled
Linux / debian:11 (push) Has been cancelled
Linux / debian:12 (linux/386) (push) Has been cancelled
Linux / debian:12 (push) Has been cancelled
Linux / debian:13 (push) Has been cancelled
Linux / fedora:41 (push) Has been cancelled
Linux / fedora:42 (push) Has been cancelled
Linux / fedora:43 (push) Has been cancelled
Linux / opensuse/leap:15.6 (push) Has been cancelled
Linux / opensuse/leap:16.0 (push) Has been cancelled
Linux / registry.suse.com/bci/bci-base:16.0 (push) Has been cancelled
Linux / registry.suse.com/suse/sle15:15.6 (push) Has been cancelled
Linux / registry.suse.com/suse/sle15:15.7 (push) Has been cancelled
Linux / rockylinux/rockylinux:10 (push) Has been cancelled
Linux / rockylinux:8 (push) Has been cancelled
Linux / rockylinux:9 (push) Has been cancelled
Linux / ubuntu:22.04 (push) Has been cancelled
Linux / ubuntu:24.04 (push) Has been cancelled
Linux / ubuntu:25.04 (push) Has been cancelled
Linux / ubuntu:25.10 (push) Has been cancelled
Windows / Windows (push) Has been cancelled
Add `OTLPMetricsWriter`
This commit is contained in:
commit
492ee8d632
29 changed files with 4040 additions and 8 deletions
51
.github/workflows/linux.bash
vendored
51
.github/workflows/linux.bash
vendored
|
|
@ -5,6 +5,7 @@ export PATH="/usr/lib/ccache/bin:/usr/lib/ccache:/usr/lib64/ccache:$PATH"
|
|||
export CCACHE_DIR=/icinga2/ccache
|
||||
export CTEST_OUTPUT_ON_FAILURE=1
|
||||
CMAKE_OPTS=()
|
||||
SCL_ENABLE_GCC=()
|
||||
# -Wstringop-overflow is notorious for false positives and has been a problem for years.
|
||||
# See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88443
|
||||
# -Wtemplate-id-cdtor leaks from using the generated headers. We should reenable this once
|
||||
|
|
@ -17,7 +18,7 @@ case "$DISTRO" in
|
|||
# - LibreSSL instead of OpenSSL 3 and
|
||||
# - no MariaDB or libpq as they depend on OpenSSL.
|
||||
# https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/community/icinga2/APKBUILD
|
||||
apk add bison boost-dev ccache cmake flex g++ libedit-dev libressl-dev ninja-build tzdata
|
||||
apk add bison boost-dev ccache cmake flex g++ libedit-dev libressl-dev ninja-build tzdata protobuf-dev
|
||||
ln -vs /usr/lib/ninja-build/bin/ninja /usr/local/bin/ninja
|
||||
;;
|
||||
|
||||
|
|
@ -44,24 +45,24 @@ case "$DISTRO" in
|
|||
|
||||
amazonlinux:20*)
|
||||
dnf install -y amazon-rpm-config bison cmake flex gcc-c++ ninja-build \
|
||||
{boost,libedit,mariadb-connector-c,ncurses,openssl,postgresql,systemd}-devel
|
||||
{boost,libedit,mariadb-connector-c,ncurses,openssl,postgresql,systemd,protobuf-lite}-devel
|
||||
;;
|
||||
|
||||
debian:*|ubuntu:*)
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-{recommends,suggests} -y \
|
||||
bison ccache cmake dpkg-dev flex g++ ninja-build tzdata \
|
||||
lib{boost-all,edit,mariadb,ncurses,pq,ssl,systemd}-dev
|
||||
bison ccache cmake dpkg-dev flex g++ ninja-build tzdata protobuf-compiler \
|
||||
lib{boost-all,edit,mariadb,ncurses,pq,ssl,systemd,protobuf}-dev
|
||||
;;
|
||||
|
||||
fedora:*)
|
||||
dnf install -y bison ccache cmake flex gcc-c++ ninja-build redhat-rpm-config \
|
||||
{boost,libedit,mariadb,ncurses,openssl,postgresql,systemd}-devel
|
||||
{boost,libedit,mariadb,ncurses,openssl,postgresql,systemd,protobuf-lite}-devel
|
||||
;;
|
||||
|
||||
*suse*)
|
||||
zypper in -y bison ccache cmake flex gcc-c++ ninja rpm-config-SUSE \
|
||||
{lib{edit,mariadb,openssl},ncurses,postgresql,systemd}-devel \
|
||||
{lib{edit,mariadb,openssl},ncurses,postgresql,systemd,protobuf}-devel \
|
||||
libboost_{context,coroutine,filesystem,iostreams,program_options,regex,system,test,thread}-devel
|
||||
;;
|
||||
|
||||
|
|
@ -71,6 +72,10 @@ case "$DISTRO" in
|
|||
case "$DISTRO" in
|
||||
*:8)
|
||||
dnf config-manager --enable powertools
|
||||
# Our Protobuf package on RHEL 8 is built with GCC 13, and since the ABI is not compatible with GCC 8,
|
||||
# we need to enable the SCL repository and install the GCC 13 packages to be able to link against it.
|
||||
SCL_ENABLE_GCC=(scl enable gcc-toolset-13 --)
|
||||
dnf install -y gcc-toolset-13-gcc-c++ gcc-toolset-13-annobin-plugin-gcc
|
||||
;;
|
||||
*)
|
||||
dnf config-manager --enable crb
|
||||
|
|
@ -79,6 +84,27 @@ case "$DISTRO" in
|
|||
|
||||
dnf install -y bison ccache cmake gcc-c++ flex ninja-build redhat-rpm-config \
|
||||
{boost,bzip2,libedit,mariadb,ncurses,openssl,postgresql,systemd,xz,libzstd}-devel
|
||||
|
||||
# Rocky Linux 8 and 9 don't have a recent enough Protobuf compiler for OTel, so we need to add
|
||||
# our repository to install the pre-built Protobuf devel package.
|
||||
case "$DISTRO" in
|
||||
*:[8-9])
|
||||
rpm --import https://packages.icinga.com/icinga.key
|
||||
cat > /etc/yum.repos.d/icinga-build-deps.repo <<'EOF'
|
||||
[icinga-build-deps]
|
||||
name=Icinga Build Dependencies
|
||||
baseurl=https://packages.icinga.com/build-dependencies/rhel/$releasever/release
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
gpgkey=https://packages.icinga.com/icinga.key
|
||||
EOF
|
||||
dnf install -y icinga-protobuf
|
||||
# Tell CMake where to find our own Protobuf CMake config files.
|
||||
CMAKE_OPTS+=(-DCMAKE_PREFIX_PATH="$(rpm -E '%{_libdir}')/icinga-protobuf/cmake")
|
||||
;;
|
||||
*)
|
||||
dnf install -y protobuf-lite-devel
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
|
|
@ -96,8 +122,19 @@ case "$DISTRO" in
|
|||
source <(dpkg-buildflags --export=sh)
|
||||
export CFLAGS="${CFLAGS} ${WARN_FLAGS}"
|
||||
export CXXFLAGS="${CXXFLAGS} ${WARN_FLAGS}"
|
||||
|
||||
# The default Protobuf compiler is too old for OTel, so we need to turn it off on Debian 11 and Ubuntu 22.04.
|
||||
case "$DISTRO" in
|
||||
debian:11|ubuntu:22.04)
|
||||
CMAKE_OPTS+=(-DICINGA2_WITH_OPENTELEMETRY=OFF)
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Turn off with OTel on Amazon Linux 2 as the default Protobuf compiler is way too old.
|
||||
if [ "$DISTRO" = "amazonlinux:2" ]; then
|
||||
CMAKE_OPTS+=(-DICINGA2_WITH_OPENTELEMETRY=OFF)
|
||||
fi
|
||||
CMAKE_OPTS+=(-DCMAKE_{C,CXX}_FLAGS="$(rpm -E '%{optflags} %{?march_flag}') ${WARN_FLAGS}")
|
||||
export LDFLAGS="$(rpm -E '%{?build_ldflags}')"
|
||||
;;
|
||||
|
|
@ -106,7 +143,7 @@ esac
|
|||
mkdir /icinga2/build
|
||||
cd /icinga2/build
|
||||
|
||||
cmake \
|
||||
"${SCL_ENABLE_GCC[@]}" cmake \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DICINGA2_UNITY_BUILD=ON \
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ option(ICINGA2_WITH_LIVESTATUS "Build the Livestatus module" ${ICINGA2_MASTER})
|
|||
option(ICINGA2_WITH_NOTIFICATION "Build the notification module" ON)
|
||||
option(ICINGA2_WITH_PERFDATA "Build the perfdata module" ${ICINGA2_MASTER})
|
||||
option(ICINGA2_WITH_ICINGADB "Build the IcingaDB module" ${ICINGA2_MASTER})
|
||||
option(ICINGA2_WITH_OPENTELEMETRY "Build the OpenTelemetry integration module" ${ICINGA2_MASTER})
|
||||
|
||||
option (USE_SYSTEMD
|
||||
"Configure icinga as native systemd service instead of a SysV initscript" OFF)
|
||||
|
|
@ -207,6 +208,23 @@ set(HAVE_EDITLINE "${EDITLINE_FOUND}")
|
|||
find_package(Termcap)
|
||||
set(HAVE_TERMCAP "${TERMCAP_FOUND}")
|
||||
|
||||
if(ICINGA2_WITH_OPENTELEMETRY)
|
||||
# Newer Protobuf versions provide a CMake config package that we should prefer, since it implicitly
|
||||
# links against all its dependencies (like absl, etc.) that would otherwise need to be linked manually.
|
||||
# Thus, first try to find Protobuf in config mode and only fall back to module mode if that fails.
|
||||
find_package(Protobuf CONFIG)
|
||||
if(NOT Protobuf_FOUND)
|
||||
# FindProtobuf.cmake in CMake versions < 3.31.0 is just broken and mixes up the Protobuf output directories
|
||||
# and it doesn't even support to pass any PLUGIN_OPTIONS like "lite" to the protobuf_generate() function in
|
||||
# order to generate code for the lite runtime without having to modify the proto files directly.
|
||||
if(CMAKE_VERSION VERSION_LESS 3.31.0)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/third-party/cmake/protobuf")
|
||||
endif()
|
||||
find_package(Protobuf REQUIRED)
|
||||
endif()
|
||||
list(APPEND base_DEPS protobuf::libprotobuf-lite)
|
||||
endif()
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib
|
||||
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/lib
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ RUN apt-get update && \
|
|||
libpq-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libprotobuf-dev \
|
||||
protobuf-compiler \
|
||||
make && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
@ -165,6 +167,7 @@ RUN apt-get update && \
|
|||
libmariadb3 \
|
||||
libmoosex-role-timer-perl \
|
||||
libpq5 \
|
||||
libprotobuf-lite32t64 \
|
||||
libssl3 \
|
||||
libsystemd0 \
|
||||
mailutils \
|
||||
|
|
|
|||
|
|
@ -2959,6 +2959,7 @@ By default, the following features provide advanced HA functionality:
|
|||
* [Graphite](09-object-types.md#objecttype-graphitewriter)
|
||||
* [InfluxDB](09-object-types.md#objecttype-influxdb2writer) (v1 and v2)
|
||||
* [OpenTsdb](09-object-types.md#objecttype-opentsdbwriter)
|
||||
* [OTLPMetrics](09-object-types.md#objecttype-otlpmetricswriter)
|
||||
* [Perfdata](09-object-types.md#objecttype-perfdatawriter) (for PNP)
|
||||
|
||||
#### High-Availability with Checks <a id="distributed-monitoring-high-availability-checks"></a>
|
||||
|
|
|
|||
|
|
@ -1871,6 +1871,54 @@ Configuration Attributes:
|
|||
host_template | Dictionary | **Optional.** Specify additional tags to be included with host metrics. This requires a sub-dictionary named `tags`. Also specify a naming prefix by setting `metric`. More information can be found in [OpenTSDB custom tags](14-features.md#opentsdb-custom-tags) and [OpenTSDB Metric Prefix](14-features.md#opentsdb-metric-prefix). More information can be found in [OpenTSDB custom tags](14-features.md#opentsdb-custom-tags). Defaults to an `empty Dictionary`.
|
||||
service_template | Dictionary | **Optional.** Specify additional tags to be included with service metrics. This requires a sub-dictionary named `tags`. Also specify a naming prefix by setting `metric`. More information can be found in [OpenTSDB custom tags](14-features.md#opentsdb-custom-tags) and [OpenTSDB Metric Prefix](14-features.md#opentsdb-metric-prefix). Defaults to an `empty Dictionary`.
|
||||
|
||||
### OTLPMetricsWriter <a id="objecttype-otlpmetricswriter"></a>
|
||||
|
||||
Emits metrics in [OpenTelemetry Protocol (OTLP)](https://opentelemetry.io/) format to a defined OpenTelemetry Collector
|
||||
or any other OTLP-compatible backend that accepts OTLP data over HTTP. This configuration object is available as
|
||||
[otlpmetrics feature](14-features.md#otlpmetrics-writer). You can find more information about OpenTelemetry and OTLP
|
||||
on the [OpenTelemetry website](https://opentelemetry.io/).
|
||||
|
||||
A basic copy and pastable example configuration is shown below:
|
||||
|
||||
```
|
||||
object OTLPMetricsWriter "otlp-metrics" {
|
||||
host = "127.0.0.1"
|
||||
port = 4318
|
||||
metrics_endpoint = "/v1/metrics"
|
||||
service_namespace = "icinga2-production"
|
||||
}
|
||||
```
|
||||
|
||||
There are more configuration options available as described in the table below.
|
||||
|
||||
| Name | Type | Description |
|
||||
|-------------------------------|------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| host | String | **Required.** OTLP backend host address. Defaults to `127.0.0.1`. |
|
||||
| port | Number | **Required.** OTLP backend HTTP port. Defaults to `4318`. |
|
||||
| metrics\_endpoint | String | **Required.** OTLP metrics endpoint path. Defaults to `/v1/metrics`. |
|
||||
| service\_namespace | String | **Required.** The namespace to associate with emitted metrics used in the `service.namespace` OTel resource attribute. Defaults to `icinga`. |
|
||||
| basic\_auth | Dictionary | **Optional.** Username and password for HTTP basic authentication. |
|
||||
| host\_resource\_attributes | Dictionary | **Optional.** Additional resource attributes to be included with host metrics. Defaults to none. |
|
||||
| service\_resource\_attributes | Dictionary | **Optional.** Additional resource attributes to be included with service metrics. Defaults to none. |
|
||||
| flush\_interval | Duration | **Optional.** How long to buffer data points before transferring to the OTLP backend. Defaults to `15s`. |
|
||||
| flush\_threshold | Number | **Optional.** How many bytes to buffer before forcing a transfer to the OTLP backend. Defaults to `16MiB`. |
|
||||
| enable\_ha | Boolean | **Optional.** Enable the high availability functionality. Has no effect in non-cluster setups. Defaults to `true`. |
|
||||
| enable\_send\_thresholds | Boolean | **Optional.** Whether to stream warning, critical, minimum & maximum as separate metrics to the OTLP backend. Defaults to `false`. |
|
||||
| diconnect\_timeout | Duration | **Optional.** Timeout to wait for any outstanding data to be flushed to the OTLP backend before disconnecting. Defaults to `10s`. |
|
||||
| enable\_tls | Boolean | **Optional.** Whether to use a TLS stream. Defaults to `false`. |
|
||||
| tls\_insecure\_noverify | Boolean | **Optional.** Disable TLS peer verification. Defaults to `false`. |
|
||||
| tls\_ca\_file | String | **Optional.** Path to CA certificate to validate the remote host. |
|
||||
| tls\_cert\_file | String | **Optional.** Path to the client certificate to present to the OTLP backend for mutual verification. |
|
||||
| tls\_key\_file | String | **Optional.** Path to the client certificate key. |
|
||||
|
||||
!!! tip
|
||||
|
||||
The `flush_threshold` is a byte size threshold, not a metric count threshold. By default, the writer will flush all
|
||||
buffered metrics to the OTLP backend once the total size of buffered metrics exceeds 16 MiB. This number is chosen
|
||||
based on the default `max_request_body_size` of the OpenTelemetry Collector, and you must adjust it according to the
|
||||
`max_request_body_size` of your OTLP backend to avoid metrics being dropped due to exceeding the maximum request body
|
||||
size. Furthermore, the writer may not flush at the exact byte size threshold due to the internal structure of OTLP
|
||||
messages, so make sure that the threshold is lower than the configured `max_request_body_size` of your OTLP backend.
|
||||
|
||||
### PerfdataWriter <a id="objecttype-perfdatawriter"></a>
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ best practice is to provide performance data.
|
|||
|
||||
This data is parsed by features sending metrics to time series databases (TSDB):
|
||||
|
||||
* [OpenTelemetry](14-features.md#otlpmetrics-writer)
|
||||
* [Graphite](14-features.md#graphite-carbon-cache-writer)
|
||||
* [InfluxDB](14-features.md#influxdb-writer)
|
||||
* [OpenTSDB](14-features.md#opentsdb-writer)
|
||||
|
|
@ -644,6 +645,267 @@ mechanism ensures that metrics are written even if the cluster fails.
|
|||
The recommended way of running OpenTSDB in this scenario is a dedicated server
|
||||
where you have OpenTSDB running.
|
||||
|
||||
### OTLPMetrics Writer <a id="otlpmetrics-writer"></a>
|
||||
|
||||
The [OpenTelemetry Protocol (OTLP/HTTP)](https://opentelemetry.io/docs/specs/otlp/#otlphttp) metrics Writer feature
|
||||
allows Icinga 2 to send metrics to OpenTelemetry Collector or any other backend that supports the OTLP HTTP protocol,
|
||||
such as [Prometheus OTLP](https://prometheus.io/docs/guides/opentelemetry/) receiver,
|
||||
[Grafana Mimir](https://grafana.com/docs/mimir/latest/configure/configure-otel-collector/),
|
||||
[OpenSearch Data Prepper](https://docs.opensearch.org/latest/data-prepper/pipelines/configuration/sources/otlp-source/),
|
||||
etc. It enables seamless integration of Icinga 2 metrics into modern observability stacks, allowing you to leverage the
|
||||
capabilities of OpenTelemetry for advanced analysis and visualization of your monitoring data. OpenTelemetry provides a
|
||||
standardized way to collect, process, and export telemetry data, making it easier to integrate with numerous
|
||||
[monitoring and observability](https://opentelemetry.io/docs/collector/components/exporter/) tools effortlessly.
|
||||
|
||||
In order to enable this feature, you can use the following command:
|
||||
|
||||
```bash
|
||||
icinga2 feature enable otlpmetrics
|
||||
```
|
||||
|
||||
By default, the OTLPMetrics Writer expects the OpenTelemetry Collector or any other OTLP HTTP receiver to listen at
|
||||
`127.0.0.1` on port `4318` but most of the third-party backends use their own ports, so you may need to adjust the
|
||||
configuration accordingly. Additionally, the `metrics_endpoint` can vary based on the backend you are using.
|
||||
For example, OpenTelemetry Collector uses `/v1/metrics` by default, while the Prometheus OTLP receiver uses
|
||||
`/api/v1/otlp/v1/metrics`. Therefore, it is important to set the correct `metrics_endpoint` in the configuration file.
|
||||
|
||||
You can find more details about the configuration options [here](09-object-types.md#objecttype-otlpmetricswriter).
|
||||
|
||||
The generated metric names follow the OpenTelemetry naming conventions and cannot be customized by end-users and are
|
||||
therefore always the same across all Icinga 2 installations. The OTLP metrics writer currently sends the following metrics:
|
||||
|
||||
| Metric Name | Description |
|
||||
|-----------------------|----------------------------------------------------------------------|
|
||||
| state_check.perfdata | Performance data metrics from checks. |
|
||||
| state_check.threshold | Threshold values for perfdata metrics (warning, critical, min, max). |
|
||||
|
||||
By default, the writer will not stream any data point for the `state_check.threshold` metric. To enable the streaming
|
||||
of threshold metrics, you need to set the `enable_send_thresholds` option to `true` in the OTLPMetrics Writer
|
||||
configuration. Once enabled, it will send the threshold values for each performance data metric if they are available
|
||||
in the produced check results.
|
||||
|
||||
The data points type for all the above metrics is [`gauge`](https://opentelemetry.io/docs/specs/otel/metrics/data-model/#gauge)
|
||||
and the perfdata labels and their units (if available) are mapped to OpenTelemetry metric points attributes. For example,
|
||||
a perfdata label `file_size` with a value of `42` and unit `B` will be sent to the `state_check.perfdata` metric stream,
|
||||
with a metric point having a value of `42`, along with the attributes `perfdata_label="file_size"` and `unit="B"`.
|
||||
Additionally, each metric point will also include other relevant attributes such as `icinga2.host.name`, `icinga2.service.name`,
|
||||
`icinga2.command.name`, etc. as resource attributes. You can find the full list of metric point formats and attributes
|
||||
in the [OTLPMetrics data format](#otlpmetrics-writer-data-format) section below.
|
||||
|
||||
In addition to the default attributes, it is also possible to configure custom resource attributes that are sent along
|
||||
with the metrics to the OpenTelemetry backend. You can use the `host_resource_attributes` and `service_resource_attributes`
|
||||
options in the OTLPMetrics Writer configuration to define custom resource attributes for host and service checks
|
||||
respectively. You can use macros in the attribute values to dynamically populate them based on the check context.
|
||||
For instance, you can add a custom resource attribute `host.os` with the value `$host.vars.os$` and it will be populated
|
||||
with the value of `vars.os` for each host that has this variable defined, otherwise it will silently be ignored.
|
||||
All custom resource attributes will be prefixed with `icinga2.custom.` to avoid naming conflicts with existing
|
||||
OpenTelemetry and Icinga 2's built-in resource attributes. For example, if you define a custom resource attribute
|
||||
`host.os`, it will be sent as `icinga2.custom.host.os` to OpenTelemetry.
|
||||
|
||||
!!! warning
|
||||
|
||||
Be cautious when defining custom resource attributes, as they are sent with every metric and can lead to high
|
||||
cardinality issues if not used carefully. It is recommended to only define custom resource attributes that are
|
||||
necessary for your monitoring use case and to avoid using attributes with high variability or a large number of
|
||||
unique values.
|
||||
|
||||
Apart from custom resource attributes, the OTLPMetrics Writer also allows you to configure an additional resource
|
||||
attribute called [`service.namespace`](https://opentelemetry.io/docs/specs/semconv/registry/attributes/service/#service-namespace)
|
||||
via the `service_namespace` option in the OTLPMetrics Writer configuration. This attribute is not specific to any host
|
||||
or service but is a general attribute that applies to all metrics emitted by one OTLPMetrics Writer instance.
|
||||
By default, it is set to `icinga`. You can customize it to better fit your monitoring environment. For example, you
|
||||
might set it to `production`, `staging`, or any other relevant namespace that categorizes your Icinga 2 metrics emitted
|
||||
to the OpenTelemetry backend effectively.
|
||||
|
||||
#### OTLPMetrics in HA Cluster Zones <a id="otlpmetrics-writer-ha-cluster"></a>
|
||||
|
||||
This writer supports [High Availability (HA)](06-distributed-monitoring.md#distributed-monitoring-high-availability-features)
|
||||
cluster zones in Icinga 2. By default, the `enable_ha` option is set to `true` in the OTLPMetrics Writer config, which
|
||||
means that only one writer in the cluster will be active at any given time, sending metrics to the configured OTLP backend.
|
||||
The other OTLPMetrics Writer will remain in standby mode and ready to take over if the active endpoint fails or becomes
|
||||
unavailable for any reason. However, due to how HA works in Icinga 2, the failover mechanism won't take place until the
|
||||
two endpoints in the cluster lose connection with each other, and not just when the OTLPMetrics Writer fails. Therefore,
|
||||
as long as the cluster connection is healthy, the other writer won't take over even if the active writer encounters some
|
||||
issues connecting to the OTLP backend or sending metrics.
|
||||
|
||||
In general, do not set `enable_ha` to `false` unless you have a specific use case that requires multiple OTLPMetrics
|
||||
Writer instances to be active at the same time, sending metrics to different OTLP backends. In most cases, it is
|
||||
recommended to keep `enable_ha` set to `true` to ensure that only one writer is active even in a non-HA cluster zone.
|
||||
|
||||
#### OTLPMetrics Data Format <a id="otlpmetrics-writer-data-format"></a>
|
||||
|
||||
The OTLPMetrics Writer sends metrics to the configured OTLP HTTP endpoint in the OpenTelemetry Protocol (OTLP) format.
|
||||
The metric names and attributes follow the OpenTelemetry naming conventions. The `state_check.perfdata` metric includes
|
||||
performance data metrics from checks, while the `state_check.threshold` metric is used to stream all threshold related
|
||||
data points. In general, both metric streams share the same set of resource attributes, they only differ in the concrete
|
||||
metric point attributes. Below is an example of the full data format for both metrics and can be used as a reference for
|
||||
configuring your OTLP backend to properly receive and process the emitted metrics.
|
||||
|
||||
```json
|
||||
{
|
||||
"resourceMetrics": [
|
||||
{
|
||||
"resource": {
|
||||
"attributes": [
|
||||
{
|
||||
"key": "service.name",
|
||||
"value": {
|
||||
"stringValue": "Icinga 2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "service.instance.id",
|
||||
"value": {
|
||||
"stringValue": "9a1f9d6d58648f2274c539bbdd5f09388b68fc0a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "service.version",
|
||||
"value": {
|
||||
"stringValue": "v2.15.0-285-g196ba8e9d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "telemetry.sdk.language",
|
||||
"value": {
|
||||
"stringValue": "cpp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "telemetry.sdk.name",
|
||||
"value": {
|
||||
"stringValue": "Icinga 2 OTel Integration"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "telemetry.sdk.version",
|
||||
"value": {
|
||||
"stringValue": "v2.15.0-285-g196ba8e9d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "service.namespace",
|
||||
"value": {
|
||||
"stringValue": "icinga"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "icinga2.host.name",
|
||||
"value": {
|
||||
"stringValue": "something"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "icinga2.service.name",
|
||||
"value": {
|
||||
"stringValue": "something-service"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "icinga2.command.name",
|
||||
"value": {
|
||||
"stringValue": "icinga"
|
||||
}
|
||||
}
|
||||
],
|
||||
"entityRefs": [
|
||||
{
|
||||
"type": "service",
|
||||
"idKeys": [
|
||||
"icinga2.host.name",
|
||||
"icinga2.service.name"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"scopeMetrics": [
|
||||
{
|
||||
"scope": {
|
||||
"name": "icinga2",
|
||||
"version": "v2.15.0-285-g196ba8e9d"
|
||||
},
|
||||
"metrics": [
|
||||
{
|
||||
"name": "state_check.perfdata",
|
||||
"gauge": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "perfdata_label",
|
||||
"value": {
|
||||
"stringValue": "some_perfdata_label"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "1770385516896651008",
|
||||
"timeUnixNano": "1770385516896651008",
|
||||
"asDouble": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "state_check.threshold",
|
||||
"gauge": {
|
||||
"dataPoints": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "perfdata_label",
|
||||
"value": {
|
||||
"stringValue": "some_perfdata_label"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "threshold_type",
|
||||
"value": {
|
||||
"stringValue": "critical"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "1770385516896651008",
|
||||
"timeUnixNano": "1770385516896651008",
|
||||
"asDouble": 0
|
||||
},
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "perfdata_label",
|
||||
"value": {
|
||||
"stringValue": "some_perfdata_label"
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "threshold_type",
|
||||
"value": {
|
||||
"stringValue": "warning"
|
||||
}
|
||||
}
|
||||
],
|
||||
"startTimeUnixNano": "1770385516896651008",
|
||||
"timeUnixNano": "1770385516896651008",
|
||||
"asDouble": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemaUrl": "https://opentelemetry.io/schemas/1.39.0"
|
||||
}
|
||||
],
|
||||
"schemaUrl": "https://opentelemetry.io/schemas/1.39.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
As you can see in the above example, most of the attributes are resource attributes that are shared across all emitted
|
||||
metrics. The only attributes that are specific to the OTLPMetrics Writer have `icinga2.` prefix like `icinga2.host.name`
|
||||
etc. The `state_check.perfdata` metric has an additional attribute `perfdata_label` that corresponds to the perfdata
|
||||
label of the emitted metric point value. Likewise, the `state_check.threshold` metric has two additional attributes
|
||||
`perfdata_label` and `threshold_type` that correspond to the perfdata label they belong to and the threshold type
|
||||
(warning, critical, min, max) respectively.
|
||||
|
||||
### Writing Performance Data Files <a id="writing-performance-data-files"></a>
|
||||
|
||||
|
|
|
|||
53
etc/icinga2/features-available/otlpmetrics.conf
Normal file
53
etc/icinga2/features-available/otlpmetrics.conf
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* The OpenTelemetry Metrics Writer feature allows Icinga 2 to export metrics from performance
|
||||
* data to an OpenTelemetry Collector or compatible backend.
|
||||
*
|
||||
* For more information, see the official documentation:
|
||||
* https://icinga.com/docs/icinga-2/latest/doc/14-features/#otlpmetrics-writer
|
||||
*/
|
||||
object OTLPMetricsWriter "otlp-metrics" {
|
||||
// host = "127.0.0.1"
|
||||
// port = 4318
|
||||
// metrics_endpoint = "/v1/metrics"
|
||||
# Optionally, you can set a namespace to be used as OTel service.namespace attribute for all exported metrics.
|
||||
// service_namespace = "icinga"
|
||||
|
||||
# By default, basic AUTH is disabled. Uncomment and set the following lines to enable it.
|
||||
// basic_auth = {
|
||||
// username = "otel_user"
|
||||
// password = "otel_password"
|
||||
// }
|
||||
|
||||
# You can also add custom tags to the exported metrics based on host and service variables.
|
||||
# These tags will be included in the OTel metrics as resource attributes for hosts and services, respectively.
|
||||
# By default, no additional tags are added. Adjust the templates as needed to include the desired variables.
|
||||
// host_resource_attributes = {
|
||||
// "host.vars.env" = "$host.vars.env$"
|
||||
// "host.vars.os" = "$host.vars.os$"
|
||||
// }
|
||||
// service_resource_attributes = {
|
||||
// "service.vars.env" = "$service.vars.env$"
|
||||
// "service.vars.os" = "$service.vars.os$"
|
||||
// }
|
||||
|
||||
# These are the default settings used by the OTel writer. Adjust them as needed.
|
||||
# Please refer to the documentation for more details on each option.
|
||||
// enable_ha = true
|
||||
// flush_interval = 15s
|
||||
// flush_threshold = 16*1024*1024
|
||||
# When stopping Icinga 2, this timeout defines how long to wait for any pending OTel
|
||||
# metrics to be sent before disconnecting and discarding them.
|
||||
// disconnect_timeout = 10s
|
||||
|
||||
# Allow the OTLP writer to send the check thresholds as OTel metrics to the configured endpoint.
|
||||
# By default, this is disabled but you can enable it to have the thresholds available in the `state_check.threshold` OTel metric.
|
||||
// enable_send_thresholds = false
|
||||
|
||||
# You can enable TLS encryption by uncommenting and configuring the following options.
|
||||
# By default, the OTel writer uses unencrypted connections (plain HTTP requests).
|
||||
// enable_tls = false
|
||||
// tls_insecure_noverify = false
|
||||
// tls_ca_file = "/path/to/otel/ca.crt"
|
||||
// tls_cert_file = "/path/to/otel/client.crt"
|
||||
// tls_key_file = "/path/to/otel/client.key"
|
||||
}
|
||||
|
|
@ -50,6 +50,10 @@ if(ICINGA2_WITH_NOTIFICATION)
|
|||
list(APPEND icinga_app_SOURCES $<TARGET_OBJECTS:notification>)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_WITH_OPENTELEMETRY)
|
||||
list(APPEND icinga_app_SOURCES $<TARGET_OBJECTS:otel>)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_WITH_PERFDATA)
|
||||
list(APPEND icinga_app_SOURCES $<TARGET_OBJECTS:perfdata>)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ if(ICINGA2_WITH_NOTIFICATION)
|
|||
add_subdirectory(notification)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_WITH_OPENTELEMETRY)
|
||||
add_subdirectory(otel)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_WITH_PERFDATA)
|
||||
add_subdirectory(perfdata)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
using namespace icinga;
|
||||
|
||||
AtomicOrLocked<String> Application::m_EnvironmentId;
|
||||
|
||||
String Application::GetAppEnvironment()
|
||||
{
|
||||
Value defaultValue = Empty;
|
||||
|
|
@ -16,3 +18,29 @@ void Application::SetAppEnvironment(const String& name)
|
|||
{
|
||||
ScriptGlobal::Set("Environment", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cluster environment ID set by IcingaDB.
|
||||
*
|
||||
* This method returns the cluster environment ID generated by the IcingaDB component (if enabled).
|
||||
* The environment ID is a unique identifier used to distinguish between different Icinga 2 clusters
|
||||
* in a multi-cluster setup. It is typically set by IcingaDB when it starts up and can be used by other
|
||||
* components (e.g., for telemetry) to correlate data across clusters. If IcingaDB is not enabled or has
|
||||
* not yet set the environment ID, this method will return an empty string.
|
||||
*
|
||||
* @return The cluster environment ID set by IcingaDB, or an empty string if not set.
|
||||
*/
|
||||
String Application::GetEnvironmentId()
|
||||
{
|
||||
return m_EnvironmentId.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cluster environment ID.
|
||||
*
|
||||
* @param envID The cluster environment ID to set, typically generated by IcingaDB.
|
||||
*/
|
||||
void Application::SetEnvironmentId(const String& envID)
|
||||
{
|
||||
m_EnvironmentId.store(envID);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ public:
|
|||
|
||||
static String GetAppEnvironment();
|
||||
static void SetAppEnvironment(const String& name);
|
||||
static String GetEnvironmentId();
|
||||
static void SetEnvironmentId(const String& envID);
|
||||
|
||||
static double GetStartTime();
|
||||
static void SetStartTime(double ts);
|
||||
|
|
@ -130,6 +132,8 @@ private:
|
|||
static pid_t m_ReloadProcess; /**< The PID of a subprocess doing a reload, only valid when l_Restarting==true */
|
||||
static bool m_RequestReopenLogs; /**< Whether we should re-open log files. */
|
||||
|
||||
static AtomicOrLocked<String> m_EnvironmentId; /**< The cluster environment ID set by IcingaDB. */
|
||||
|
||||
#ifndef _WIN32
|
||||
static pid_t m_UmbrellaProcess; /**< The PID of the Icinga umbrella process */
|
||||
#endif /* _WIN32 */
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ void IcingaDB::Validate(int types, const ValidationUtils& utils)
|
|||
|
||||
try {
|
||||
InitEnvironmentId();
|
||||
Application::SetEnvironmentId(m_EnvironmentId);
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_THROW_EXCEPTION(ValidationError(this, std::vector<String>(), e.what()));
|
||||
}
|
||||
|
|
|
|||
43
lib/otel/CMakeLists.txt
Normal file
43
lib/otel/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
set(ICINGA2_OPENTELEMETRY_PROTOS_DIR "${icinga2_SOURCE_DIR}/third-party/opentelemetry-proto")
|
||||
protobuf_generate(
|
||||
LANGUAGE cpp
|
||||
# According to the Protobuf docs[^1], the Protobuf compiler generates with the "LITE_RUNTIME" option much
|
||||
# smaller code than the default optimze_for=SPEED option, which includes code for reflection, descriptors,
|
||||
# and other features not needed by any part of the Icinga 2 OpenTelemetry integration. Thus, we use the "lite"
|
||||
# option to generate code that only depend on the libprotobuf-lite instead of the full libprotobuf library.
|
||||
#
|
||||
# The only downside of using the lite runtime is that we won't be able to use any debugging capabilities
|
||||
# provided by the full Protobuf runtime (like the DebugString() method on messages for easy printing,
|
||||
# which heavily relies on reflection).
|
||||
#
|
||||
# [^1]: https://protobuf.dev/programming-guides/proto3/#options
|
||||
PLUGIN_OPTIONS lite
|
||||
OUT_VAR otel_PROTO_SRCS
|
||||
IMPORT_DIRS "${ICINGA2_OPENTELEMETRY_PROTOS_DIR}"
|
||||
PROTOS
|
||||
"${ICINGA2_OPENTELEMETRY_PROTOS_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.proto"
|
||||
"${ICINGA2_OPENTELEMETRY_PROTOS_DIR}/opentelemetry/proto/common/v1/common.proto"
|
||||
"${ICINGA2_OPENTELEMETRY_PROTOS_DIR}/opentelemetry/proto/metrics/v1/metrics.proto"
|
||||
"${ICINGA2_OPENTELEMETRY_PROTOS_DIR}/opentelemetry/proto/resource/v1/resource.proto"
|
||||
)
|
||||
|
||||
set(otel_SOURCES
|
||||
otel.cpp otel.hpp
|
||||
${otel_PROTO_SRCS}
|
||||
)
|
||||
|
||||
add_library(otel OBJECT ${otel_SOURCES})
|
||||
add_dependencies(otel base remote)
|
||||
target_include_directories(otel
|
||||
SYSTEM PUBLIC
|
||||
$<TARGET_PROPERTY:protobuf::libprotobuf-lite,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
otel PROPERTIES
|
||||
FOLDER Lib
|
||||
)
|
||||
678
lib/otel/otel.cpp
Normal file
678
lib/otel/otel.cpp
Normal file
|
|
@ -0,0 +1,678 @@
|
|||
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "otel/otel.hpp"
|
||||
#include "base/application.hpp"
|
||||
#include "base/defer.hpp"
|
||||
#include "base/tcpsocket.hpp"
|
||||
#include "base/tlsutility.hpp"
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <future>
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
namespace http = boost::beast::http;
|
||||
namespace v1_metrics = opentelemetry::proto::metrics::v1;
|
||||
|
||||
// The max buffer size used to batch Protobuf writes to Asio streams.
|
||||
static constexpr std::size_t l_BufferSize = 64UL * 1024;
|
||||
// The OpenTelemetry schema convention URL used in the exported metrics.
|
||||
// See https://opentelemetry.io/docs/specs/semconv/
|
||||
static constexpr std::string_view l_OTelSchemaConv = "https://opentelemetry.io/schemas/1.39.0";
|
||||
|
||||
template std::size_t OTel::Record(Gauge&, int64_t, double, double, AttrsMap);
|
||||
template std::size_t OTel::Record(Gauge&, double, double, double, AttrsMap);
|
||||
template void OTel::SetAttribute(Attribute&, std::string_view&&, String&&);
|
||||
template void OTel::SetAttribute(Attribute&, String&&, Value&);
|
||||
|
||||
/**
|
||||
* Calculate the exponential backoff duration for retrying failed exports or reconnections.
|
||||
*
|
||||
* This method calculates the backoff duration based on the number of retry attempts using an exponential
|
||||
* backoff strategy as per OTel specifications. The backoff duration starts at a minimum value and doubles
|
||||
* with each attempt, up to a maximum cap (30s). This helps to avoid overwhelming the OpenTelemetry backend
|
||||
* with rapid retry attempts in case of transient errors.
|
||||
*
|
||||
* @param attempt The current retry attempt number (starting from 1).
|
||||
*
|
||||
* @return The calculated backoff duration in milliseconds.
|
||||
*/
|
||||
static constexpr std::chrono::milliseconds Backoff(uint64_t attempt)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
constexpr milliseconds MaxBackoffMs = seconds(30);
|
||||
constexpr milliseconds MinBackoffMs = milliseconds(100);
|
||||
|
||||
// 2^attempt may overflow, so we cap it to a safe value within the 64-bit range,
|
||||
// which is sufficient to reach MaxBackoffMs from MinBackoffMs.
|
||||
constexpr uint64_t maxSafeAttempt = 16; // 2^16 * 100ms = 6553.6s > 30s
|
||||
auto exponential = MinBackoffMs * (1ULL << std::min(attempt, maxSafeAttempt));
|
||||
if (exponential >= MaxBackoffMs) {
|
||||
return MaxBackoffMs;
|
||||
}
|
||||
return duration_cast<milliseconds>(exponential);
|
||||
}
|
||||
|
||||
OTel::OTel(OTelConnInfo& connInfo): OTel{connInfo, IoEngine::Get().GetIoContext()}
|
||||
{
|
||||
}
|
||||
|
||||
OTel::OTel(OTelConnInfo& connInfo, boost::asio::io_context& io)
|
||||
: m_ConnInfo{std::move(connInfo)},
|
||||
m_Strand{io},
|
||||
m_ExportAsioCV{io},
|
||||
m_RetryExportAndConnTimer{io},
|
||||
m_Exporting{false},
|
||||
m_Stopped{false}
|
||||
{
|
||||
if (m_ConnInfo.EnableTls) {
|
||||
m_TlsContext = MakeAsioSslContext(m_ConnInfo.TlsCrt, m_ConnInfo.TlsKey, m_ConnInfo.TlsCaCrt);
|
||||
}
|
||||
}
|
||||
|
||||
void OTel::Start()
|
||||
{
|
||||
if (m_Stopped.exchange(false)) {
|
||||
ResetExporting(true);
|
||||
}
|
||||
|
||||
IoEngine::SpawnCoroutine(m_Strand, [this, keepAlive = ConstPtr(this)](boost::asio::yield_context yc) {
|
||||
ExportLoop(yc);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the OTel exporter and disconnect from the OpenTelemetry backend.
|
||||
*
|
||||
* This method blocks until the exporter has fully stopped and disconnected from the backend.
|
||||
* It cancels any ongoing export operations and clears all its internal state, so that it can be
|
||||
* safely restarted later if needed.
|
||||
*/
|
||||
void OTel::Stop()
|
||||
{
|
||||
if (m_Stopped.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::promise<void> promise;
|
||||
IoEngine::SpawnCoroutine(m_Strand, [this, &promise, keepAlive = ConstPtr(this)](boost::asio::yield_context& yc) {
|
||||
m_ExportAsioCV.NotifyAll(); // Wake up the export loop if it's waiting for new export requests.
|
||||
m_RetryExportAndConnTimer.cancel();
|
||||
|
||||
if (!m_Stream) {
|
||||
promise.set_value();
|
||||
return;
|
||||
}
|
||||
|
||||
// We only wait for ongoing export operations to complete if we're currently exporting,
|
||||
// otherwise there will be nothing that would wake us up from the `WaitForClear` sleep
|
||||
// below, and we would end up blocking indefinitely, so we have to check the exporting
|
||||
// state here first.
|
||||
if (Exporting()) {
|
||||
Timeout writerTimeout(m_Strand, boost::posix_time::seconds(5), [this] {
|
||||
boost::system::error_code ec;
|
||||
std::visit([&ec](auto& stream) { stream->lowest_layer().cancel(ec); }, *m_Stream);
|
||||
});
|
||||
while (m_Request) {
|
||||
m_ExportAsioCV.Wait(yc);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the stream is still valid before attempting to disconnect, since the above lowest_layer.cancel()
|
||||
// may have caused the export loop to detect a broken connection and reset the stream already.
|
||||
if (m_Stream) {
|
||||
if (auto* tlsStreamPtr = std::get_if<Shared<AsioTlsStream>::Ptr>(&*m_Stream); tlsStreamPtr) {
|
||||
(*tlsStreamPtr)->GracefulDisconnect(m_Strand, yc);
|
||||
} else if (auto* tcpStreamPtr = std::get_if<Shared<AsioTcpStream>::Ptr>(&*m_Stream); tcpStreamPtr) {
|
||||
boost::system::error_code ec;
|
||||
(*tcpStreamPtr)->lowest_layer().shutdown(AsioTcpStream::lowest_layer_type::shutdown_both, ec);
|
||||
(*tcpStreamPtr)->lowest_layer().close(ec);
|
||||
}
|
||||
}
|
||||
|
||||
Log(LogInformation, "OTelExporter")
|
||||
<< "Disconnected from OpenTelemetry backend.";
|
||||
|
||||
m_Stream.reset();
|
||||
promise.set_value();
|
||||
});
|
||||
promise.get_future().wait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the given OTel metrics request to the OpenTelemetry backend.
|
||||
*
|
||||
* This method initiates the export of the provided OTel metrics request to the configured
|
||||
* OpenTelemetry backend. If an export is already in progress, it waits for the previous
|
||||
* export to complete before proceeding with the new export request (blocking the caller).
|
||||
*
|
||||
* @param request The OTel metrics request to export.
|
||||
*/
|
||||
void OTel::Export(std::unique_ptr<MetricsRequest>&& request)
|
||||
{
|
||||
std::unique_lock lock(m_Mutex);
|
||||
if (m_Exporting) {
|
||||
Log(LogWarning, "OTelExporter")
|
||||
<< "Received export request while previous export is still in progress. Waiting for it to complete.";
|
||||
|
||||
m_ExportCV.wait(lock, [this] { return m_Stopped || !m_Exporting; });
|
||||
if (m_Stopped) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_Exporting = true;
|
||||
lock.unlock();
|
||||
|
||||
// Access to m_Request is serialized via m_Strand, so we must post the actual export operation to it.
|
||||
boost::asio::post(m_Strand, [this, keepAlive = ConstPtr(this), request = std::move(request)]() mutable {
|
||||
m_Request = std::move(request);
|
||||
m_ExportAsioCV.NotifyAll();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the standard OTel resource attributes in the given ResourceMetrics Protobuf object.
|
||||
*
|
||||
* This method populates the standard OTel resource attributes as per OTel specifications[^1][^2]
|
||||
* into the provided ResourceMetrics Protobuf object. It sets attributes such as service name,
|
||||
* instance ID, version, and telemetry SDK information.
|
||||
*
|
||||
* @param rm The ResourceMetrics Protobuf object to populate.
|
||||
*
|
||||
* [^1]: https://opentelemetry.io/docs/specs/semconv/resource/#telemetry-sdk
|
||||
* [^2]: https://opentelemetry.io/docs/specs/semconv/resource/service/
|
||||
*/
|
||||
void OTel::PopulateResourceAttrs(const std::unique_ptr<v1_metrics::ResourceMetrics>& rm)
|
||||
{
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
rm->set_schema_url(l_OTelSchemaConv.data());
|
||||
auto* resource = rm->mutable_resource();
|
||||
|
||||
auto* attr = resource->add_attributes();
|
||||
SetAttribute(*attr, "service.name"sv, "Icinga 2"sv);
|
||||
|
||||
auto instanceID = Application::GetEnvironmentId();
|
||||
if (instanceID.IsEmpty()) {
|
||||
instanceID = "unknown";
|
||||
}
|
||||
attr = resource->add_attributes();
|
||||
SetAttribute(*attr, "service.instance.id"sv, std::move(instanceID));
|
||||
|
||||
attr = resource->add_attributes();
|
||||
SetAttribute(*attr, "service.version"sv, Application::GetAppVersion());
|
||||
|
||||
attr = resource->add_attributes();
|
||||
// We don't actually use OTel SDKs here, but to comply with OTel specs, we need to provide these attributes anyway.
|
||||
SetAttribute(*attr, "telemetry.sdk.language"sv, "cpp"sv);
|
||||
|
||||
attr = resource->add_attributes();
|
||||
SetAttribute(*attr, "telemetry.sdk.name"sv, "Icinga 2 OTel Integration"sv);
|
||||
|
||||
attr = resource->add_attributes();
|
||||
SetAttribute(*attr, "telemetry.sdk.version"sv, Application::GetAppVersion());
|
||||
|
||||
auto* ism = rm->add_scope_metrics();
|
||||
ism->set_schema_url(l_OTelSchemaConv.data());
|
||||
ism->mutable_scope()->set_name("icinga2");
|
||||
ism->mutable_scope()->set_version(Application::GetAppVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a connection to the OpenTelemetry backend endpoint.
|
||||
*
|
||||
* In case of connection failures, it retries as per OTel spec[^1] with exponential backoff until a successful
|
||||
* connection is established or the exporter is stopped. Therefore, @c m_Stream is not guaranteed to be valid
|
||||
* after this method returns, so the caller must check it before using it.
|
||||
*
|
||||
* @param yc The Boost.Asio yield context for asynchronous operations.
|
||||
*
|
||||
* [^1]: https://opentelemetry.io/docs/specs/otlp/#otlphttp-connection
|
||||
*/
|
||||
void OTel::Connect(boost::asio::yield_context& yc)
|
||||
{
|
||||
Log(LogInformation, "OTelExporter")
|
||||
<< "Connecting to OpenTelemetry backend on host '" << m_ConnInfo.Host << ":" << m_ConnInfo.Port << "'.";
|
||||
|
||||
for (uint64_t attempt = 1; !m_Stopped; ++attempt) {
|
||||
try {
|
||||
decltype(m_Stream) stream;
|
||||
if (m_ConnInfo.EnableTls) {
|
||||
stream = Shared<AsioTlsStream>::Make(m_Strand.context(), *m_TlsContext, m_ConnInfo.Host);
|
||||
} else {
|
||||
stream = Shared<AsioTcpStream>::Make(m_Strand.context());
|
||||
}
|
||||
|
||||
Timeout timeout{m_Strand, boost::posix_time::seconds(10), [this, stream] {
|
||||
Log(LogCritical, "OTelExporter")
|
||||
<< "Timeout while connecting to OpenTelemetry backend '" << m_ConnInfo.Host << ":" << m_ConnInfo.Port << "', cancelling attempt.";
|
||||
|
||||
boost::system::error_code ec;
|
||||
std::visit([&ec](auto& s) { s->lowest_layer().cancel(ec); }, *stream);
|
||||
}};
|
||||
|
||||
std::visit([this, &yc](auto& streamArg) {
|
||||
icinga::Connect(streamArg->lowest_layer(), m_ConnInfo.Host, std::to_string(m_ConnInfo.Port), yc);
|
||||
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(streamArg)>, Shared<AsioTlsStream>::Ptr>) {
|
||||
streamArg->next_layer().async_handshake(AsioTlsStream::next_layer_type::client, yc);
|
||||
|
||||
if (m_ConnInfo.VerifyPeerCertificate && !streamArg->next_layer().IsVerifyOK()) {
|
||||
BOOST_THROW_EXCEPTION(std::runtime_error(
|
||||
"TLS certificate validation failed: " + streamArg->next_layer().GetVerifyError()
|
||||
));
|
||||
}
|
||||
}
|
||||
}, *stream);
|
||||
|
||||
m_Stream = std::move(stream);
|
||||
|
||||
Log(LogInformation, "OTelExporter")
|
||||
<< "Successfully connected to OpenTelemetry backend.";
|
||||
return;
|
||||
} catch (const std::exception& ex) {
|
||||
Log(m_Stopped ? LogDebug : LogCritical, "OTelExporter")
|
||||
<< "Cannot connect to OpenTelemetry backend '" << m_ConnInfo.Host << ":" << m_ConnInfo.Port
|
||||
<< "' (attempt #" << attempt << "): " << ex.what();
|
||||
|
||||
if (!m_Stopped) {
|
||||
boost::system::error_code ec;
|
||||
m_RetryExportAndConnTimer.expires_after(Backoff(attempt));
|
||||
m_RetryExportAndConnTimer.async_wait(yc[ec]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main export loop for exporting OTel metrics to the configured backend.
|
||||
*
|
||||
* This method runs in a loop, waiting for new metrics to be available for export. In case of export failures,
|
||||
* it retries the export as per OTel spec[^1] with exponential backoff until the export succeeds or the exporter
|
||||
* is stopped. After a successful export, it clears the exported metrics from @c m_Request to make room for new metrics.
|
||||
*
|
||||
* @param yc The Asio yield context for asynchronous operations.
|
||||
*
|
||||
* [^1]: https://opentelemetry.io/docs/specs/otlp/#retryable-response-codes
|
||||
*/
|
||||
void OTel::ExportLoop(boost::asio::yield_context& yc)
|
||||
{
|
||||
Defer cleanup{[this] {
|
||||
m_Request.reset();
|
||||
m_ExportAsioCV.NotifyAll();
|
||||
ResetExporting(true /* notify all */);
|
||||
}};
|
||||
|
||||
namespace ch = std::chrono;
|
||||
|
||||
while (true) {
|
||||
// Wait for a new export request to be available. If the exporter is stopped while waiting,
|
||||
// we will be notified without a new request, so we also check the stopped state here to
|
||||
// avoid waiting indefinitely in that case.
|
||||
while (!m_Request && !m_Stopped) {
|
||||
m_ExportAsioCV.Wait(yc);
|
||||
}
|
||||
|
||||
if (m_Stopped) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!m_Stream) {
|
||||
Connect(yc);
|
||||
}
|
||||
|
||||
for (uint64_t attempt = 1; m_Stream && !m_Stopped; ++attempt) {
|
||||
try {
|
||||
ExportImpl(yc);
|
||||
m_Request.reset();
|
||||
m_ExportAsioCV.NotifyAll();
|
||||
ResetExporting(false /* notify one */);
|
||||
break;
|
||||
} catch (const RetryableExportError& ex) {
|
||||
ch::milliseconds retryAfter;
|
||||
if (auto throttle = ex.Throttle(); throttle > 0ms) {
|
||||
retryAfter = throttle;
|
||||
} else {
|
||||
retryAfter = Backoff(attempt);
|
||||
}
|
||||
|
||||
Log(LogWarning, "OTelExporter")
|
||||
<< "Failed to export metrics to OpenTelemetry backend (attempt #" << attempt << "). Retrying in "
|
||||
<< retryAfter.count() << "ms.";
|
||||
|
||||
boost::system::error_code ec;
|
||||
m_RetryExportAndConnTimer.expires_after(retryAfter);
|
||||
m_RetryExportAndConnTimer.async_wait(yc[ec]);
|
||||
} catch (const std::exception& ex) {
|
||||
LogSeverity severity = LogCritical;
|
||||
const auto* ser{dynamic_cast<const boost::system::system_error*>(&ex)};
|
||||
// Since we don't have a proper connection health check mechanism, we assume that certain errors
|
||||
// indicate a broken connection and force a reconnect in those cases. For the `end_of_stream` case,
|
||||
// we downgrade the log severity to debug level since this is a normal occurrence when using an OTEL
|
||||
// collector compatible backend that don't honor keep-alive connections (e.g., OpenSearch Data Prepper).
|
||||
if (m_Stopped || (ser && ser->code() == http::error::end_of_stream)) {
|
||||
severity = LogDebug;
|
||||
}
|
||||
Log{severity, "OTelExporter", DiagnosticInformation(ex, false)};
|
||||
m_Stream.reset(); // Force reconnect on next export attempt.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OTel::ExportImpl(boost::asio::yield_context& yc) const
|
||||
{
|
||||
AsioProtobufOutStream outputS{*m_Stream, m_ConnInfo, yc};
|
||||
[[maybe_unused]] auto serialized = m_Request->SerializeToZeroCopyStream(&outputS);
|
||||
ASSERT(serialized);
|
||||
// Must have completed chunk writing successfully, otherwise reading the response will hang forever.
|
||||
if (!outputS.WriterDone()) {
|
||||
BOOST_THROW_EXCEPTION(std::runtime_error("BUG: Protobuf output stream writer did not complete successfully."));
|
||||
}
|
||||
|
||||
IncomingHttpResponse responseMsg{*m_Stream};
|
||||
responseMsg.Parse(yc);
|
||||
|
||||
if (auto ct = responseMsg[http::field::content_type]; ct != "application/x-protobuf") {
|
||||
if (responseMsg.result() == http::status::ok) {
|
||||
// Some OpenTelemetry Collector compatible backends (e.g., Prometheus OTLP Receiver) respond with 200 OK
|
||||
// but without the expected Protobuf content type. So, don't do anything here since the request succeeded.
|
||||
return;
|
||||
}
|
||||
Log(LogWarning, "OTelExporter")
|
||||
<< "Unexpected Content-Type from OpenTelemetry backend '" << ct << "' (" << responseMsg.reason() << "):\n"
|
||||
<< responseMsg.body();
|
||||
} else if (responseMsg.result_int() >= 200 && responseMsg.result_int() <= 299) {
|
||||
// We've got a valid Protobuf response, so we've to deserialize the body to check for partial success.
|
||||
// See https://opentelemetry.io/docs/specs/otlp/#partial-success-1.
|
||||
google::protobuf::Arena arena;
|
||||
auto* response = MetricsResponse::default_instance().New(&arena);
|
||||
[[maybe_unused]] auto deserialized = response->ParseFromString(responseMsg.body());
|
||||
ASSERT(deserialized);
|
||||
|
||||
if (response->has_partial_success()) {
|
||||
const auto& ps = response->partial_success();
|
||||
const auto& msg = ps.error_message();
|
||||
if (ps.rejected_data_points() > 0 || !msg.empty()) {
|
||||
Log(LogWarning, "OTelExporter")
|
||||
<< "OpenTelemetry backend reported partial success: " << (msg.empty() ? "<none>" : msg)
|
||||
<< " (" << ps.rejected_data_points() << " metric data points rejected).";
|
||||
}
|
||||
}
|
||||
} else if (IsRetryableExportError(responseMsg.result())) {
|
||||
uint64_t throttleSeconds = 0;
|
||||
if (auto throttle = responseMsg[http::field::retry_after]; !throttle.empty()) {
|
||||
try {
|
||||
throttleSeconds = boost::lexical_cast<uint64_t>(throttle);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogWarning, "OTelExporter")
|
||||
<< "Failed to parse 'Retry-After' header from OpenTelemetry backend response: " << ex.what();
|
||||
}
|
||||
}
|
||||
BOOST_THROW_EXCEPTION(RetryableExportError{throttleSeconds});
|
||||
} else {
|
||||
Log(LogWarning, "OTelExporter")
|
||||
<< "OpenTelemetry backend responded with non-success and non-retryable status code "
|
||||
<< responseMsg.result_int() << " (" << responseMsg.reason() << ").\n" << responseMsg.body();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the exporting state and notify waiters.
|
||||
*
|
||||
* This method resets the internal exporting state to indicate that no export is currently
|
||||
* in progress. It then notifies either one or all waiters waiting for the export to complete,
|
||||
* based on the @c notifyAll parameter.
|
||||
*
|
||||
* @param notifyAll If true, notifies all waiters; otherwise, notifies only one waiter.
|
||||
*/
|
||||
void OTel::ResetExporting(bool notifyAll)
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
m_Exporting = false;
|
||||
}
|
||||
if (notifyAll) {
|
||||
m_ExportCV.notify_all();
|
||||
} else {
|
||||
m_ExportCV.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given OTel metric name according to OTel naming conventions[^1].
|
||||
* Here's the ABNF definition for reference:
|
||||
* @verbatim
|
||||
* instrument-name = ALPHA 0*254 ("_" / "." / "-" / "/" / ALPHA / DIGIT)
|
||||
* ALPHA = %x41-5A / %x61-7A; A-Z / a-z
|
||||
* DIGIT = %x30-39 ; 0-9
|
||||
* @endverbatim
|
||||
*
|
||||
* @param name The metric name to validate.
|
||||
*
|
||||
* @throws std::invalid_argument if the metric name is invalid.
|
||||
*
|
||||
* [^1]: https://opentelemetry.io/docs/specs/otel/metrics/api/#instrument-name-syntax
|
||||
*/
|
||||
void OTel::ValidateName(std::string_view name)
|
||||
{
|
||||
if (name.empty() || name.size() > 255) {
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("OTel instrument name must be between 1 and 255 characters long."));
|
||||
}
|
||||
|
||||
auto isAlpha = [](char c) { return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); };
|
||||
auto isDigit = [](char c) { return '0' <= c && c <= '9'; };
|
||||
for (std::size_t i = 0; i < name.size(); ++i) {
|
||||
auto c = name[i];
|
||||
if (i == 0 && !isAlpha(c)) {
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("OTel instrument name must start with an alphabetic character."));
|
||||
}
|
||||
if (!isAlpha(c) && !isDigit(c) && c != '_' && c != '.' && c != '-' && c != '/') {
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument(
|
||||
"OTel instrument name contains invalid character '" + std::string(1, c) + "'."
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given OTel attribute key-value pair in the provided @c Attribute Protobuf object.
|
||||
*
|
||||
* This method sets the given key-value pair in the provided KeyValue Protobuf object according to
|
||||
* OTel specifications[^1]. While the OTel specs[^2] allows a wider range of attr value types, we
|
||||
* only support the most common/scalar types (Boolean, Number (double), and String) for simplicity.
|
||||
*
|
||||
* @param attr The OTel attribute Protobuf object to set the value for.
|
||||
* @param key The attribute key to set. Must not be empty.
|
||||
* @param value The Value object containing the value to set in the attribute.
|
||||
*
|
||||
* @throws std::invalid_argument if key is empty or if @c Value represents an unsupported attribute value type.
|
||||
*
|
||||
* [^1]: https://opentelemetry.io/docs/specs/otel/common/#attribute
|
||||
* [^2]: https://opentelemetry.io/docs/specs/otel/common/#anyvalue
|
||||
*/
|
||||
template <typename Key, typename AttrVal, typename>
|
||||
void OTel::SetAttribute(Attribute& attr, Key&& key, AttrVal&& value)
|
||||
{
|
||||
if (begin(key) == end(key)) {
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("OTel attribute key must not be empty."));
|
||||
}
|
||||
|
||||
if constexpr (std::is_rvalue_reference_v<Key> && std::is_same_v<std::decay_t<Key>, String>) {
|
||||
attr.set_key(std::move(key.GetData()));
|
||||
} else {
|
||||
attr.set_key(std::string{std::forward<Key>(key)});
|
||||
}
|
||||
|
||||
constexpr bool isRvalReference = std::is_rvalue_reference_v<AttrVal>;
|
||||
if constexpr (isRvalReference && std::is_same_v<std::decay_t<AttrVal>, String>) {
|
||||
attr.mutable_value()->set_string_value(std::move(value.GetData()));
|
||||
} else if constexpr (std::is_constructible_v<std::string, AttrVal>) {
|
||||
attr.mutable_value()->set_string_value(std::string{std::forward<AttrVal>(value)});
|
||||
} else {
|
||||
switch (value.GetType()) {
|
||||
case ValueBoolean:
|
||||
attr.mutable_value()->set_bool_value(value.template Get<bool>());
|
||||
break;
|
||||
case ValueNumber:
|
||||
attr.mutable_value()->set_double_value(value.template Get<double>());
|
||||
break;
|
||||
case ValueString:
|
||||
if (isRvalReference) {
|
||||
attr.mutable_value()->set_string_value(std::move(value.template Get<String>().GetData()));
|
||||
} else {
|
||||
attr.mutable_value()->set_string_value(value.template Get<String>().GetData());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument(
|
||||
"OTel attribute value must be of type Boolean, Number, or String, got '" + value.GetTypeName() + "'."
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a data point in the given OTel Gauge metric stream with the provided value, timestamps, and attributes.
|
||||
*
|
||||
* This method adds a new data point to the provided Gauge Protobuf object with the given value, start and end
|
||||
* timestamps, and a set of attributes. The value can be either an int64_t or a double, depending on the type
|
||||
* of the Gauge. The timestamps are expected to be in seconds and will be converted to nanoseconds as required
|
||||
* by OTel specifications. The attributes are provided as a map of key-value pairs and will be set in the data
|
||||
* point according to OTel attribute specs.
|
||||
*
|
||||
* @tparam T The type of the data point value, which must be either int64_t or double.
|
||||
*
|
||||
* @param gauge The Gauge Protobuf object to record the data point in.
|
||||
* @param data The value of the data point to record.
|
||||
* @param start The start timestamp of the data point in seconds.
|
||||
* @param end The end timestamp of the data point in seconds.
|
||||
* @param attrs A map of attribute key-value pairs to set in the data point.
|
||||
*
|
||||
* @return The size in bytes of the recorded data point after serialization.
|
||||
*
|
||||
* @throws std::invalid_argument if any attribute key is empty or has an unsupported value type.
|
||||
*/
|
||||
template<typename T, typename>
|
||||
std::size_t OTel::Record(Gauge& gauge, T data, double start, double end, AttrsMap attrs)
|
||||
{
|
||||
namespace ch = std::chrono;
|
||||
|
||||
auto* dataPoint = gauge.add_data_points();
|
||||
if constexpr (std::is_same_v<T, double>) {
|
||||
dataPoint->set_as_double(data);
|
||||
} else {
|
||||
dataPoint->set_as_int(data);
|
||||
}
|
||||
|
||||
dataPoint->set_start_time_unix_nano(
|
||||
static_cast<uint64_t>(ch::duration_cast<ch::nanoseconds>(ch::duration<double>(start)).count())
|
||||
);
|
||||
dataPoint->set_time_unix_nano(
|
||||
static_cast<uint64_t>(ch::duration_cast<ch::nanoseconds>(ch::duration<double>(end)).count())
|
||||
);
|
||||
|
||||
while (!attrs.empty()) {
|
||||
auto* attr = dataPoint->add_attributes();
|
||||
auto node = attrs.extract(attrs.begin());
|
||||
SetAttribute(*attr, std::move(node.key()), std::move(node.mapped()));
|
||||
}
|
||||
return dataPoint->ByteSizeLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given HTTP status code represents a retryable export error as per OTel specs[^1].
|
||||
*
|
||||
* @param status The HTTP status code to check.
|
||||
*
|
||||
* @return true if the status code indicates a retryable error; false otherwise.
|
||||
*
|
||||
* [^1]: https://opentelemetry.io/docs/specs/otlp/#retryable-response-codes
|
||||
*/
|
||||
bool OTel::IsRetryableExportError(const http::status status)
|
||||
{
|
||||
return status == http::status::too_many_requests
|
||||
|| status == http::status::bad_gateway
|
||||
|| status == http::status::service_unavailable
|
||||
|| status == http::status::gateway_timeout;
|
||||
}
|
||||
|
||||
AsioProtobufOutStream::AsioProtobufOutStream(const AsioTlsOrTcpStream& stream, const OTelConnInfo& connInfo, boost::asio::yield_context yc)
|
||||
: m_Writer{stream}, m_YieldContext{std::move(yc)}
|
||||
{
|
||||
m_Writer.method(http::verb::post);
|
||||
m_Writer.target(connInfo.MetricsEndpoint);
|
||||
m_Writer.set(http::field::host, connInfo.Host + ":" + std::to_string(connInfo.Port));
|
||||
m_Writer.set(http::field::content_type, "application/x-protobuf");
|
||||
if (!connInfo.BasicAuth.IsEmpty()) {
|
||||
m_Writer.set(http::field::authorization, "Basic " + connInfo.BasicAuth);
|
||||
}
|
||||
m_Writer.StartStreaming();
|
||||
}
|
||||
|
||||
bool AsioProtobufOutStream::Next(void** data, int* size)
|
||||
{
|
||||
if (m_Buffered == l_BufferSize) {
|
||||
Flush();
|
||||
}
|
||||
// Prepare a new buffer segment that the Protobuf serializer can write into.
|
||||
// The buffer size is fixed to l_BufferSize, and as seen above, we flush if the previous buffer
|
||||
// segment was fully used (which is always the case on each Next call after the initial one), so
|
||||
// we'll end up reusing the same memory region for each Next call because when we flush, we also
|
||||
// consume the committed data, and that region becomes writable again.
|
||||
auto buf = m_Writer.Prepare(l_BufferSize - m_Buffered);
|
||||
*data = buf.data();
|
||||
*size = static_cast<int>(l_BufferSize);
|
||||
m_Buffered = l_BufferSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsioProtobufOutStream::BackUp(int count)
|
||||
{
|
||||
// Make sure we've not already finalized the HTTP body because BackUp
|
||||
// is supposed to be called only after a preceding (final) Next call.
|
||||
ASSERT(!m_Writer.Done());
|
||||
ASSERT(static_cast<std::size_t>(count) <= m_Buffered);
|
||||
ASSERT(m_Buffered == l_BufferSize);
|
||||
// If the last prepared buffer segment was not fully used, we need to adjust the buffered size,
|
||||
// so that we don't commit unused memory regions with the below Flush() call. If count is zero,
|
||||
// this adjustment is a no-op, and indicates that the entire buffer was used and there won't be
|
||||
// any subsequent Next calls anymore (i.e., the Protobuf serialization is complete).
|
||||
m_Buffered -= count;
|
||||
Flush(true);
|
||||
}
|
||||
|
||||
int64_t AsioProtobufOutStream::ByteCount() const
|
||||
{
|
||||
return m_Pos + static_cast<int64_t>(m_Buffered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush any buffered data to the underlying Asio stream.
|
||||
*
|
||||
* If the `finish` parameter is set to true, it indicates that no more data will
|
||||
* be buffered/generated, and the HTTP body will be finalized accordingly.
|
||||
*
|
||||
* @param finish Whether this is the final flush operation.
|
||||
*/
|
||||
void AsioProtobufOutStream::Flush(bool finish)
|
||||
{
|
||||
ASSERT(m_Buffered > 0 || finish);
|
||||
m_Writer.Commit(m_Buffered);
|
||||
m_Writer.Flush(m_YieldContext, finish);
|
||||
m_Pos += static_cast<int64_t>(m_Buffered);
|
||||
m_Buffered = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the underlying HTTP request writer has completed writing.
|
||||
*
|
||||
* @return true if the writer has finished writing; false otherwise.
|
||||
*/
|
||||
bool AsioProtobufOutStream::WriterDone()
|
||||
{
|
||||
return m_Writer.Done();
|
||||
}
|
||||
190
lib/otel/otel.hpp
Normal file
190
lib/otel/otel.hpp
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "base/io-engine.hpp"
|
||||
#include "base/tlsstream.hpp"
|
||||
#include "base/shared.hpp"
|
||||
#include "base/shared-object.hpp"
|
||||
#include "base/string.hpp"
|
||||
#include "remote/httpmessage.hpp"
|
||||
#include "otel/opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h"
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <google/protobuf/io/zero_copy_stream.h>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
|
||||
/**
|
||||
* Connection parameters for connecting to an OpenTelemetry collector endpoint.
|
||||
*
|
||||
* @ingroup otel
|
||||
*/
|
||||
struct OTelConnInfo
|
||||
{
|
||||
bool EnableTls{false};
|
||||
bool VerifyPeerCertificate{true};
|
||||
int Port;
|
||||
String Host;
|
||||
String TlsCaCrt;
|
||||
String TlsCrt;
|
||||
String TlsKey;
|
||||
String MetricsEndpoint;
|
||||
String BasicAuth; // Base64-encoded "username:password" string for basic authentication.
|
||||
};
|
||||
|
||||
/**
|
||||
* OTel implements the OpenTelemetry Protocol (OTLP) exporter.
|
||||
*
|
||||
* This class manages the connection to an OpenTelemetry collector or compatible backend and
|
||||
* handles exporting (currently only metrics) in OTLP Protobuf format over HTTP. It supports
|
||||
* TLS connections, basic authentication, and implements retry logic for transient errors as
|
||||
* per OTel specs.
|
||||
*
|
||||
* @ingroup otel
|
||||
*/
|
||||
class OTel : public SharedObject
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(OTel);
|
||||
|
||||
// Protobuf request and response types for exporting metrics.
|
||||
using MetricsRequest = opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest;
|
||||
using MetricsResponse = opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceResponse;
|
||||
// Protobuf attribute type used for OTel resource and data point attributes.
|
||||
using Attribute = opentelemetry::proto::common::v1::KeyValue;
|
||||
// Protobuf Gauge type used for representing OTel Gauge metric streams.
|
||||
using Gauge = opentelemetry::proto::metrics::v1::Gauge;
|
||||
|
||||
/**
|
||||
* Represents a collection of OTel attributes[^1] as key-value pairs.
|
||||
*
|
||||
* [^1]: https://opentelemetry.io/docs/specs/otel/common/#attribute
|
||||
*/
|
||||
using AttrsMap = std::map<String, Value>;
|
||||
|
||||
explicit OTel(OTelConnInfo& connInfo);
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
void Export(std::unique_ptr<MetricsRequest>&& request);
|
||||
|
||||
bool Exporting() const
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
return m_Exporting;
|
||||
}
|
||||
|
||||
bool Stopped() const { return m_Stopped.load(); }
|
||||
|
||||
static void PopulateResourceAttrs(const std::unique_ptr<opentelemetry::proto::metrics::v1::ResourceMetrics>& rm);
|
||||
static void ValidateName(std::string_view name);
|
||||
template<typename Key, typename AttrVal, typename = std::enable_if_t<
|
||||
std::is_constructible_v<std::string, Key> && (
|
||||
std::is_same_v<std::decay_t<AttrVal>, Value> ||
|
||||
std::is_constructible_v<std::string, AttrVal>
|
||||
)
|
||||
>>
|
||||
static void SetAttribute(Attribute& attr, Key&& key, AttrVal&& value);
|
||||
static bool IsRetryableExportError(boost::beast::http::status status);
|
||||
|
||||
template<typename T, typename = std::enable_if_t<
|
||||
std::is_same_v<std::decay_t<T>, int64_t> || std::is_same_v<std::decay_t<T>, double>>
|
||||
>
|
||||
[[nodiscard]] static std::size_t Record(Gauge& gauge, T data, double start, double end, AttrsMap attrs);
|
||||
|
||||
private:
|
||||
OTel(OTelConnInfo& connInfo, boost::asio::io_context& io);
|
||||
|
||||
void Connect(boost::asio::yield_context& yc);
|
||||
void ExportLoop(boost::asio::yield_context& yc);
|
||||
void ExportImpl(boost::asio::yield_context& yc) const;
|
||||
|
||||
void ResetExporting(bool notifyAll = false);
|
||||
|
||||
const OTelConnInfo m_ConnInfo;
|
||||
std::optional<AsioTlsOrTcpStream> m_Stream;
|
||||
Shared<boost::asio::ssl::context>::Ptr m_TlsContext;
|
||||
boost::asio::io_context::strand m_Strand;
|
||||
|
||||
AsioConditionVariable m_ExportAsioCV; // Event to signal when a new export request is available.
|
||||
// Timer for scheduling retries of failed exports and reconnection attempts.
|
||||
boost::asio::steady_timer m_RetryExportAndConnTimer;
|
||||
|
||||
// Mutex and condition variable for synchronizing concurrent export requests.
|
||||
mutable std::mutex m_Mutex;
|
||||
std::condition_variable m_ExportCV;
|
||||
std::unique_ptr<MetricsRequest> m_Request; // Current export request being processed (if any).
|
||||
bool m_Exporting; // Whether an export operation is in progress.
|
||||
std::atomic_bool m_Stopped; // Whether someone has requested to stop the exporter.
|
||||
};
|
||||
extern template std::size_t OTel::Record(Gauge&, int64_t, double, double, AttrsMap);
|
||||
extern template std::size_t OTel::Record(Gauge&, double, double, double, AttrsMap);
|
||||
extern template void OTel::SetAttribute(Attribute&, std::string_view&&, String&&);
|
||||
extern template void OTel::SetAttribute(Attribute&, String&&, Value&);
|
||||
|
||||
/**
|
||||
* A zero-copy output stream that writes directly to an Asio [TLS] stream.
|
||||
*
|
||||
* This class implements the @c google::protobuf::io::ZeroCopyOutputStream interface, allowing Protobuf
|
||||
* serializers to write data directly to an Asio [TLS] stream without unnecessary copying of data. It
|
||||
* doesn't buffer data internally, but instead writes it in chunks to the underlying stream using an HTTP
|
||||
* request writer (@c HttpRequestWriter) in a Protobuf binary format. It is not safe to be reused across
|
||||
* multiple export calls.
|
||||
*
|
||||
* @ingroup otel
|
||||
*/
|
||||
class AsioProtobufOutStream final : public google::protobuf::io::ZeroCopyOutputStream
|
||||
{
|
||||
public:
|
||||
AsioProtobufOutStream(const AsioTlsOrTcpStream& stream, const OTelConnInfo& connInfo, boost::asio::yield_context yc);
|
||||
|
||||
bool Next(void** data, int* size) override;
|
||||
void BackUp(int count) override;
|
||||
int64_t ByteCount() const override;
|
||||
|
||||
bool WriterDone();
|
||||
|
||||
private:
|
||||
void Flush(bool finish = false);
|
||||
|
||||
int64_t m_Pos{0}; // Monotonically increasing byte position in the stream (excluding m_Buffered bytes).
|
||||
std::size_t m_Buffered{0}; // Number of uncommitted bytes currently buffered.
|
||||
OutgoingHttpRequest m_Writer;
|
||||
boost::asio::yield_context m_YieldContext; // Yield context for async operations.
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception class representing a retryable export error.
|
||||
*
|
||||
* This exception is thrown when an export attempt to an OpenTelemetry collector fails
|
||||
* with a retryable error status. It carries an optional HTTP throttle[^1] duration indicating
|
||||
* how long to wait before retrying the export.
|
||||
*
|
||||
* [^1]: https://opentelemetry.io/docs/specs/otlp/#otlphttp-throttling
|
||||
*
|
||||
* @ingroup otel
|
||||
*/
|
||||
struct RetryableExportError : std::exception
|
||||
{
|
||||
explicit RetryableExportError(uint64_t throttle): m_Throttle{throttle}
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] std::chrono::seconds Throttle() const { return m_Throttle; }
|
||||
const char* what() const noexcept override
|
||||
{
|
||||
return "OTel::RetryableExportError()";
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::seconds m_Throttle;
|
||||
};
|
||||
|
||||
} // namespace icinga
|
||||
|
|
@ -22,6 +22,13 @@ set(perfdata_SOURCES
|
|||
perfdatawriterconnection.cpp perfdatawriterconnection.hpp
|
||||
)
|
||||
|
||||
if(ICINGA2_WITH_OPENTELEMETRY)
|
||||
mkclass_target(otlpmetricswriter.ti otlpmetricswriter-ti.cpp otlpmetricswriter-ti.hpp)
|
||||
list(APPEND perfdata_SOURCES
|
||||
otlpmetricswriter.cpp otlpmetricswriter.hpp otlpmetricswriter-ti.hpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_UNITY_BUILD)
|
||||
mkunity_target(perfdata perfdata perfdata_SOURCES)
|
||||
endif()
|
||||
|
|
@ -29,6 +36,20 @@ endif()
|
|||
add_library(perfdata OBJECT ${perfdata_SOURCES})
|
||||
|
||||
add_dependencies(perfdata base config icinga)
|
||||
if(ICINGA2_WITH_OPENTELEMETRY)
|
||||
add_dependencies(perfdata otel)
|
||||
# All the Protobuf generated files within the otel target use relative include paths that won't be
|
||||
# resolved unless we also add the include directories of the otel target. Meaning, we include some
|
||||
# of the header files (not the generated ones) from otel within the otlpwriter and these headers
|
||||
# again include the generated headers and the generated headers in return include other generated
|
||||
# headers using relative paths like this:
|
||||
# #include "opentelemetry/proto/metrics/v1/metrics.pb.h"
|
||||
#
|
||||
# This path can only be resolved if the parent directory of "opentelemetry" is added to the compiler's
|
||||
# include search paths, which is done by the CMakefile of the otel target and we only need to propagate
|
||||
# its include directories to the perfdata target.
|
||||
target_include_directories(perfdata PUBLIC $<TARGET_PROPERTY:otel,INCLUDE_DIRECTORIES>)
|
||||
endif()
|
||||
|
||||
set_target_properties (
|
||||
perfdata PROPERTIES
|
||||
|
|
@ -65,6 +86,13 @@ install_if_not_exists(
|
|||
${ICINGA2_CONFIGDIR}/features-available
|
||||
)
|
||||
|
||||
if(ICINGA2_WITH_OPENTELEMETRY)
|
||||
install_if_not_exists(
|
||||
${PROJECT_SOURCE_DIR}/etc/icinga2/features-available/otlpmetrics.conf
|
||||
${ICINGA2_CONFIGDIR}/features-available
|
||||
)
|
||||
endif()
|
||||
|
||||
install_if_not_exists(
|
||||
${PROJECT_SOURCE_DIR}/etc/icinga2/features-available/perfdata.conf
|
||||
${ICINGA2_CONFIGDIR}/features-available
|
||||
|
|
|
|||
444
lib/perfdata/otlpmetricswriter.cpp
Normal file
444
lib/perfdata/otlpmetricswriter.cpp
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "perfdata/otlpmetricswriter.hpp"
|
||||
#include "perfdata/otlpmetricswriter-ti.cpp"
|
||||
#include "base/base64.hpp"
|
||||
#include "base/defer.hpp"
|
||||
#include "base/json.hpp"
|
||||
#include "base/object-packer.hpp"
|
||||
#include "base/perfdatavalue.hpp"
|
||||
#include "base/statsfunction.hpp"
|
||||
#include "icinga/checkable.hpp"
|
||||
#include "icinga/checkcommand.hpp"
|
||||
#include "icinga/macroprocessor.hpp"
|
||||
#include "icinga/service.hpp"
|
||||
#include <future>
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
REGISTER_TYPE(OTLPMetricsWriter);
|
||||
|
||||
REGISTER_STATSFUNCTION(OTLPMetricsWriter, &OTLPMetricsWriter::StatsFunc);
|
||||
|
||||
// Represent our currently supported metric streams.
|
||||
//
|
||||
// Note: These and all other attribute keys used within this compilation unit follow
|
||||
// the OTel general naming guidelines[^1] and conventions[^2].
|
||||
//
|
||||
// [^1]: https://opentelemetry.io/docs/specs/semconv/general/metrics/#general-guidelines
|
||||
// [^2]: https://opentelemetry.io/docs/specs/semconv/general/naming
|
||||
static constexpr std::string_view l_PerfdataMetric = "state_check.perfdata";
|
||||
static constexpr std::string_view l_ThresholdMetric = "state_check.threshold";
|
||||
|
||||
void OTLPMetricsWriter::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata)
|
||||
{
|
||||
DictionaryData statusData;
|
||||
for (const Ptr& otlpWriter : ConfigType::GetObjectsByType<OTLPMetricsWriter>()) {
|
||||
std::size_t workQueueSize = otlpWriter->m_WorkQueue.GetLength();
|
||||
double workQueueItemRate = otlpWriter->m_WorkQueue.GetTaskCount(60) / 60.0;
|
||||
std::size_t dataPointsCount = otlpWriter->m_DataPointsCount.load(std::memory_order_relaxed);
|
||||
uint64_t messageSize = otlpWriter->m_RecordedBytes.load(std::memory_order_relaxed);
|
||||
|
||||
const auto name = otlpWriter->GetName();
|
||||
statusData.emplace_back(name, new Dictionary{
|
||||
{"work_queue_items", workQueueSize},
|
||||
{"work_queue_item_rate", workQueueItemRate},
|
||||
{"data_buffer_items", dataPointsCount},
|
||||
{"data_buffer_bytes", messageSize},
|
||||
});
|
||||
|
||||
perfdata->Add(new PerfdataValue("otlpmetricswriter_" + name + "_work_queue_items", workQueueSize, true));
|
||||
perfdata->Add(new PerfdataValue("otlpmetricswriter_" + name + "_work_queue_item_rate", workQueueItemRate));
|
||||
perfdata->Add(new PerfdataValue("otlpmetricswriter_" + name + "_data_buffer_items", dataPointsCount, true));
|
||||
perfdata->Add(new PerfdataValue("otlpmetricswriter_" + name + "_data_buffer_bytes", messageSize, false, "bytes"));
|
||||
}
|
||||
status->Set("otlpmetricswriter", new Dictionary{std::move(statusData)});
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::OnConfigLoaded()
|
||||
{
|
||||
ObjectImpl::OnConfigLoaded();
|
||||
|
||||
m_WorkQueue.SetName("OTLPMetricsWriter, " + GetName());
|
||||
|
||||
if (!GetEnableHa()) {
|
||||
Log(LogDebug, "OTLPMetricsWriter")
|
||||
<< "HA functionality disabled. Won't pause connection: " << GetName();
|
||||
|
||||
SetHAMode(HARunEverywhere);
|
||||
} else {
|
||||
SetHAMode(HARunOnce);
|
||||
}
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::Start(bool runtimeCreated)
|
||||
{
|
||||
ObjectImpl::Start(runtimeCreated);
|
||||
|
||||
OTelConnInfo connInfo;
|
||||
connInfo.EnableTls = GetEnableTls();
|
||||
connInfo.VerifyPeerCertificate = !GetTlsInsecureNoverify();
|
||||
connInfo.Host = GetHost();
|
||||
connInfo.Port = GetPort();
|
||||
connInfo.TlsCaCrt = GetTlsCaFile();
|
||||
connInfo.TlsCrt = GetTlsCertFile();
|
||||
connInfo.TlsKey = GetTlsKeyFile();
|
||||
connInfo.MetricsEndpoint = GetMetricsEndpoint();
|
||||
if (auto auth = GetBasicAuth(); auth) {
|
||||
connInfo.BasicAuth = Base64::Encode(auth->Get("username") + ":" + auth->Get("password"));
|
||||
}
|
||||
|
||||
m_Exporter.reset(new OTel{connInfo});
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::Resume()
|
||||
{
|
||||
ObjectImpl::Resume();
|
||||
|
||||
Log(LogInformation, "OTLPMetricsWriter")
|
||||
<< "'" << GetName() << "' resumed.";
|
||||
|
||||
m_WorkQueue.SetExceptionCallback([](boost::exception_ptr exp) {
|
||||
Log(LogCritical, "OTLPMetricsWriter")
|
||||
<< "Exception while producing OTel metric: " << DiagnosticInformation(exp);
|
||||
});
|
||||
|
||||
m_FlushTimer = Timer::Create();
|
||||
m_FlushTimer->SetInterval(GetFlushInterval());
|
||||
m_FlushTimer->OnTimerExpired.connect([this](const Timer* const&) {
|
||||
if (m_TimerFlushInProgress.exchange(true, std::memory_order_relaxed)) {
|
||||
// Previous timer-initiated flush still in progress, skip this one.
|
||||
return;
|
||||
}
|
||||
m_WorkQueue.Enqueue([this] {
|
||||
Defer resetTimerFlag{[this] { m_TimerFlushInProgress.store(false, std::memory_order_relaxed); }};
|
||||
Flush(true);
|
||||
});
|
||||
});
|
||||
m_FlushTimer->Start();
|
||||
m_Exporter->Start();
|
||||
|
||||
m_CheckResultsSlot = Checkable::OnNewCheckResult.connect([this](
|
||||
const Checkable::Ptr& checkable,
|
||||
const CheckResult::Ptr& cr,
|
||||
const MessageOrigin::Ptr&
|
||||
) {
|
||||
CheckResultHandler(checkable, cr);
|
||||
});
|
||||
m_ActiveChangedSlot = OnActiveChanged.connect([this](const ConfigObject::Ptr& obj, const Value&) {
|
||||
auto checkable = dynamic_pointer_cast<Checkable>(obj);
|
||||
if (!checkable || checkable->IsActive()) {
|
||||
return;
|
||||
}
|
||||
m_WorkQueue.Enqueue([this, checkable] { m_Metrics.erase(checkable.get()); });
|
||||
});
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::Pause()
|
||||
{
|
||||
m_CheckResultsSlot.disconnect();
|
||||
m_ActiveChangedSlot.disconnect();
|
||||
|
||||
m_FlushTimer->Stop(true);
|
||||
|
||||
std::promise<void> promise;
|
||||
auto future = promise.get_future();
|
||||
m_WorkQueue.Enqueue([this, &promise] {
|
||||
Flush();
|
||||
promise.set_value();
|
||||
}, PriorityLow);
|
||||
|
||||
if (auto status = future.wait_for(std::chrono::seconds(GetDisconnectTimeout())); status != std::future_status::ready) {
|
||||
Log(LogWarning, "OTLPMetricsWriter")
|
||||
<< "Disconnect timeout reached while flushing OTel metrics, discarding '" << m_DataPointsCount
|
||||
<< "' data points ('" << m_RecordedBytes << "' bytes).";
|
||||
}
|
||||
m_Exporter->Stop();
|
||||
m_WorkQueue.Join();
|
||||
|
||||
m_Metrics.clear();
|
||||
|
||||
Log(LogInformation, "OTLPMetricsWriter")
|
||||
<< "'" << GetName() << "' paused.";
|
||||
|
||||
ObjectImpl::Pause();
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
|
||||
{
|
||||
if (!IcingaApplication::GetInstance()->GetEnablePerfdata() || !checkable->GetEnablePerfdata() || !cr->GetPerformanceData()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_WorkQueue.Enqueue([this, checkable, cr] {
|
||||
if (m_Exporter->Stopped()) {
|
||||
return;
|
||||
}
|
||||
CONTEXT("Processing check result for '" << checkable->GetName() << "'.");
|
||||
|
||||
auto startTime = cr->GetScheduleStart();
|
||||
auto endTime = cr->GetExecutionEnd();
|
||||
|
||||
Array::Ptr perfdata = cr->GetPerformanceData();
|
||||
ObjectLock olock(perfdata);
|
||||
for (const Value& val : perfdata) {
|
||||
PerfdataValue::Ptr pdv;
|
||||
if (val.IsObjectType<PerfdataValue>()) {
|
||||
pdv = val;
|
||||
} else {
|
||||
try {
|
||||
pdv = PerfdataValue::Parse(val);
|
||||
} catch (const std::exception&) {
|
||||
Log(LogWarning, "OTLPMetricsWriter")
|
||||
<< "Ignoring invalid perfdata for checkable '" << checkable->GetName() << "' and command '"
|
||||
<< checkable->GetCheckCommand()->GetName() << "' with value: " << val;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
OTel::AttrsMap attrs{{"perfdata_label", pdv->GetLabel()}};
|
||||
if (auto unit = pdv->GetUnit(); !unit.IsEmpty()) {
|
||||
attrs.emplace("unit", std::move(unit));
|
||||
}
|
||||
AddBytesAndFlushIfNeeded(Record(checkable, cr, l_PerfdataMetric, pdv->GetValue(), startTime, endTime, std::move(attrs)));
|
||||
|
||||
if (GetEnableSendThresholds()) {
|
||||
std::array<std::pair<String, Value>, 4> thresholds{{
|
||||
{"critical", pdv->GetCrit()},
|
||||
{"warning", pdv->GetWarn()},
|
||||
{"min", pdv->GetMin()},
|
||||
{"max", pdv->GetMax()},
|
||||
}};
|
||||
for (auto& [label, threshold] : thresholds) {
|
||||
if (!threshold.IsEmpty()) {
|
||||
attrs = {
|
||||
{"perfdata_label", pdv->GetLabel()},
|
||||
{"threshold_type", std::move(label)},
|
||||
};
|
||||
AddBytesAndFlushIfNeeded(
|
||||
Record(
|
||||
checkable,
|
||||
cr,
|
||||
l_ThresholdMetric,
|
||||
Convert::ToDouble(threshold),
|
||||
startTime,
|
||||
endTime,
|
||||
std::move(attrs)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::Flush(bool fromTimer)
|
||||
{
|
||||
// If previous export is still in progress and this flush is requested from timer, skip it.
|
||||
// For manual flushes (e.g., due to reaching flush threshold), we want to block until
|
||||
// the previous export is done before returning to the caller (blocking is handled in OTel::Export()).
|
||||
if (fromTimer && m_Exporter->Exporting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log(LogDebug, "OTLPMetricsWriter")
|
||||
<< "Flushing OTel metrics to OpenTelemetry backend" << (fromTimer ? " (timer expired)." : ".");
|
||||
|
||||
auto request = std::make_unique<OTel::MetricsRequest>();
|
||||
for (auto& [checkable, resourceMetrics] : m_Metrics) {
|
||||
if (resourceMetrics) {
|
||||
request->mutable_resource_metrics()->AddAllocated(resourceMetrics.release());
|
||||
}
|
||||
}
|
||||
if (request->resource_metrics_size() == 0) {
|
||||
Log(LogDebug, "OTLPMetricsWriter")
|
||||
<< "Not flushing OTel metrics: No data points recorded.";
|
||||
return;
|
||||
}
|
||||
m_Exporter->Export(std::move(request));
|
||||
m_RecordedBytes.store(0, std::memory_order_relaxed);
|
||||
m_DataPointsCount.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::AddBytesAndFlushIfNeeded(std::size_t newBytes)
|
||||
{
|
||||
auto existingBytes = m_RecordedBytes.fetch_add(newBytes, std::memory_order_relaxed);
|
||||
if (auto bytes{existingBytes + newBytes}; bytes >= static_cast<uint64_t>(GetFlushThreshold())) {
|
||||
Log(LogDebug, "OTLPMetricsWriter")
|
||||
<< "Flush threshold reached, flushing '" << bytes << "' bytes of OTel metrics.";
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a data point for the specified OTel metric associated with the given configuration object.
|
||||
*
|
||||
* This method records a data point of type T for the specified metric name associated with the
|
||||
* provided configuration object. If the metric does not exist for the object, it is created.
|
||||
*
|
||||
* @tparam T The type of the data point to record (e.g., int64_t, double).
|
||||
*
|
||||
* @param checkable The configuration object to associate the metric with.
|
||||
* @param cr The check result associated with the metric data point, used for macro resolution in attributes.
|
||||
* @param metric The OTel metric enum value indicating which metric stream to record the data point for.
|
||||
* @param value The data point value to record.
|
||||
* @param startTime The start time of the data point in seconds.
|
||||
* @param endTime The end time of the data point in seconds.
|
||||
* @param attrs The attributes associated with the data point.
|
||||
*
|
||||
* @return The number of bytes recorded for this data point, which contributes to the flush threshold.
|
||||
*/
|
||||
template<typename T>
|
||||
std::size_t OTLPMetricsWriter::Record(
|
||||
const Checkable::Ptr& checkable,
|
||||
const CheckResult::Ptr& cr,
|
||||
std::string_view metric,
|
||||
T value,
|
||||
double startTime,
|
||||
double endTime,
|
||||
OTel::AttrsMap attrs
|
||||
)
|
||||
{
|
||||
std::size_t bytes = 0;
|
||||
auto& resourceMetrics = m_Metrics[checkable.get()];
|
||||
if (!resourceMetrics) {
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
resourceMetrics = std::make_unique<opentelemetry::proto::metrics::v1::ResourceMetrics>();
|
||||
OTel::PopulateResourceAttrs(resourceMetrics);
|
||||
|
||||
auto* resource = resourceMetrics->mutable_resource();
|
||||
auto* attr = resource->add_attributes();
|
||||
OTel::SetAttribute(*attr, "service.namespace"sv, GetServiceNamespace());
|
||||
|
||||
auto [host, service] = GetHostService(checkable);
|
||||
attr = resource->add_attributes();
|
||||
OTel::SetAttribute(*attr, "icinga2.host.name"sv, host->GetName());
|
||||
|
||||
// Add entity reference (https://opentelemetry.io/docs/specs/otel/entities/data-model/).
|
||||
auto* entity = resource->add_entity_refs();
|
||||
entity->mutable_id_keys()->Add("icinga2.host.name");
|
||||
if (service) {
|
||||
entity->set_type("service");
|
||||
entity->mutable_id_keys()->Add("icinga2.service.name");
|
||||
|
||||
attr = resource->add_attributes();
|
||||
OTel::SetAttribute(*attr, "icinga2.service.name"sv, service->GetShortName());
|
||||
} else {
|
||||
entity->set_type("host");
|
||||
}
|
||||
attr = resource->add_attributes();
|
||||
OTel::SetAttribute(*attr, "icinga2.command.name"sv, checkable->GetCheckCommand()->GetName());
|
||||
|
||||
if (Dictionary::Ptr tmpl = service ? GetServiceResourceAttributes() : GetHostResourceAttributes(); tmpl) {
|
||||
MacroProcessor::ResolverList resolvers{{"host", host}};
|
||||
if (service) {
|
||||
resolvers.emplace_back("service", service);
|
||||
}
|
||||
|
||||
ObjectLock olock(tmpl);
|
||||
for (const Dictionary::Pair& pair : tmpl) {
|
||||
String missingMacro;
|
||||
auto resolvedVal = MacroProcessor::ResolveMacros(pair.second, resolvers, cr, &missingMacro);
|
||||
if (missingMacro.IsEmpty()) {
|
||||
attr = resource->add_attributes();
|
||||
try {
|
||||
OTel::SetAttribute(*attr, "icinga2.custom." + pair.first, resolvedVal);
|
||||
} catch (const std::exception& ex) {
|
||||
Log(LogWarning, "OTLPMetricsWriter")
|
||||
<< "Ignoring invalid resource attribute '" << pair.first << "' for checkable '"
|
||||
<< checkable->GetName() << "': " << ex.what();
|
||||
// Remove the last attribute from the list which is the one we just attempted to set.
|
||||
resource->mutable_attributes()->RemoveLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bytes = resourceMetrics->ByteSizeLong();
|
||||
}
|
||||
|
||||
auto* sm = resourceMetrics->mutable_scope_metrics(0);
|
||||
auto* metrics = sm->mutable_metrics();
|
||||
auto it = std::find_if(metrics->begin(), metrics->end(), [metric](const auto& m) { return m.name() == metric; });
|
||||
OTel::Gauge* gaugePtr = nullptr;
|
||||
if (it == metrics->end()) {
|
||||
OTel::ValidateName(metric);
|
||||
auto* metricPtr = sm->add_metrics();
|
||||
metricPtr->set_name(std::string(metric));
|
||||
bytes += metricPtr->ByteSizeLong(); // Account for metric name size in bytes.
|
||||
gaugePtr = metricPtr->mutable_gauge();
|
||||
} else {
|
||||
gaugePtr = it->mutable_gauge();
|
||||
}
|
||||
bytes += OTel::Record(*gaugePtr, value, startTime, endTime, std::move(attrs));
|
||||
m_DataPointsCount.fetch_add(1, std::memory_order_relaxed);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::ValidatePort(const Lazy<int>& lvalue, const ValidationUtils& utils)
|
||||
{
|
||||
ObjectImpl::ValidatePort(lvalue, utils);
|
||||
if (auto p = lvalue(); p < 1 || p > 65535) {
|
||||
BOOST_THROW_EXCEPTION(ValidationError(this, {"port"}, "Port must be in the range 1-65535."));
|
||||
}
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::ValidateFlushInterval(const Lazy<int>& lvalue, const ValidationUtils& utils)
|
||||
{
|
||||
ObjectImpl::ValidateFlushInterval(lvalue, utils);
|
||||
if (lvalue() < 1) {
|
||||
BOOST_THROW_EXCEPTION(ValidationError(this, {"flush_interval"}, "Flush interval must be at least 1 second."));
|
||||
}
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::ValidateFlushThreshold(const Lazy<int64_t>& lvalue, const ValidationUtils& utils)
|
||||
{
|
||||
ObjectImpl::ValidateFlushThreshold(lvalue, utils);
|
||||
if (lvalue() < 1) {
|
||||
BOOST_THROW_EXCEPTION(ValidationError(this, {"flush_threshold"}, "Flush threshold must be at least 1."));
|
||||
}
|
||||
// Protobuf limits the size of messages to be serialiazed/deserialized to max 2GiB. Thus, we can't accept
|
||||
// a flush threshold that would exceed that limit with a reasonable safe margin of 10MiB for any other
|
||||
// overhead in the message not accounted for in @c m_RecordedBytes.
|
||||
// See https://protobuf.dev/programming-guides/proto-limits/#total.
|
||||
constexpr std::size_t maxMessageSize = 2ULL * 1024 * 1024 * 1024 - 10 * 1024 * 1024;
|
||||
if (static_cast<uint64_t>(lvalue()) > maxMessageSize) {
|
||||
BOOST_THROW_EXCEPTION(ValidationError(
|
||||
this,
|
||||
{"flush_threshold"},
|
||||
"Flush threshold too high, would exceed Protobuf message size limit of 2GiB (1.9GiB max allowed)."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::ValidateHostResourceAttributes(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
|
||||
{
|
||||
ObjectImpl::ValidateHostResourceAttributes(lvalue, utils);
|
||||
if (const auto& tags{lvalue()}; tags) {
|
||||
ValidateResourceAttributes(tags, "host_resource_attributes");
|
||||
}
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::ValidateServiceResourceAttributes(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
|
||||
{
|
||||
ObjectImpl::ValidateServiceResourceAttributes(lvalue, utils);
|
||||
if (const auto& tags{lvalue()}; tags) {
|
||||
ValidateResourceAttributes(tags, "service_resource_attributes");
|
||||
}
|
||||
}
|
||||
|
||||
void OTLPMetricsWriter::ValidateResourceAttributes(const Dictionary::Ptr& tmpl, const String& attrName)
|
||||
{
|
||||
ObjectLock olock(tmpl);
|
||||
for (const auto& pair : tmpl) {
|
||||
if (!MacroProcessor::ValidateMacroString(pair.second)) {
|
||||
BOOST_THROW_EXCEPTION(ValidationError(
|
||||
this,
|
||||
{attrName, pair.first},
|
||||
"Closing $ not found in macro format string '" + pair.second + "'."
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
65
lib/perfdata/otlpmetricswriter.hpp
Normal file
65
lib/perfdata/otlpmetricswriter.hpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "perfdata/otlpmetricswriter-ti.hpp"
|
||||
#include "base/workqueue.hpp"
|
||||
#include "icinga/checkable.hpp"
|
||||
#include "otel/otel.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
|
||||
class OTLPMetricsWriter final : public ObjectImpl<OTLPMetricsWriter>
|
||||
{
|
||||
public:
|
||||
DECLARE_OBJECT(OTLPMetricsWriter);
|
||||
DECLARE_OBJECTNAME(OTLPMetricsWriter);
|
||||
|
||||
static void StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata);
|
||||
|
||||
void Start(bool runtimeCreated) override;
|
||||
void OnConfigLoaded() override;
|
||||
void Resume() override;
|
||||
void Pause() override;
|
||||
|
||||
protected:
|
||||
void ValidatePort(const Lazy<int>& lvalue, const ValidationUtils& utils) override;
|
||||
void ValidateFlushInterval(const Lazy<int>& lvalue, const ValidationUtils& utils) override;
|
||||
void ValidateFlushThreshold(const Lazy<int64_t>& lvalue, const ValidationUtils& utils) override;
|
||||
void ValidateHostResourceAttributes(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils) override;
|
||||
void ValidateServiceResourceAttributes(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils) override;
|
||||
|
||||
private:
|
||||
void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr);
|
||||
void Flush(bool fromTimer = false);
|
||||
void AddBytesAndFlushIfNeeded(std::size_t newBytes = 0);
|
||||
void ValidateResourceAttributes(const Dictionary::Ptr& tmpl, const String& attrName);
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::size_t Record(
|
||||
const Checkable::Ptr& checkable,
|
||||
const CheckResult::Ptr& cr,
|
||||
std::string_view metric,
|
||||
T value,
|
||||
double startTime,
|
||||
double endTime,
|
||||
OTel::AttrsMap attrs
|
||||
);
|
||||
|
||||
std::atomic_uint64_t m_RecordedBytes{0}; // Total bytes recorded in the current OTel message.
|
||||
std::atomic_uint64_t m_DataPointsCount{0}; // Total data points recorded in the current OTel message.
|
||||
|
||||
// Checkables and their associated OTel ResourceMetrics that are being recorded for the current OTel message.
|
||||
std::unordered_map<Checkable*, std::unique_ptr<opentelemetry::proto::metrics::v1::ResourceMetrics>> m_Metrics;
|
||||
|
||||
WorkQueue m_WorkQueue{10'000'000, 1};
|
||||
boost::signals2::connection m_CheckResultsSlot, m_ActiveChangedSlot;
|
||||
OTel::Ptr m_Exporter;
|
||||
Timer::Ptr m_FlushTimer;
|
||||
std::atomic_bool m_TimerFlushInProgress{false}; // Whether a timer-initiated flush is in progress.
|
||||
};
|
||||
|
||||
} // namespace icinga
|
||||
74
lib/perfdata/otlpmetricswriter.ti
Normal file
74
lib/perfdata/otlpmetricswriter.ti
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// SPDX-FileCopyrightText: 2026 Icinga GmbH <https://icinga.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "base/configobject.hpp"
|
||||
|
||||
library perfdata;
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
|
||||
class OTLPMetricsWriter : ConfigObject
|
||||
{
|
||||
activation_priority 100;
|
||||
|
||||
[config, required, no_user_modify] String host {
|
||||
default {{{ return "127.0.0.1"; }}}
|
||||
};
|
||||
[config, no_user_modify] int port {
|
||||
default {{{ return 4318; }}}
|
||||
};
|
||||
[config, required, no_user_modify] String metrics_endpoint {
|
||||
default {{{ return "/v1/metrics"; }}}
|
||||
};
|
||||
|
||||
[config, required] String service_namespace {
|
||||
default {{{ return "icinga"; }}}
|
||||
};
|
||||
|
||||
[config, no_user_view, no_user_modify] Dictionary::Ptr basic_auth;
|
||||
|
||||
[config] Dictionary::Ptr host_resource_attributes;
|
||||
[config] Dictionary::Ptr service_resource_attributes;
|
||||
|
||||
[config] int flush_interval {
|
||||
default {{{ return 15; }}}
|
||||
};
|
||||
[config] int64_t flush_threshold {
|
||||
default {{{ return 16 * 1024 * 1024; }}}
|
||||
};
|
||||
[config] bool enable_ha {
|
||||
default {{{ return true; }}}
|
||||
};
|
||||
[config] bool enable_send_thresholds {
|
||||
default {{{ return false; }}}
|
||||
};
|
||||
[config] int disconnect_timeout {
|
||||
default {{{ return 10; }}}
|
||||
};
|
||||
|
||||
[config, no_user_modify] bool enable_tls {
|
||||
default {{{ return false; }}}
|
||||
};
|
||||
[config, no_user_modify] bool tls_insecure_noverify {
|
||||
default {{{ return false; }}}
|
||||
};
|
||||
[config, no_user_modify] String tls_ca_file;
|
||||
[config, no_user_modify] String tls_cert_file;
|
||||
[config, no_user_modify] String tls_key_file;
|
||||
};
|
||||
|
||||
validator OTLPMetricsWriter
|
||||
{
|
||||
Dictionary basic_auth {
|
||||
required username;
|
||||
String username;
|
||||
required password;
|
||||
String password;
|
||||
};
|
||||
|
||||
Dictionary host_resource_attributes { String "*"; };
|
||||
Dictionary service_resource_attributes { String "*"; };
|
||||
};
|
||||
|
||||
} // namespace icinga
|
||||
|
|
@ -93,6 +93,14 @@ void IncomingHttpMessage<isRequest, Body, StreamVariant>::ParseBody(
|
|||
Base::body() = std::move(m_Parser.release().body());
|
||||
}
|
||||
|
||||
template<bool isRequest, typename Body, typename StreamVariant>
|
||||
void IncomingHttpMessage<isRequest, Body, StreamVariant>::Parse(boost::asio::yield_context& yc)
|
||||
{
|
||||
boost::beast::flat_buffer buf;
|
||||
ParseHeader(buf, yc);
|
||||
ParseBody(buf, yc);
|
||||
}
|
||||
|
||||
HttpApiRequest::HttpApiRequest(Shared<AsioTlsStream>::Ptr stream) : IncomingHttpMessage(std::move(stream))
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,6 +178,14 @@ public:
|
|||
*/
|
||||
void ParseBody(boost::beast::flat_buffer& buf, boost::asio::yield_context yc);
|
||||
|
||||
/**
|
||||
* Parse the entire message (header and body) using the internal parser object.
|
||||
*
|
||||
* This is just a convenience wrapper around @c ParseHeader() and @c ParseBody() that consecutively calls
|
||||
* both of them. It can be used when you don't need to do anything with the header before parsing the body.
|
||||
*/
|
||||
void Parse(boost::asio::yield_context& yc);
|
||||
|
||||
ParserType& Parser() { return m_Parser; }
|
||||
|
||||
private:
|
||||
|
|
@ -251,6 +259,13 @@ public:
|
|||
|
||||
[[nodiscard]] bool HasSerializationStarted() const { return m_SerializationStarted; }
|
||||
|
||||
/**
|
||||
* Check if the message has been fully serialized.
|
||||
*
|
||||
* @return true if the message is fully serialized; false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool Done() { return m_Serializer.is_done(); }
|
||||
|
||||
/**
|
||||
* Sends the contents of a file.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ if(ICINGA2_WITH_NOTIFICATION)
|
|||
list(APPEND types_test_SOURCES $<TARGET_OBJECTS:notification>)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_WITH_OPENTELEMETRY)
|
||||
list(APPEND types_test_SOURCES $<TARGET_OBJECTS:otel>)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_WITH_PERFDATA)
|
||||
list(APPEND types_test_SOURCES $<TARGET_OBJECTS:perfdata>)
|
||||
endif()
|
||||
|
|
@ -140,6 +144,10 @@ if(ICINGA2_WITH_NOTIFICATION)
|
|||
)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_WITH_OPENTELEMETRY)
|
||||
list(APPEND base_test_SOURCES $<TARGET_OBJECTS:otel>)
|
||||
endif()
|
||||
|
||||
if(ICINGA2_WITH_PERFDATA)
|
||||
list(APPEND base_test_SOURCES
|
||||
perfdata-elasticsearchwriter.cpp
|
||||
|
|
|
|||
804
third-party/cmake/protobuf/FindProtobuf.cmake
vendored
Normal file
804
third-party/cmake/protobuf/FindProtobuf.cmake
vendored
Normal file
|
|
@ -0,0 +1,804 @@
|
|||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
FindProtobuf
|
||||
------------
|
||||
|
||||
Locate and configure the Google Protocol Buffers library.
|
||||
|
||||
.. versionadded:: 3.6
|
||||
Support for :command:`find_package` version checks.
|
||||
|
||||
.. versionchanged:: 3.6
|
||||
All input and output variables use the ``Protobuf_`` prefix.
|
||||
Variables with ``PROTOBUF_`` prefix are still supported for compatibility.
|
||||
|
||||
The following variables can be set and are optional:
|
||||
|
||||
``Protobuf_SRC_ROOT_FOLDER``
|
||||
When compiling with MSVC, if this cache variable is set
|
||||
the protobuf-default VS project build locations
|
||||
(vsprojects/Debug and vsprojects/Release
|
||||
or vsprojects/x64/Debug and vsprojects/x64/Release)
|
||||
will be searched for libraries and binaries.
|
||||
``Protobuf_IMPORT_DIRS``
|
||||
List of additional directories to be searched for
|
||||
imported .proto files.
|
||||
``Protobuf_DEBUG``
|
||||
.. versionadded:: 3.6
|
||||
|
||||
Show debug messages.
|
||||
``Protobuf_USE_STATIC_LIBS``
|
||||
.. versionadded:: 3.9
|
||||
|
||||
Set to ON to force the use of the static libraries.
|
||||
Default is OFF.
|
||||
|
||||
Defines the following variables:
|
||||
|
||||
``Protobuf_FOUND``
|
||||
Found the Google Protocol Buffers library
|
||||
(libprotobuf & header files)
|
||||
``Protobuf_VERSION``
|
||||
.. versionadded:: 3.6
|
||||
|
||||
Version of package found.
|
||||
``Protobuf_INCLUDE_DIRS``
|
||||
Include directories for Google Protocol Buffers
|
||||
``Protobuf_LIBRARIES``
|
||||
The protobuf libraries
|
||||
``Protobuf_PROTOC_LIBRARIES``
|
||||
The protoc libraries
|
||||
``Protobuf_LITE_LIBRARIES``
|
||||
The protobuf-lite libraries
|
||||
|
||||
.. versionadded:: 3.9
|
||||
The following :prop_tgt:`IMPORTED` targets are also defined:
|
||||
|
||||
``protobuf::libprotobuf``
|
||||
The protobuf library.
|
||||
``protobuf::libprotobuf-lite``
|
||||
The protobuf lite library.
|
||||
``protobuf::libprotoc``
|
||||
The protoc library.
|
||||
``protobuf::protoc``
|
||||
.. versionadded:: 3.10
|
||||
The protoc compiler.
|
||||
|
||||
The following cache variables are also available to set or use:
|
||||
|
||||
``Protobuf_LIBRARY``
|
||||
The protobuf library
|
||||
``Protobuf_PROTOC_LIBRARY``
|
||||
The protoc library
|
||||
``Protobuf_INCLUDE_DIR``
|
||||
The include directory for protocol buffers
|
||||
``Protobuf_PROTOC_EXECUTABLE``
|
||||
The protoc compiler
|
||||
``Protobuf_LIBRARY_DEBUG``
|
||||
The protobuf library (debug)
|
||||
``Protobuf_PROTOC_LIBRARY_DEBUG``
|
||||
The protoc library (debug)
|
||||
``Protobuf_LITE_LIBRARY``
|
||||
The protobuf lite library
|
||||
``Protobuf_LITE_LIBRARY_DEBUG``
|
||||
The protobuf lite library (debug)
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
find_package(Protobuf REQUIRED)
|
||||
include_directories(${Protobuf_INCLUDE_DIRS})
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS foo.proto)
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS EXPORT_MACRO DLL_EXPORT foo.proto)
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS DESCRIPTORS PROTO_DESCS foo.proto)
|
||||
protobuf_generate_python(PROTO_PY foo.proto)
|
||||
add_executable(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
target_link_libraries(bar ${Protobuf_LIBRARIES})
|
||||
|
||||
.. note::
|
||||
The ``protobuf_generate_cpp`` and ``protobuf_generate_python``
|
||||
functions and :command:`add_executable` or :command:`add_library`
|
||||
calls only work properly within the same directory.
|
||||
|
||||
.. command:: protobuf_generate_cpp
|
||||
|
||||
Add custom commands to process ``.proto`` files to C++::
|
||||
|
||||
protobuf_generate_cpp (<SRCS> <HDRS>
|
||||
[DESCRIPTORS <DESC>] [EXPORT_MACRO <MACRO>] [<ARGN>...])
|
||||
|
||||
``SRCS``
|
||||
Variable to define with autogenerated source files
|
||||
``HDRS``
|
||||
Variable to define with autogenerated header files
|
||||
``DESCRIPTORS``
|
||||
.. versionadded:: 3.10
|
||||
Variable to define with autogenerated descriptor files, if requested.
|
||||
``EXPORT_MACRO``
|
||||
is a macro which should expand to ``__declspec(dllexport)`` or
|
||||
``__declspec(dllimport)`` depending on what is being compiled.
|
||||
``ARGN``
|
||||
``.proto`` files
|
||||
|
||||
.. command:: protobuf_generate_python
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
Add custom commands to process ``.proto`` files to Python::
|
||||
|
||||
protobuf_generate_python (<PY> [<ARGN>...])
|
||||
|
||||
``PY``
|
||||
Variable to define with autogenerated Python files
|
||||
``ARGN``
|
||||
``.proto`` files
|
||||
|
||||
.. command:: protobuf_generate
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
Automatically generate source files from ``.proto`` schema files at build time::
|
||||
|
||||
protobuf_generate (
|
||||
TARGET <target>
|
||||
[LANGUAGE <lang>]
|
||||
[OUT_VAR <out_var>]
|
||||
[EXPORT_MACRO <macro>]
|
||||
[PROTOC_OUT_DIR <dir>]
|
||||
[PLUGIN <plugin>]
|
||||
[PLUGIN_OPTIONS <plugin_options>]
|
||||
[DEPENDENCIES <depends]
|
||||
[PROTOS <protobuf_files>]
|
||||
[IMPORT_DIRS <dirs>]
|
||||
[GENERATE_EXTENSIONS <extensions>]
|
||||
[PROTOC_OPTIONS <protoc_options>]
|
||||
[APPEND_PATH])
|
||||
|
||||
``APPEND_PATH``
|
||||
A flag that causes the base path of all proto schema files to be added to
|
||||
``IMPORT_DIRS``.
|
||||
``LANGUAGE``
|
||||
A single value: cpp or python. Determines what kind of source files are
|
||||
being generated. Defaults to cpp.
|
||||
``OUT_VAR``
|
||||
Name of a CMake variable that will be filled with the paths to the generated
|
||||
source files.
|
||||
``EXPORT_MACRO``
|
||||
Name of a macro that is applied to all generated Protobuf message classes
|
||||
and extern variables. It can, for example, be used to declare DLL exports.
|
||||
``PROTOC_OUT_DIR``
|
||||
Output directory of generated source files. Defaults to ``CMAKE_CURRENT_BINARY_DIR``.
|
||||
``PLUGIN``
|
||||
.. versionadded:: 3.21
|
||||
|
||||
An optional plugin executable. This could, for example, be the path to
|
||||
``grpc_cpp_plugin``.
|
||||
``PLUGIN_OPTIONS``
|
||||
.. versionadded:: 3.28
|
||||
|
||||
Additional options provided to the plugin, such as ``generate_mock_code=true``
|
||||
for the gRPC cpp plugin.
|
||||
``DEPENDENCIES``
|
||||
.. versionadded:: 3.28
|
||||
|
||||
Arguments forwarded to the ``DEPENDS`` of the underlying ``add_custom_command``
|
||||
invocation.
|
||||
``TARGET``
|
||||
CMake target that will have the generated files added as sources.
|
||||
``PROTOS``
|
||||
List of proto schema files. If omitted, then every source file ending in *proto* of ``TARGET`` will be used.
|
||||
``IMPORT_DIRS``
|
||||
A common parent directory for the schema files. For example, if the schema file is
|
||||
``proto/helloworld/helloworld.proto`` and the import directory ``proto/`` then the
|
||||
generated files are ``${PROTOC_OUT_DIR}/helloworld/helloworld.pb.h`` and
|
||||
``${PROTOC_OUT_DIR}/helloworld/helloworld.pb.cc``.
|
||||
``GENERATE_EXTENSIONS``
|
||||
If LANGUAGE is omitted then this must be set to the extensions that protoc generates.
|
||||
``PROTOC_OPTIONS``
|
||||
.. versionadded:: 3.28
|
||||
|
||||
Additional arguments that are forwarded to protoc.
|
||||
|
||||
Example::
|
||||
|
||||
find_package(gRPC CONFIG REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
add_library(ProtoTest Test.proto)
|
||||
target_link_libraries(ProtoTest PUBLIC gRPC::grpc++)
|
||||
protobuf_generate(TARGET ProtoTest)
|
||||
protobuf_generate(
|
||||
TARGET ProtoTest
|
||||
LANGUAGE grpc
|
||||
PLUGIN protoc-gen-grpc=$<TARGET_FILE:gRPC::grpc_cpp_plugin>
|
||||
PLUGIN_OPTIONS generate_mock_code=true
|
||||
GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc)
|
||||
#]=======================================================================]
|
||||
|
||||
function(protobuf_generate)
|
||||
set(_options APPEND_PATH DESCRIPTORS)
|
||||
set(_singleargs LANGUAGE OUT_VAR EXPORT_MACRO PROTOC_OUT_DIR PLUGIN PLUGIN_OPTIONS DEPENDENCIES)
|
||||
if(COMMAND target_sources)
|
||||
list(APPEND _singleargs TARGET)
|
||||
endif()
|
||||
set(_multiargs PROTOS IMPORT_DIRS GENERATE_EXTENSIONS PROTOC_OPTIONS)
|
||||
|
||||
cmake_parse_arguments(protobuf_generate "${_options}" "${_singleargs}" "${_multiargs}" "${ARGN}")
|
||||
|
||||
if(NOT protobuf_generate_PROTOS AND NOT protobuf_generate_TARGET)
|
||||
message(SEND_ERROR "Error: protobuf_generate called without any targets or source files")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT protobuf_generate_OUT_VAR AND NOT protobuf_generate_TARGET)
|
||||
message(SEND_ERROR "Error: protobuf_generate called without a target or output variable")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT protobuf_generate_LANGUAGE)
|
||||
set(protobuf_generate_LANGUAGE cpp)
|
||||
endif()
|
||||
string(TOLOWER ${protobuf_generate_LANGUAGE} protobuf_generate_LANGUAGE)
|
||||
|
||||
if(NOT protobuf_generate_PROTOC_OUT_DIR)
|
||||
set(protobuf_generate_PROTOC_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
|
||||
if(protobuf_generate_EXPORT_MACRO AND protobuf_generate_LANGUAGE STREQUAL cpp)
|
||||
set(_dll_export_decl "dllexport_decl=${protobuf_generate_EXPORT_MACRO}")
|
||||
endif()
|
||||
|
||||
foreach(_option ${_dll_export_decl} ${protobuf_generate_PLUGIN_OPTIONS})
|
||||
# append comma - not using CMake lists and string replacement as users
|
||||
# might have semicolons in options
|
||||
if(_plugin_options)
|
||||
set( _plugin_options "${_plugin_options},")
|
||||
endif()
|
||||
set(_plugin_options "${_plugin_options}${_option}")
|
||||
endforeach()
|
||||
|
||||
if(protobuf_generate_PLUGIN)
|
||||
set(_plugin "--plugin=${protobuf_generate_PLUGIN}")
|
||||
endif()
|
||||
|
||||
if(NOT protobuf_generate_GENERATE_EXTENSIONS)
|
||||
if(protobuf_generate_LANGUAGE STREQUAL cpp)
|
||||
set(protobuf_generate_GENERATE_EXTENSIONS .pb.h .pb.cc)
|
||||
elseif(protobuf_generate_LANGUAGE STREQUAL python)
|
||||
set(protobuf_generate_GENERATE_EXTENSIONS _pb2.py)
|
||||
else()
|
||||
message(SEND_ERROR "Error: protobuf_generate given unknown Language ${LANGUAGE}, please provide a value for GENERATE_EXTENSIONS")
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(protobuf_generate_TARGET)
|
||||
get_target_property(_source_list ${protobuf_generate_TARGET} SOURCES)
|
||||
foreach(_file ${_source_list})
|
||||
if(_file MATCHES "proto$")
|
||||
list(APPEND protobuf_generate_PROTOS ${_file})
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(NOT protobuf_generate_PROTOS)
|
||||
message(SEND_ERROR "Error: protobuf_generate could not find any .proto files")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT TARGET protobuf::protoc)
|
||||
message(SEND_ERROR "protoc executable not found. "
|
||||
"Please define the Protobuf_PROTOC_EXECUTABLE variable or ensure that protoc is in CMake's search path.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(protobuf_generate_APPEND_PATH)
|
||||
# Create an include path for each file specified
|
||||
foreach(_file ${protobuf_generate_PROTOS})
|
||||
get_filename_component(_abs_file ${_file} ABSOLUTE)
|
||||
get_filename_component(_abs_dir ${_abs_file} DIRECTORY)
|
||||
list(FIND _protobuf_include_path ${_abs_dir} _contains_already)
|
||||
if(${_contains_already} EQUAL -1)
|
||||
list(APPEND _protobuf_include_path -I ${_abs_dir})
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
foreach(DIR ${protobuf_generate_IMPORT_DIRS})
|
||||
get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
|
||||
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
|
||||
if(${_contains_already} EQUAL -1)
|
||||
list(APPEND _protobuf_include_path -I ${ABS_PATH})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(NOT protobuf_generate_APPEND_PATH)
|
||||
list(APPEND _protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
set(_generated_srcs_all)
|
||||
foreach(_proto ${protobuf_generate_PROTOS})
|
||||
get_filename_component(_abs_file ${_proto} ABSOLUTE)
|
||||
get_filename_component(_abs_dir ${_abs_file} DIRECTORY)
|
||||
get_filename_component(_basename ${_proto} NAME_WLE)
|
||||
file(RELATIVE_PATH _rel_dir ${CMAKE_CURRENT_SOURCE_DIR} ${_abs_dir})
|
||||
|
||||
set(_possible_rel_dir)
|
||||
if (NOT protobuf_generate_APPEND_PATH)
|
||||
foreach(DIR ${_protobuf_include_path})
|
||||
if(NOT DIR STREQUAL "-I")
|
||||
file(RELATIVE_PATH _rel_dir ${DIR} ${_abs_dir})
|
||||
if(_rel_dir STREQUAL _abs_dir)
|
||||
continue()
|
||||
endif()
|
||||
string(FIND "${_rel_dir}" "../" _is_in_parent_folder)
|
||||
if (NOT ${_is_in_parent_folder} EQUAL 0)
|
||||
break()
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
set(_possible_rel_dir ${_rel_dir}/)
|
||||
endif()
|
||||
|
||||
set(_generated_srcs)
|
||||
foreach(_ext ${protobuf_generate_GENERATE_EXTENSIONS})
|
||||
list(APPEND _generated_srcs "${protobuf_generate_PROTOC_OUT_DIR}/${_possible_rel_dir}${_basename}${_ext}")
|
||||
endforeach()
|
||||
|
||||
if(protobuf_generate_DESCRIPTORS AND protobuf_generate_LANGUAGE STREQUAL cpp)
|
||||
set(_descriptor_file "${CMAKE_CURRENT_BINARY_DIR}/${_basename}.desc")
|
||||
set(_dll_desc_out "--descriptor_set_out=${_descriptor_file}")
|
||||
list(APPEND _generated_srcs ${_descriptor_file})
|
||||
endif()
|
||||
list(APPEND _generated_srcs_all ${_generated_srcs})
|
||||
|
||||
set(_comment "Running ${protobuf_generate_LANGUAGE} protocol buffer compiler on ${_proto}")
|
||||
if(protobuf_generate_PROTOC_OPTIONS)
|
||||
set(_comment "${_comment}, protoc-options: ${protobuf_generate_PROTOC_OPTIONS}")
|
||||
endif()
|
||||
if(_plugin_options)
|
||||
set(_comment "${_comment}, plugin-options: ${_plugin_options}")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${_generated_srcs}
|
||||
COMMAND protobuf::protoc
|
||||
ARGS ${protobuf_generate_PROTOC_OPTIONS} --${protobuf_generate_LANGUAGE}_out ${_plugin_options}:${protobuf_generate_PROTOC_OUT_DIR} ${_plugin} ${_dll_desc_out} ${_protobuf_include_path} ${_abs_file}
|
||||
DEPENDS ${_abs_file} protobuf::protoc ${protobuf_generate_DEPENDENCIES}
|
||||
COMMENT ${_comment}
|
||||
VERBATIM )
|
||||
endforeach()
|
||||
|
||||
set_source_files_properties(${_generated_srcs_all} PROPERTIES GENERATED TRUE)
|
||||
if(protobuf_generate_OUT_VAR)
|
||||
set(${protobuf_generate_OUT_VAR} ${_generated_srcs_all} PARENT_SCOPE)
|
||||
endif()
|
||||
if(protobuf_generate_TARGET)
|
||||
target_sources(${protobuf_generate_TARGET} PRIVATE ${_generated_srcs_all})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(PROTOBUF_GENERATE_CPP SRCS HDRS)
|
||||
cmake_parse_arguments(protobuf_generate_cpp "" "EXPORT_MACRO;DESCRIPTORS" "" ${ARGN})
|
||||
|
||||
set(_proto_files "${protobuf_generate_cpp_UNPARSED_ARGUMENTS}")
|
||||
if(NOT _proto_files)
|
||||
message(SEND_ERROR "Error: PROTOBUF_GENERATE_CPP() called without any proto files")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(PROTOBUF_GENERATE_CPP_APPEND_PATH)
|
||||
set(_append_arg APPEND_PATH)
|
||||
endif()
|
||||
|
||||
if(protobuf_generate_cpp_DESCRIPTORS)
|
||||
set(_descriptors DESCRIPTORS)
|
||||
endif()
|
||||
|
||||
if(DEFINED PROTOBUF_IMPORT_DIRS AND NOT DEFINED Protobuf_IMPORT_DIRS)
|
||||
set(Protobuf_IMPORT_DIRS "${PROTOBUF_IMPORT_DIRS}")
|
||||
endif()
|
||||
|
||||
if(DEFINED Protobuf_IMPORT_DIRS)
|
||||
set(_import_arg IMPORT_DIRS ${Protobuf_IMPORT_DIRS})
|
||||
endif()
|
||||
|
||||
set(_outvar)
|
||||
protobuf_generate(${_append_arg} ${_descriptors} LANGUAGE cpp EXPORT_MACRO ${protobuf_generate_cpp_EXPORT_MACRO} OUT_VAR _outvar ${_import_arg} PROTOS ${_proto_files})
|
||||
|
||||
set(${SRCS})
|
||||
set(${HDRS})
|
||||
if(protobuf_generate_cpp_DESCRIPTORS)
|
||||
set(${protobuf_generate_cpp_DESCRIPTORS})
|
||||
endif()
|
||||
|
||||
foreach(_file ${_outvar})
|
||||
if(_file MATCHES "cc$")
|
||||
list(APPEND ${SRCS} ${_file})
|
||||
elseif(_file MATCHES "desc$")
|
||||
list(APPEND ${protobuf_generate_cpp_DESCRIPTORS} ${_file})
|
||||
else()
|
||||
list(APPEND ${HDRS} ${_file})
|
||||
endif()
|
||||
endforeach()
|
||||
set(${SRCS} ${${SRCS}} PARENT_SCOPE)
|
||||
set(${HDRS} ${${HDRS}} PARENT_SCOPE)
|
||||
if(protobuf_generate_cpp_DESCRIPTORS)
|
||||
set(${protobuf_generate_cpp_DESCRIPTORS} "${${protobuf_generate_cpp_DESCRIPTORS}}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(PROTOBUF_GENERATE_PYTHON SRCS)
|
||||
if(NOT ARGN)
|
||||
message(SEND_ERROR "Error: PROTOBUF_GENERATE_PYTHON() called without any proto files")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(PROTOBUF_GENERATE_CPP_APPEND_PATH)
|
||||
set(_append_arg APPEND_PATH)
|
||||
endif()
|
||||
|
||||
if(DEFINED PROTOBUF_IMPORT_DIRS AND NOT DEFINED Protobuf_IMPORT_DIRS)
|
||||
set(Protobuf_IMPORT_DIRS "${PROTOBUF_IMPORT_DIRS}")
|
||||
endif()
|
||||
|
||||
if(DEFINED Protobuf_IMPORT_DIRS)
|
||||
set(_import_arg IMPORT_DIRS ${Protobuf_IMPORT_DIRS})
|
||||
endif()
|
||||
|
||||
set(_outvar)
|
||||
protobuf_generate(${_append_arg} LANGUAGE python OUT_VAR _outvar ${_import_arg} PROTOS ${ARGN})
|
||||
set(${SRCS} ${_outvar} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
|
||||
if(Protobuf_DEBUG)
|
||||
# Output some of their choices
|
||||
message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] "
|
||||
"Protobuf_USE_STATIC_LIBS = ${Protobuf_USE_STATIC_LIBS}")
|
||||
endif()
|
||||
|
||||
|
||||
# Backwards compatibility
|
||||
# Define camel case versions of input variables
|
||||
foreach(UPPER
|
||||
PROTOBUF_SRC_ROOT_FOLDER
|
||||
PROTOBUF_IMPORT_DIRS
|
||||
PROTOBUF_DEBUG
|
||||
PROTOBUF_LIBRARY
|
||||
PROTOBUF_PROTOC_LIBRARY
|
||||
PROTOBUF_INCLUDE_DIR
|
||||
PROTOBUF_PROTOC_EXECUTABLE
|
||||
PROTOBUF_LIBRARY_DEBUG
|
||||
PROTOBUF_PROTOC_LIBRARY_DEBUG
|
||||
PROTOBUF_LITE_LIBRARY
|
||||
PROTOBUF_LITE_LIBRARY_DEBUG
|
||||
)
|
||||
if (DEFINED ${UPPER})
|
||||
string(REPLACE "PROTOBUF_" "Protobuf_" Camel ${UPPER})
|
||||
if (NOT DEFINED ${Camel})
|
||||
set(${Camel} ${${UPPER}})
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(_PROTOBUF_ARCH_DIR x64/)
|
||||
endif()
|
||||
|
||||
|
||||
# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES
|
||||
if( Protobuf_USE_STATIC_LIBS )
|
||||
set( _protobuf_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
if(WIN32)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
else()
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES .a )
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
# Internal function: search for normal library as well as a debug one
|
||||
# if the debug one is specified also include debug/optimized keywords
|
||||
# in *_LIBRARIES variable
|
||||
function(_protobuf_find_libraries name filename)
|
||||
if(${name}_LIBRARIES)
|
||||
# Use result recorded by a previous call.
|
||||
return()
|
||||
elseif(${name}_LIBRARY)
|
||||
# Honor cache entry used by CMake 3.5 and lower.
|
||||
set(${name}_LIBRARIES "${${name}_LIBRARY}" PARENT_SCOPE)
|
||||
else()
|
||||
find_library(${name}_LIBRARY_RELEASE
|
||||
NAMES ${filename}
|
||||
NAMES_PER_DIR
|
||||
PATHS ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Release)
|
||||
mark_as_advanced(${name}_LIBRARY_RELEASE)
|
||||
|
||||
find_library(${name}_LIBRARY_DEBUG
|
||||
NAMES ${filename}d ${filename}
|
||||
NAMES_PER_DIR
|
||||
PATHS ${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Debug)
|
||||
mark_as_advanced(${name}_LIBRARY_DEBUG)
|
||||
|
||||
select_library_configurations(${name})
|
||||
|
||||
if(UNIX AND Threads_FOUND AND ${name}_LIBRARY)
|
||||
list(APPEND ${name}_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
set(${name}_LIBRARY "${${name}_LIBRARY}" PARENT_SCOPE)
|
||||
set(${name}_LIBRARIES "${${name}_LIBRARIES}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
#
|
||||
# Main.
|
||||
#
|
||||
|
||||
# By default have PROTOBUF_GENERATE_CPP macro pass -I to protoc
|
||||
# for each directory where a proto file is referenced.
|
||||
if(NOT DEFINED PROTOBUF_GENERATE_CPP_APPEND_PATH)
|
||||
set(PROTOBUF_GENERATE_CPP_APPEND_PATH TRUE)
|
||||
endif()
|
||||
|
||||
|
||||
# Google's provided vcproj files generate libraries with a "lib"
|
||||
# prefix on Windows
|
||||
if(MSVC)
|
||||
set(Protobuf_ORIG_FIND_LIBRARY_PREFIXES "${CMAKE_FIND_LIBRARY_PREFIXES}")
|
||||
set(CMAKE_FIND_LIBRARY_PREFIXES "lib" "")
|
||||
|
||||
find_path(Protobuf_SRC_ROOT_FOLDER protobuf.pc.in)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
# Protobuf headers may depend on threading.
|
||||
find_package(Threads QUIET)
|
||||
endif()
|
||||
|
||||
# The Protobuf library
|
||||
_protobuf_find_libraries(Protobuf protobuf)
|
||||
#DOC "The Google Protocol Buffers RELEASE Library"
|
||||
|
||||
_protobuf_find_libraries(Protobuf_LITE protobuf-lite)
|
||||
|
||||
# The Protobuf Protoc Library
|
||||
_protobuf_find_libraries(Protobuf_PROTOC protoc)
|
||||
|
||||
# Restore original find library prefixes
|
||||
if(MSVC)
|
||||
set(CMAKE_FIND_LIBRARY_PREFIXES "${Protobuf_ORIG_FIND_LIBRARY_PREFIXES}")
|
||||
endif()
|
||||
|
||||
# Find the include directory
|
||||
find_path(Protobuf_INCLUDE_DIR
|
||||
google/protobuf/service.h
|
||||
PATHS ${Protobuf_SRC_ROOT_FOLDER}/src
|
||||
)
|
||||
mark_as_advanced(Protobuf_INCLUDE_DIR)
|
||||
|
||||
# Find the protoc Executable
|
||||
find_program(Protobuf_PROTOC_EXECUTABLE
|
||||
NAMES protoc
|
||||
DOC "The Google Protocol Buffers Compiler"
|
||||
PATHS
|
||||
${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Release
|
||||
${Protobuf_SRC_ROOT_FOLDER}/vsprojects/${_PROTOBUF_ARCH_DIR}Debug
|
||||
)
|
||||
mark_as_advanced(Protobuf_PROTOC_EXECUTABLE)
|
||||
|
||||
if(Protobuf_DEBUG)
|
||||
message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] "
|
||||
"requested version of Google Protobuf is ${Protobuf_FIND_VERSION}")
|
||||
endif()
|
||||
|
||||
if(Protobuf_INCLUDE_DIR)
|
||||
set(_PROTOBUF_COMMON_HEADER ${Protobuf_INCLUDE_DIR}/google/protobuf/stubs/common.h)
|
||||
|
||||
if(Protobuf_DEBUG)
|
||||
message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] "
|
||||
"location of common.h: ${_PROTOBUF_COMMON_HEADER}")
|
||||
endif()
|
||||
|
||||
set(Protobuf_VERSION "")
|
||||
set(Protobuf_LIB_VERSION "")
|
||||
file(STRINGS ${_PROTOBUF_COMMON_HEADER} _PROTOBUF_COMMON_H_CONTENTS REGEX "#define[ \t]+GOOGLE_PROTOBUF_VERSION[ \t]+")
|
||||
if(_PROTOBUF_COMMON_H_CONTENTS MATCHES "#define[ \t]+GOOGLE_PROTOBUF_VERSION[ \t]+([0-9]+)")
|
||||
set(Protobuf_LIB_VERSION "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
unset(_PROTOBUF_COMMON_H_CONTENTS)
|
||||
|
||||
math(EXPR _PROTOBUF_MAJOR_VERSION "${Protobuf_LIB_VERSION} / 1000000")
|
||||
math(EXPR _PROTOBUF_MINOR_VERSION "${Protobuf_LIB_VERSION} / 1000 % 1000")
|
||||
math(EXPR _PROTOBUF_SUBMINOR_VERSION "${Protobuf_LIB_VERSION} % 1000")
|
||||
set(Protobuf_VERSION "${_PROTOBUF_MAJOR_VERSION}.${_PROTOBUF_MINOR_VERSION}.${_PROTOBUF_SUBMINOR_VERSION}")
|
||||
|
||||
if(Protobuf_DEBUG)
|
||||
message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] "
|
||||
"${_PROTOBUF_COMMON_HEADER} reveals protobuf ${Protobuf_VERSION}")
|
||||
endif()
|
||||
|
||||
if(Protobuf_PROTOC_EXECUTABLE)
|
||||
# Check Protobuf compiler version to be aligned with libraries version
|
||||
execute_process(COMMAND ${Protobuf_PROTOC_EXECUTABLE} --version
|
||||
OUTPUT_VARIABLE _PROTOBUF_PROTOC_EXECUTABLE_VERSION)
|
||||
|
||||
if("${_PROTOBUF_PROTOC_EXECUTABLE_VERSION}" MATCHES "libprotoc ([0-9.]+)")
|
||||
set(_PROTOBUF_PROTOC_EXECUTABLE_VERSION "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
|
||||
if(Protobuf_DEBUG)
|
||||
message(STATUS "[ ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE} ] "
|
||||
"${Protobuf_PROTOC_EXECUTABLE} reveals version ${_PROTOBUF_PROTOC_EXECUTABLE_VERSION}")
|
||||
endif()
|
||||
|
||||
# protoc version 22 and up don't print the major version any more
|
||||
if(NOT "${_PROTOBUF_PROTOC_EXECUTABLE_VERSION}" VERSION_EQUAL "${Protobuf_VERSION}" AND
|
||||
NOT "${_PROTOBUF_PROTOC_EXECUTABLE_VERSION}" VERSION_EQUAL "${_PROTOBUF_MINOR_VERSION}.${_PROTOBUF_SUBMINOR_VERSION}")
|
||||
message(WARNING "Protobuf compiler version ${_PROTOBUF_PROTOC_EXECUTABLE_VERSION}"
|
||||
" doesn't match library version ${Protobuf_VERSION}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(Protobuf_LIBRARY)
|
||||
if(NOT TARGET protobuf::libprotobuf)
|
||||
add_library(protobuf::libprotobuf UNKNOWN IMPORTED)
|
||||
set_target_properties(protobuf::libprotobuf PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Protobuf_INCLUDE_DIR}")
|
||||
if(EXISTS "${Protobuf_LIBRARY}")
|
||||
set_target_properties(protobuf::libprotobuf PROPERTIES
|
||||
IMPORTED_LOCATION "${Protobuf_LIBRARY}")
|
||||
endif()
|
||||
if(EXISTS "${Protobuf_LIBRARY_RELEASE}")
|
||||
set_property(TARGET protobuf::libprotobuf APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(protobuf::libprotobuf PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${Protobuf_LIBRARY_RELEASE}")
|
||||
endif()
|
||||
if(EXISTS "${Protobuf_LIBRARY_DEBUG}")
|
||||
set_property(TARGET protobuf::libprotobuf APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(protobuf::libprotobuf PROPERTIES
|
||||
IMPORTED_LOCATION_DEBUG "${Protobuf_LIBRARY_DEBUG}")
|
||||
endif()
|
||||
if (Protobuf_VERSION VERSION_GREATER_EQUAL "3.6")
|
||||
set_property(TARGET protobuf::libprotobuf APPEND PROPERTY
|
||||
INTERFACE_COMPILE_FEATURES cxx_std_11
|
||||
)
|
||||
endif()
|
||||
if (WIN32 AND NOT Protobuf_USE_STATIC_LIBS)
|
||||
set_property(TARGET protobuf::libprotobuf APPEND PROPERTY
|
||||
INTERFACE_COMPILE_DEFINITIONS "PROTOBUF_USE_DLLS"
|
||||
)
|
||||
endif()
|
||||
if(UNIX AND TARGET Threads::Threads)
|
||||
set_property(TARGET protobuf::libprotobuf APPEND PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(Protobuf_LITE_LIBRARY)
|
||||
if(NOT TARGET protobuf::libprotobuf-lite)
|
||||
add_library(protobuf::libprotobuf-lite UNKNOWN IMPORTED)
|
||||
set_target_properties(protobuf::libprotobuf-lite PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Protobuf_INCLUDE_DIR}")
|
||||
if(EXISTS "${Protobuf_LITE_LIBRARY}")
|
||||
set_target_properties(protobuf::libprotobuf-lite PROPERTIES
|
||||
IMPORTED_LOCATION "${Protobuf_LITE_LIBRARY}")
|
||||
endif()
|
||||
if(EXISTS "${Protobuf_LITE_LIBRARY_RELEASE}")
|
||||
set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(protobuf::libprotobuf-lite PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${Protobuf_LITE_LIBRARY_RELEASE}")
|
||||
endif()
|
||||
if(EXISTS "${Protobuf_LITE_LIBRARY_DEBUG}")
|
||||
set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(protobuf::libprotobuf-lite PROPERTIES
|
||||
IMPORTED_LOCATION_DEBUG "${Protobuf_LITE_LIBRARY_DEBUG}")
|
||||
endif()
|
||||
if (WIN32 AND NOT Protobuf_USE_STATIC_LIBS)
|
||||
set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY
|
||||
INTERFACE_COMPILE_DEFINITIONS "PROTOBUF_USE_DLLS"
|
||||
)
|
||||
endif()
|
||||
if(UNIX AND TARGET Threads::Threads)
|
||||
set_property(TARGET protobuf::libprotobuf-lite APPEND PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(Protobuf_PROTOC_LIBRARY)
|
||||
if(NOT TARGET protobuf::libprotoc)
|
||||
add_library(protobuf::libprotoc UNKNOWN IMPORTED)
|
||||
set_target_properties(protobuf::libprotoc PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Protobuf_INCLUDE_DIR}")
|
||||
if(EXISTS "${Protobuf_PROTOC_LIBRARY}")
|
||||
set_target_properties(protobuf::libprotoc PROPERTIES
|
||||
IMPORTED_LOCATION "${Protobuf_PROTOC_LIBRARY}")
|
||||
endif()
|
||||
if(EXISTS "${Protobuf_PROTOC_LIBRARY_RELEASE}")
|
||||
set_property(TARGET protobuf::libprotoc APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_target_properties(protobuf::libprotoc PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${Protobuf_PROTOC_LIBRARY_RELEASE}")
|
||||
endif()
|
||||
if(EXISTS "${Protobuf_PROTOC_LIBRARY_DEBUG}")
|
||||
set_property(TARGET protobuf::libprotoc APPEND PROPERTY
|
||||
IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_target_properties(protobuf::libprotoc PROPERTIES
|
||||
IMPORTED_LOCATION_DEBUG "${Protobuf_PROTOC_LIBRARY_DEBUG}")
|
||||
endif()
|
||||
if (Protobuf_VERSION VERSION_GREATER_EQUAL "3.6")
|
||||
set_property(TARGET protobuf::libprotoc APPEND PROPERTY
|
||||
INTERFACE_COMPILE_FEATURES cxx_std_11
|
||||
)
|
||||
endif()
|
||||
if (WIN32 AND NOT Protobuf_USE_STATIC_LIBS)
|
||||
set_property(TARGET protobuf::libprotoc APPEND PROPERTY
|
||||
INTERFACE_COMPILE_DEFINITIONS "PROTOBUF_USE_DLLS"
|
||||
)
|
||||
endif()
|
||||
if(UNIX AND TARGET Threads::Threads)
|
||||
set_property(TARGET protobuf::libprotoc APPEND PROPERTY
|
||||
INTERFACE_LINK_LIBRARIES Threads::Threads)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(Protobuf_PROTOC_EXECUTABLE)
|
||||
if(NOT TARGET protobuf::protoc)
|
||||
add_executable(protobuf::protoc IMPORTED)
|
||||
if(EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
|
||||
set_target_properties(protobuf::protoc PROPERTIES
|
||||
IMPORTED_LOCATION "${Protobuf_PROTOC_EXECUTABLE}")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Protobuf
|
||||
REQUIRED_VARS Protobuf_LIBRARIES Protobuf_INCLUDE_DIR
|
||||
VERSION_VAR Protobuf_VERSION
|
||||
)
|
||||
|
||||
if(Protobuf_FOUND)
|
||||
set(Protobuf_INCLUDE_DIRS ${Protobuf_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
# Restore the original find library ordering
|
||||
if( Protobuf_USE_STATIC_LIBS )
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_protobuf_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
endif()
|
||||
|
||||
# Backwards compatibility
|
||||
# Define upper case versions of output variables
|
||||
foreach(Camel
|
||||
Protobuf_SRC_ROOT_FOLDER
|
||||
Protobuf_IMPORT_DIRS
|
||||
Protobuf_DEBUG
|
||||
Protobuf_INCLUDE_DIRS
|
||||
Protobuf_LIBRARIES
|
||||
Protobuf_PROTOC_LIBRARIES
|
||||
Protobuf_LITE_LIBRARIES
|
||||
Protobuf_LIBRARY
|
||||
Protobuf_PROTOC_LIBRARY
|
||||
Protobuf_INCLUDE_DIR
|
||||
Protobuf_PROTOC_EXECUTABLE
|
||||
Protobuf_LIBRARY_DEBUG
|
||||
Protobuf_PROTOC_LIBRARY_DEBUG
|
||||
Protobuf_LITE_LIBRARY
|
||||
Protobuf_LITE_LIBRARY_DEBUG
|
||||
)
|
||||
string(TOUPPER ${Camel} UPPER)
|
||||
set(${UPPER} ${${Camel}})
|
||||
endforeach()
|
||||
201
third-party/opentelemetry-proto/LICENSE
vendored
Normal file
201
third-party/opentelemetry-proto/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
77
third-party/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto
vendored
Normal file
77
third-party/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2019, 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.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package opentelemetry.proto.collector.metrics.v1;
|
||||
|
||||
import "opentelemetry/proto/metrics/v1/metrics.proto";
|
||||
|
||||
option csharp_namespace = "OpenTelemetry.Proto.Collector.Metrics.V1";
|
||||
option java_multiple_files = true;
|
||||
option java_package = "io.opentelemetry.proto.collector.metrics.v1";
|
||||
option java_outer_classname = "MetricsServiceProto";
|
||||
option go_package = "go.opentelemetry.io/proto/otlp/collector/metrics/v1";
|
||||
|
||||
// Service that can be used to push metrics between one Application
|
||||
// instrumented with OpenTelemetry and a collector, or between a collector and a
|
||||
// central collector.
|
||||
service MetricsService {
|
||||
rpc Export(ExportMetricsServiceRequest) returns (ExportMetricsServiceResponse) {}
|
||||
}
|
||||
|
||||
message ExportMetricsServiceRequest {
|
||||
// An array of ResourceMetrics.
|
||||
// For data coming from a single resource this array will typically contain one
|
||||
// element. Intermediary nodes (such as OpenTelemetry Collector) that receive
|
||||
// data from multiple origins typically batch the data before forwarding further and
|
||||
// in that case this array will contain multiple elements.
|
||||
repeated opentelemetry.proto.metrics.v1.ResourceMetrics resource_metrics = 1;
|
||||
}
|
||||
|
||||
message ExportMetricsServiceResponse {
|
||||
// The details of a partially successful export request.
|
||||
//
|
||||
// If the request is only partially accepted
|
||||
// (i.e. when the server accepts only parts of the data and rejects the rest)
|
||||
// the server MUST initialize the `partial_success` field and MUST
|
||||
// set the `rejected_<signal>` with the number of items it rejected.
|
||||
//
|
||||
// Servers MAY also make use of the `partial_success` field to convey
|
||||
// warnings/suggestions to senders even when the request was fully accepted.
|
||||
// In such cases, the `rejected_<signal>` MUST have a value of `0` and
|
||||
// the `error_message` MUST be non-empty.
|
||||
//
|
||||
// A `partial_success` message with an empty value (rejected_<signal> = 0 and
|
||||
// `error_message` = "") is equivalent to it not being set/present. Senders
|
||||
// SHOULD interpret it the same way as in the full success case.
|
||||
ExportMetricsPartialSuccess partial_success = 1;
|
||||
}
|
||||
|
||||
message ExportMetricsPartialSuccess {
|
||||
// The number of rejected data points.
|
||||
//
|
||||
// A `rejected_<signal>` field holding a `0` value indicates that the
|
||||
// request was fully accepted.
|
||||
int64 rejected_data_points = 1;
|
||||
|
||||
// A developer-facing human-readable message in English. It should be used
|
||||
// either to explain why the server rejected parts of the data during a partial
|
||||
// success or to convey warnings/suggestions during a full success. The message
|
||||
// should offer guidance on how users can address such issues.
|
||||
//
|
||||
// error_message is an optional field. An error_message with an empty value
|
||||
// is equivalent to it not being set.
|
||||
string error_message = 2;
|
||||
}
|
||||
154
third-party/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto
vendored
Normal file
154
third-party/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// Copyright 2019, 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.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package opentelemetry.proto.common.v1;
|
||||
|
||||
option csharp_namespace = "OpenTelemetry.Proto.Common.V1";
|
||||
option java_multiple_files = true;
|
||||
option java_package = "io.opentelemetry.proto.common.v1";
|
||||
option java_outer_classname = "CommonProto";
|
||||
option go_package = "go.opentelemetry.io/proto/otlp/common/v1";
|
||||
|
||||
// Represents 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 "empty".
|
||||
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;
|
||||
bytes bytes_value = 7;
|
||||
// Reference to the string value in ProfilesDictionary.string_table.
|
||||
//
|
||||
// Note: This is currently used exclusively in the Profiling signal.
|
||||
// Implementers of OTLP receivers for signals other than Profiling should
|
||||
// treat the presence of this value as a non-fatal issue.
|
||||
// Log an error or warning indicating an unexpected field intended for the
|
||||
// Profiling signal and process the data as if this value were absent or
|
||||
// empty, ignoring its semantic content for the non-Profiling signal.
|
||||
//
|
||||
// Status: [Development]
|
||||
int32 string_value_strindex = 8;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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).
|
||||
//
|
||||
// The keys MUST be unique (it is not allowed to have more than one
|
||||
// value with the same key).
|
||||
// The behavior of software that receives duplicated keys can be unpredictable.
|
||||
repeated KeyValue values = 1;
|
||||
}
|
||||
|
||||
// Represents a key-value pair that is used to store Span attributes, Link
|
||||
// attributes, etc.
|
||||
message KeyValue {
|
||||
// The key name of the pair.
|
||||
// key_ref MUST NOT be set if key is used.
|
||||
string key = 1;
|
||||
|
||||
// The value of the pair.
|
||||
AnyValue value = 2;
|
||||
|
||||
// Reference to the string key in ProfilesDictionary.string_table.
|
||||
// key MUST NOT be set if key_strindex is used.
|
||||
//
|
||||
// Note: This is currently used exclusively in the Profiling signal.
|
||||
// Implementers of OTLP receivers for signals other than Profiling should
|
||||
// treat the presence of this key as a non-fatal issue.
|
||||
// Log an error or warning indicating an unexpected field intended for the
|
||||
// Profiling signal and process the data as if this value were absent or
|
||||
// empty, ignoring its semantic content for the non-Profiling signal.
|
||||
//
|
||||
// Status: [Development]
|
||||
int32 key_strindex = 3;
|
||||
}
|
||||
|
||||
// InstrumentationScope is a message representing the instrumentation scope information
|
||||
// such as the fully qualified name and version.
|
||||
message InstrumentationScope {
|
||||
// A name denoting the Instrumentation scope.
|
||||
// An empty instrumentation scope name means the name is unknown.
|
||||
string name = 1;
|
||||
|
||||
// Defines the version of the instrumentation scope.
|
||||
// An empty instrumentation scope version means the version is unknown.
|
||||
string version = 2;
|
||||
|
||||
// Additional attributes that describe the scope. [Optional].
|
||||
// Attribute keys MUST be unique (it is not allowed to have more than one
|
||||
// attribute with the same key).
|
||||
// The behavior of software that receives duplicated keys can be unpredictable.
|
||||
repeated KeyValue attributes = 3;
|
||||
|
||||
// The number of attributes that were discarded. Attributes
|
||||
// can be discarded because their keys are too long or because there are too many
|
||||
// attributes. If this value is 0, then no attributes were dropped.
|
||||
uint32 dropped_attributes_count = 4;
|
||||
}
|
||||
|
||||
// A reference to an Entity.
|
||||
// Entity represents an object of interest associated with produced telemetry: e.g spans, metrics, profiles, or logs.
|
||||
//
|
||||
// Status: [Development]
|
||||
message EntityRef {
|
||||
// The Schema URL, if known. This is the identifier of the Schema that the entity data
|
||||
// is recorded in. To learn more about Schema URL see
|
||||
// https://opentelemetry.io/docs/specs/otel/schemas/#schema-url
|
||||
//
|
||||
// This schema_url applies to the data in this message and to the Resource attributes
|
||||
// referenced by id_keys and description_keys.
|
||||
// TODO: discuss if we are happy with this somewhat complicated definition of what
|
||||
// the schema_url applies to.
|
||||
//
|
||||
// This field obsoletes the schema_url field in ResourceMetrics/ResourceSpans/ResourceLogs.
|
||||
string schema_url = 1;
|
||||
|
||||
// Defines the type of the entity. MUST not change during the lifetime of the entity.
|
||||
// For example: "service" or "host". This field is required and MUST not be empty
|
||||
// for valid entities.
|
||||
string type = 2;
|
||||
|
||||
// Attribute Keys that identify the entity.
|
||||
// MUST not change during the lifetime of the entity. The Id must contain at least one attribute.
|
||||
// These keys MUST exist in the containing {message}.attributes.
|
||||
repeated string id_keys = 3;
|
||||
|
||||
// Descriptive (non-identifying) attribute keys of the entity.
|
||||
// MAY change over the lifetime of the entity. MAY be empty.
|
||||
// These attribute keys are not part of entity's identity.
|
||||
// These keys MUST exist in the containing {message}.attributes.
|
||||
repeated string description_keys = 4;
|
||||
}
|
||||
735
third-party/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto
vendored
Normal file
735
third-party/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto
vendored
Normal file
|
|
@ -0,0 +1,735 @@
|
|||
// Copyright 2019, 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.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package opentelemetry.proto.metrics.v1;
|
||||
|
||||
import "opentelemetry/proto/common/v1/common.proto";
|
||||
import "opentelemetry/proto/resource/v1/resource.proto";
|
||||
|
||||
option csharp_namespace = "OpenTelemetry.Proto.Metrics.V1";
|
||||
option java_multiple_files = true;
|
||||
option java_package = "io.opentelemetry.proto.metrics.v1";
|
||||
option java_outer_classname = "MetricsProto";
|
||||
option go_package = "go.opentelemetry.io/proto/otlp/metrics/v1";
|
||||
|
||||
// MetricsData represents the metrics data that can be stored in a persistent
|
||||
// storage, OR can be embedded by other protocols that transfer OTLP metrics
|
||||
// data but do not implement the OTLP protocol.
|
||||
//
|
||||
// MetricsData
|
||||
// └─── ResourceMetrics
|
||||
// ├── Resource
|
||||
// ├── SchemaURL
|
||||
// └── ScopeMetrics
|
||||
// ├── Scope
|
||||
// ├── SchemaURL
|
||||
// └── Metric
|
||||
// ├── Name
|
||||
// ├── Description
|
||||
// ├── Unit
|
||||
// └── data
|
||||
// ├── Gauge
|
||||
// ├── Sum
|
||||
// ├── Histogram
|
||||
// ├── ExponentialHistogram
|
||||
// └── Summary
|
||||
//
|
||||
// The main difference between this message and collector protocol is that
|
||||
// in this message there will not be any "control" or "metadata" specific to
|
||||
// OTLP protocol.
|
||||
//
|
||||
// When new fields are added into this message, the OTLP request MUST be updated
|
||||
// as well.
|
||||
message MetricsData {
|
||||
// An array of ResourceMetrics.
|
||||
// For data coming from a single resource this array will typically contain
|
||||
// one element. Intermediary nodes that receive data from multiple origins
|
||||
// typically batch the data before forwarding further and in that case this
|
||||
// array will contain multiple elements.
|
||||
repeated ResourceMetrics resource_metrics = 1;
|
||||
}
|
||||
|
||||
// A collection of ScopeMetrics from a Resource.
|
||||
message ResourceMetrics {
|
||||
reserved 1000;
|
||||
|
||||
// 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 = 1;
|
||||
|
||||
// A list of metrics that originate from a resource.
|
||||
repeated ScopeMetrics scope_metrics = 2;
|
||||
|
||||
// The Schema URL, if known. This is the identifier of the Schema that the resource data
|
||||
// is recorded in. Notably, the last part of the URL path is the version number of the
|
||||
// schema: http[s]://server[:port]/path/<version>. To learn more about Schema URL see
|
||||
// https://opentelemetry.io/docs/specs/otel/schemas/#schema-url
|
||||
// This schema_url applies to the data in the "resource" field. It does not apply
|
||||
// to the data in the "scope_metrics" field which have their own schema_url field.
|
||||
string schema_url = 3;
|
||||
}
|
||||
|
||||
// A collection of Metrics produced by an Scope.
|
||||
message ScopeMetrics {
|
||||
// The instrumentation scope information for the metrics in this message.
|
||||
// Semantically when InstrumentationScope isn't set, it is equivalent with
|
||||
// an empty instrumentation scope name (unknown).
|
||||
opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
|
||||
|
||||
// A list of metrics that originate from an instrumentation library.
|
||||
repeated Metric metrics = 2;
|
||||
|
||||
// The Schema URL, if known. This is the identifier of the Schema that the metric data
|
||||
// is recorded in. Notably, the last part of the URL path is the version number of the
|
||||
// schema: http[s]://server[:port]/path/<version>. To learn more about Schema URL see
|
||||
// https://opentelemetry.io/docs/specs/otel/schemas/#schema-url
|
||||
// This schema_url applies to the data in the "scope" field and all metrics in the
|
||||
// "metrics" field.
|
||||
string schema_url = 3;
|
||||
}
|
||||
|
||||
// Defines a Metric which has one or more timeseries. The following is a
|
||||
// brief summary of the Metric data model. For more details, see:
|
||||
//
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md
|
||||
//
|
||||
// The data model and relation between entities is shown in the
|
||||
// diagram below. Here, "DataPoint" is the term used to refer to any
|
||||
// one of the specific data point value types, and "points" is the term used
|
||||
// to refer to any one of the lists of points contained in the Metric.
|
||||
//
|
||||
// - Metric is composed of a metadata and data.
|
||||
// - Metadata part contains a name, description, unit.
|
||||
// - Data is one of the possible types (Sum, Gauge, Histogram, Summary).
|
||||
// - DataPoint contains timestamps, attributes, and one of the possible value type
|
||||
// fields.
|
||||
//
|
||||
// Metric
|
||||
// +------------+
|
||||
// |name |
|
||||
// |description |
|
||||
// |unit | +------------------------------------+
|
||||
// |data |---> |Gauge, Sum, Histogram, Summary, ... |
|
||||
// +------------+ +------------------------------------+
|
||||
//
|
||||
// Data [One of Gauge, Sum, Histogram, Summary, ...]
|
||||
// +-----------+
|
||||
// |... | // Metadata about the Data.
|
||||
// |points |--+
|
||||
// +-----------+ |
|
||||
// | +---------------------------+
|
||||
// | |DataPoint 1 |
|
||||
// v |+------+------+ +------+ |
|
||||
// +-----+ ||label |label |...|label | |
|
||||
// | 1 |-->||value1|value2|...|valueN| |
|
||||
// +-----+ |+------+------+ +------+ |
|
||||
// | . | |+-----+ |
|
||||
// | . | ||value| |
|
||||
// | . | |+-----+ |
|
||||
// | . | +---------------------------+
|
||||
// | . | .
|
||||
// | . | .
|
||||
// | . | .
|
||||
// | . | +---------------------------+
|
||||
// | . | |DataPoint M |
|
||||
// +-----+ |+------+------+ +------+ |
|
||||
// | M |-->||label |label |...|label | |
|
||||
// +-----+ ||value1|value2|...|valueN| |
|
||||
// |+------+------+ +------+ |
|
||||
// |+-----+ |
|
||||
// ||value| |
|
||||
// |+-----+ |
|
||||
// +---------------------------+
|
||||
//
|
||||
// Each distinct type of DataPoint represents the output of a specific
|
||||
// aggregation function, the result of applying the DataPoint's
|
||||
// associated function of to one or more measurements.
|
||||
//
|
||||
// All DataPoint types have three common fields:
|
||||
// - Attributes includes key-value pairs associated with the data point
|
||||
// - TimeUnixNano is required, set to the end time of the aggregation
|
||||
// - StartTimeUnixNano is optional, but strongly encouraged for DataPoints
|
||||
// having an AggregationTemporality field, as discussed below.
|
||||
//
|
||||
// Both TimeUnixNano and StartTimeUnixNano values are expressed as
|
||||
// UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970.
|
||||
//
|
||||
// # TimeUnixNano
|
||||
//
|
||||
// This field is required, having consistent interpretation across
|
||||
// DataPoint types. TimeUnixNano is the moment corresponding to when
|
||||
// the data point's aggregate value was captured.
|
||||
//
|
||||
// Data points with the 0 value for TimeUnixNano SHOULD be rejected
|
||||
// by consumers.
|
||||
//
|
||||
// # StartTimeUnixNano
|
||||
//
|
||||
// StartTimeUnixNano in general allows detecting when a sequence of
|
||||
// observations is unbroken. This field indicates to consumers the
|
||||
// start time for points with cumulative and delta
|
||||
// AggregationTemporality, and it should be included whenever possible
|
||||
// to support correct rate calculation. Although it may be omitted
|
||||
// when the start time is truly unknown, setting StartTimeUnixNano is
|
||||
// strongly encouraged.
|
||||
message Metric {
|
||||
reserved 4, 6, 8;
|
||||
|
||||
// The name of the metric.
|
||||
string name = 1;
|
||||
|
||||
// A description of the metric, which can be used in documentation.
|
||||
string description = 2;
|
||||
|
||||
// The unit in which the metric value is reported. Follows the format
|
||||
// described by https://unitsofmeasure.org/ucum.html.
|
||||
string unit = 3;
|
||||
|
||||
// Data determines the aggregation type (if any) of the metric, what is the
|
||||
// reported value type for the data points, as well as the relatationship to
|
||||
// the time interval over which they are reported.
|
||||
oneof data {
|
||||
Gauge gauge = 5;
|
||||
Sum sum = 7;
|
||||
Histogram histogram = 9;
|
||||
ExponentialHistogram exponential_histogram = 10;
|
||||
Summary summary = 11;
|
||||
}
|
||||
|
||||
// Additional metadata attributes that describe the metric. [Optional].
|
||||
// Attributes are non-identifying.
|
||||
// Consumers SHOULD NOT need to be aware of these attributes.
|
||||
// These attributes MAY be used to encode information allowing
|
||||
// for lossless roundtrip translation to / from another data model.
|
||||
// Attribute keys MUST be unique (it is not allowed to have more than one
|
||||
// attribute with the same key).
|
||||
// The behavior of software that receives duplicated keys can be unpredictable.
|
||||
repeated opentelemetry.proto.common.v1.KeyValue metadata = 12;
|
||||
}
|
||||
|
||||
// Gauge represents the type of a scalar metric that always exports the
|
||||
// "current value" for every data point. It should be used for an "unknown"
|
||||
// aggregation.
|
||||
//
|
||||
// A Gauge does not support different aggregation temporalities. Given the
|
||||
// aggregation is unknown, points cannot be combined using the same
|
||||
// aggregation, regardless of aggregation temporalities. Therefore,
|
||||
// AggregationTemporality is not included. Consequently, this also means
|
||||
// "StartTimeUnixNano" is ignored for all data points.
|
||||
message Gauge {
|
||||
// The time series data points.
|
||||
// Note: Multiple time series may be included (same timestamp, different attributes).
|
||||
repeated NumberDataPoint data_points = 1;
|
||||
}
|
||||
|
||||
// Sum represents the type of a scalar metric that is calculated as a sum of all
|
||||
// reported measurements over a time interval.
|
||||
message Sum {
|
||||
// The time series data points.
|
||||
// Note: Multiple time series may be included (same timestamp, different attributes).
|
||||
repeated NumberDataPoint data_points = 1;
|
||||
|
||||
// aggregation_temporality describes if the aggregator reports delta changes
|
||||
// since last report time, or cumulative changes since a fixed start time.
|
||||
AggregationTemporality aggregation_temporality = 2;
|
||||
|
||||
// Represents whether the sum is monotonic.
|
||||
bool is_monotonic = 3;
|
||||
}
|
||||
|
||||
// Histogram represents the type of a metric that is calculated by aggregating
|
||||
// as a Histogram of all reported measurements over a time interval.
|
||||
message Histogram {
|
||||
// The time series data points.
|
||||
// Note: Multiple time series may be included (same timestamp, different attributes).
|
||||
repeated HistogramDataPoint data_points = 1;
|
||||
|
||||
// aggregation_temporality describes if the aggregator reports delta changes
|
||||
// since last report time, or cumulative changes since a fixed start time.
|
||||
AggregationTemporality aggregation_temporality = 2;
|
||||
}
|
||||
|
||||
// ExponentialHistogram represents the type of a metric that is calculated by aggregating
|
||||
// as a ExponentialHistogram of all reported double measurements over a time interval.
|
||||
message ExponentialHistogram {
|
||||
// The time series data points.
|
||||
// Note: Multiple time series may be included (same timestamp, different attributes).
|
||||
repeated ExponentialHistogramDataPoint data_points = 1;
|
||||
|
||||
// aggregation_temporality describes if the aggregator reports delta changes
|
||||
// since last report time, or cumulative changes since a fixed start time.
|
||||
AggregationTemporality aggregation_temporality = 2;
|
||||
}
|
||||
|
||||
// Summary metric data are used to convey quantile summaries,
|
||||
// a Prometheus (see: https://prometheus.io/docs/concepts/metric_types/#summary)
|
||||
// and OpenMetrics (see: https://github.com/prometheus/OpenMetrics/blob/4dbf6075567ab43296eed941037c12951faafb92/protos/prometheus.proto#L45)
|
||||
// data type. These data points cannot always be merged in a meaningful way.
|
||||
// While they can be useful in some applications, histogram data points are
|
||||
// recommended for new applications.
|
||||
// Summary metrics do not have an aggregation temporality field. This is
|
||||
// because the count and sum fields of a SummaryDataPoint are assumed to be
|
||||
// cumulative values.
|
||||
message Summary {
|
||||
// The time series data points.
|
||||
// Note: Multiple time series may be included (same timestamp, different attributes).
|
||||
repeated SummaryDataPoint data_points = 1;
|
||||
}
|
||||
|
||||
// AggregationTemporality defines how a metric aggregator reports aggregated
|
||||
// values. It describes how those values relate to the time interval over
|
||||
// which they are aggregated.
|
||||
enum AggregationTemporality {
|
||||
// UNSPECIFIED is the default AggregationTemporality, it MUST not be used.
|
||||
AGGREGATION_TEMPORALITY_UNSPECIFIED = 0;
|
||||
|
||||
// DELTA is an AggregationTemporality for a metric aggregator which reports
|
||||
// changes since last report time. 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.
|
||||
AGGREGATION_TEMPORALITY_DELTA = 1;
|
||||
|
||||
// CUMULATIVE is an AggregationTemporality for a metric aggregator which
|
||||
// reports changes since a fixed start time. 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.
|
||||
//
|
||||
// Note: Even though, when reporting changes since last report time, using
|
||||
// CUMULATIVE is valid, it is not recommended. This may cause problems for
|
||||
// systems that do not use start_time to determine when the aggregation
|
||||
// value was reset (e.g. Prometheus).
|
||||
AGGREGATION_TEMPORALITY_CUMULATIVE = 2;
|
||||
}
|
||||
|
||||
// DataPointFlags is defined as a protobuf 'uint32' type and is to be used as a
|
||||
// bit-field representing 32 distinct boolean flags. Each flag defined in this
|
||||
// enum is a bit-mask. To test the presence of a single flag in the flags of
|
||||
// a data point, for example, use an expression like:
|
||||
//
|
||||
// (point.flags & DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK) == DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK
|
||||
//
|
||||
enum DataPointFlags {
|
||||
// The zero value for the enum. Should not be used for comparisons.
|
||||
// Instead use bitwise "and" with the appropriate mask as shown above.
|
||||
DATA_POINT_FLAGS_DO_NOT_USE = 0;
|
||||
|
||||
// This DataPoint is valid but has no recorded value. This value
|
||||
// SHOULD be used to reflect explicitly missing data in a series, as
|
||||
// for an equivalent to the Prometheus "staleness marker".
|
||||
DATA_POINT_FLAGS_NO_RECORDED_VALUE_MASK = 1;
|
||||
|
||||
// Bits 2-31 are reserved for future use.
|
||||
}
|
||||
|
||||
// NumberDataPoint is a single data point in a timeseries that describes the
|
||||
// time-varying scalar value of a metric.
|
||||
message NumberDataPoint {
|
||||
reserved 1;
|
||||
|
||||
// The set of key/value pairs that uniquely identify the timeseries from
|
||||
// where this point belongs. The list may be empty (may contain 0 elements).
|
||||
// Attribute keys MUST be unique (it is not allowed to have more than one
|
||||
// attribute with the same key).
|
||||
// The behavior of software that receives duplicated keys can be unpredictable.
|
||||
repeated opentelemetry.proto.common.v1.KeyValue attributes = 7;
|
||||
|
||||
// StartTimeUnixNano is optional but strongly encouraged, see the
|
||||
// the detailed comments above Metric.
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
fixed64 start_time_unix_nano = 2;
|
||||
|
||||
// TimeUnixNano is required, see the detailed comments above Metric.
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
fixed64 time_unix_nano = 3;
|
||||
|
||||
// The value itself. A point is considered invalid when one of the recognized
|
||||
// value fields is not present inside this oneof.
|
||||
oneof value {
|
||||
double as_double = 4;
|
||||
sfixed64 as_int = 6;
|
||||
}
|
||||
|
||||
// (Optional) List of exemplars collected from
|
||||
// measurements that were used to form the data point
|
||||
repeated Exemplar exemplars = 5;
|
||||
|
||||
// Flags that apply to this specific data point. See DataPointFlags
|
||||
// for the available flags and their meaning.
|
||||
uint32 flags = 8;
|
||||
}
|
||||
|
||||
// HistogramDataPoint is a single data point in a timeseries that describes the
|
||||
// time-varying values of a Histogram. A Histogram contains summary statistics
|
||||
// for a population of values, it may optionally contain the distribution of
|
||||
// those values across a set of buckets.
|
||||
//
|
||||
// If the histogram contains the distribution of values, then both
|
||||
// "explicit_bounds" and "bucket counts" fields must be defined.
|
||||
// If the histogram does not contain the distribution of values, then both
|
||||
// "explicit_bounds" and "bucket_counts" must be omitted and only "count" and
|
||||
// "sum" are known.
|
||||
message HistogramDataPoint {
|
||||
reserved 1;
|
||||
|
||||
// The set of key/value pairs that uniquely identify the timeseries from
|
||||
// where this point belongs. The list may be empty (may contain 0 elements).
|
||||
// Attribute keys MUST be unique (it is not allowed to have more than one
|
||||
// attribute with the same key).
|
||||
// The behavior of software that receives duplicated keys can be unpredictable.
|
||||
repeated opentelemetry.proto.common.v1.KeyValue attributes = 9;
|
||||
|
||||
// StartTimeUnixNano is optional but strongly encouraged, see the
|
||||
// the detailed comments above Metric.
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
fixed64 start_time_unix_nano = 2;
|
||||
|
||||
// TimeUnixNano is required, see the detailed comments above Metric.
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
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.
|
||||
fixed64 count = 4;
|
||||
|
||||
// sum of the values in the population. If count is zero then this field
|
||||
// must be zero.
|
||||
//
|
||||
// Note: Sum should only be filled out when measuring non-negative discrete
|
||||
// events, and is assumed to be monotonic over the values of these events.
|
||||
// Negative events *can* be recorded, but sum should not be filled out when
|
||||
// doing so. This is specifically to enforce compatibility w/ OpenMetrics,
|
||||
// see: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#histogram
|
||||
optional double sum = 5;
|
||||
|
||||
// bucket_counts is an optional field contains the count values of histogram
|
||||
// for each bucket.
|
||||
//
|
||||
// The sum of the bucket_counts must equal the value in the count field.
|
||||
//
|
||||
// The number of elements in bucket_counts array must be by one greater than
|
||||
// the number of elements in explicit_bounds array. The exception to this rule
|
||||
// is when the length of bucket_counts is 0, then the length of explicit_bounds
|
||||
// must also be 0.
|
||||
repeated fixed64 bucket_counts = 6;
|
||||
|
||||
// explicit_bounds specifies buckets with explicitly defined bounds for values.
|
||||
//
|
||||
// The boundaries for bucket at index i are:
|
||||
//
|
||||
// (-infinity, explicit_bounds[i]] for i == 0
|
||||
// (explicit_bounds[i-1], explicit_bounds[i]] for 0 < i < size(explicit_bounds)
|
||||
// (explicit_bounds[i-1], +infinity) for i == size(explicit_bounds)
|
||||
//
|
||||
// The values in the explicit_bounds array must be strictly increasing.
|
||||
//
|
||||
// Histogram buckets are inclusive of their upper boundary, except the last
|
||||
// bucket where the boundary is at infinity. This format is intentionally
|
||||
// compatible with the OpenMetrics histogram definition.
|
||||
//
|
||||
// If bucket_counts length is 0 then explicit_bounds length must also be 0,
|
||||
// otherwise the data point is invalid.
|
||||
repeated double explicit_bounds = 7;
|
||||
|
||||
// (Optional) List of exemplars collected from
|
||||
// measurements that were used to form the data point
|
||||
repeated Exemplar exemplars = 8;
|
||||
|
||||
// Flags that apply to this specific data point. See DataPointFlags
|
||||
// for the available flags and their meaning.
|
||||
uint32 flags = 10;
|
||||
|
||||
// min is the minimum value over (start_time, end_time].
|
||||
optional double min = 11;
|
||||
|
||||
// max is the maximum value over (start_time, end_time].
|
||||
optional double max = 12;
|
||||
}
|
||||
|
||||
// ExponentialHistogramDataPoint is a single data point in a timeseries that describes the
|
||||
// time-varying values of a ExponentialHistogram of double values. A ExponentialHistogram contains
|
||||
// summary statistics for a population of values, it may optionally contain the
|
||||
// distribution of those values across a set of buckets.
|
||||
//
|
||||
message ExponentialHistogramDataPoint {
|
||||
// The set of key/value pairs that uniquely identify the timeseries from
|
||||
// where this point belongs. The list may be empty (may contain 0 elements).
|
||||
// Attribute keys MUST be unique (it is not allowed to have more than one
|
||||
// attribute with the same key).
|
||||
// The behavior of software that receives duplicated keys can be unpredictable.
|
||||
repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;
|
||||
|
||||
// StartTimeUnixNano is optional but strongly encouraged, see the
|
||||
// the detailed comments above Metric.
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
fixed64 start_time_unix_nano = 2;
|
||||
|
||||
// TimeUnixNano is required, see the detailed comments above Metric.
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
fixed64 time_unix_nano = 3;
|
||||
|
||||
// The number of values in the population. Must be
|
||||
// non-negative. This value must be equal to the sum of the "bucket_counts"
|
||||
// values in the positive and negative Buckets plus the "zero_count" field.
|
||||
fixed64 count = 4;
|
||||
|
||||
// The sum of the values in the population. If count is zero then this field
|
||||
// must be zero.
|
||||
//
|
||||
// Note: Sum should only be filled out when measuring non-negative discrete
|
||||
// events, and is assumed to be monotonic over the values of these events.
|
||||
// Negative events *can* be recorded, but sum should not be filled out when
|
||||
// doing so. This is specifically to enforce compatibility w/ OpenMetrics,
|
||||
// see: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#histogram
|
||||
optional double sum = 5;
|
||||
|
||||
// scale describes the resolution of the histogram. Boundaries are
|
||||
// located at powers of the base, where:
|
||||
//
|
||||
// base = (2^(2^-scale))
|
||||
//
|
||||
// The histogram bucket identified by `index`, a signed integer,
|
||||
// contains values that are greater than (base^index) and
|
||||
// less than or equal to (base^(index+1)).
|
||||
//
|
||||
// The positive and negative ranges of the histogram are expressed
|
||||
// separately. Negative values are mapped by their absolute value
|
||||
// into the negative range using the same scale as the positive range.
|
||||
//
|
||||
// scale is not restricted by the protocol, as the permissible
|
||||
// values depend on the range of the data.
|
||||
sint32 scale = 6;
|
||||
|
||||
// The count of values that are either exactly zero or
|
||||
// within the region considered zero by the instrumentation at the
|
||||
// tolerated degree of precision. This bucket stores values that
|
||||
// cannot be expressed using the standard exponential formula as
|
||||
// well as values that have been rounded to zero.
|
||||
//
|
||||
// Implementations MAY consider the zero bucket to have probability
|
||||
// mass equal to (zero_count / count).
|
||||
fixed64 zero_count = 7;
|
||||
|
||||
// positive carries the positive range of exponential bucket counts.
|
||||
Buckets positive = 8;
|
||||
|
||||
// negative carries the negative range of exponential bucket counts.
|
||||
Buckets negative = 9;
|
||||
|
||||
// Buckets are a set of bucket counts, encoded in a contiguous array
|
||||
// of counts.
|
||||
message Buckets {
|
||||
// The bucket index of the first entry in the bucket_counts array.
|
||||
//
|
||||
// Note: This uses a varint encoding as a simple form of compression.
|
||||
sint32 offset = 1;
|
||||
|
||||
// An array of count values, where bucket_counts[i] carries
|
||||
// the count of the bucket at index (offset+i). bucket_counts[i] is the count
|
||||
// of values greater than base^(offset+i) and less than or equal to
|
||||
// base^(offset+i+1).
|
||||
//
|
||||
// Note: By contrast, the explicit HistogramDataPoint uses
|
||||
// fixed64. This field is expected to have many buckets,
|
||||
// especially zeros, so uint64 has been selected to ensure
|
||||
// varint encoding.
|
||||
repeated uint64 bucket_counts = 2;
|
||||
}
|
||||
|
||||
// Flags that apply to this specific data point. See DataPointFlags
|
||||
// for the available flags and their meaning.
|
||||
uint32 flags = 10;
|
||||
|
||||
// (Optional) List of exemplars collected from
|
||||
// measurements that were used to form the data point
|
||||
repeated Exemplar exemplars = 11;
|
||||
|
||||
// The minimum value over (start_time, end_time].
|
||||
optional double min = 12;
|
||||
|
||||
// The maximum value over (start_time, end_time].
|
||||
optional double max = 13;
|
||||
|
||||
// ZeroThreshold may be optionally set to convey the width of the zero
|
||||
// region. Where the zero region is defined as the closed interval
|
||||
// [-ZeroThreshold, ZeroThreshold].
|
||||
// When ZeroThreshold is 0, zero count bucket stores values that cannot be
|
||||
// expressed using the standard exponential formula as well as values that
|
||||
// have been rounded to zero.
|
||||
double zero_threshold = 14;
|
||||
}
|
||||
|
||||
// SummaryDataPoint is a single data point in a timeseries that describes the
|
||||
// time-varying values of a Summary metric. The count and sum fields represent
|
||||
// cumulative values.
|
||||
message SummaryDataPoint {
|
||||
reserved 1;
|
||||
|
||||
// The set of key/value pairs that uniquely identify the timeseries from
|
||||
// where this point belongs. The list may be empty (may contain 0 elements).
|
||||
// Attribute keys MUST be unique (it is not allowed to have more than one
|
||||
// attribute with the same key).
|
||||
// The behavior of software that receives duplicated keys can be unpredictable.
|
||||
repeated opentelemetry.proto.common.v1.KeyValue attributes = 7;
|
||||
|
||||
// StartTimeUnixNano is optional but strongly encouraged, see the
|
||||
// the detailed comments above Metric.
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
fixed64 start_time_unix_nano = 2;
|
||||
|
||||
// TimeUnixNano is required, see the detailed comments above Metric.
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
fixed64 time_unix_nano = 3;
|
||||
|
||||
// count is the number of values in the population. Must be non-negative.
|
||||
fixed64 count = 4;
|
||||
|
||||
// sum of the values in the population. If count is zero then this field
|
||||
// must be zero.
|
||||
//
|
||||
// Note: Sum should only be filled out when measuring non-negative discrete
|
||||
// events, and is assumed to be monotonic over the values of these events.
|
||||
// Negative events *can* be recorded, but sum should not be filled out when
|
||||
// doing so. This is specifically to enforce compatibility w/ OpenMetrics,
|
||||
// see: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#summary
|
||||
double sum = 5;
|
||||
|
||||
// Represents the value at a given quantile of a distribution.
|
||||
//
|
||||
// To record Min and Max values following conventions are used:
|
||||
// - The 1.0 quantile is equivalent to the maximum value observed.
|
||||
// - The 0.0 quantile is equivalent to the minimum value observed.
|
||||
//
|
||||
// See the following issue for more context:
|
||||
// https://github.com/open-telemetry/opentelemetry-proto/issues/125
|
||||
message ValueAtQuantile {
|
||||
// The quantile of a distribution. Must be in the interval
|
||||
// [0.0, 1.0].
|
||||
double quantile = 1;
|
||||
|
||||
// The value at the given quantile of a distribution.
|
||||
//
|
||||
// Quantile values must NOT be negative.
|
||||
double value = 2;
|
||||
}
|
||||
|
||||
// (Optional) list of values at different quantiles of the distribution calculated
|
||||
// from the current snapshot. The quantiles must be strictly increasing.
|
||||
repeated ValueAtQuantile quantile_values = 6;
|
||||
|
||||
// Flags that apply to this specific data point. See DataPointFlags
|
||||
// for the available flags and their meaning.
|
||||
uint32 flags = 8;
|
||||
}
|
||||
|
||||
// A representation of an exemplar, which is a sample input measurement.
|
||||
// Exemplars also hold information about the environment when the measurement
|
||||
// was recorded, for example the span and trace ID of the active span when the
|
||||
// exemplar was recorded.
|
||||
message Exemplar {
|
||||
reserved 1;
|
||||
|
||||
// The set of key/value pairs that were filtered out by the aggregator, but
|
||||
// recorded alongside the original measurement. Only key/value pairs that were
|
||||
// filtered out by the aggregator should be included
|
||||
repeated opentelemetry.proto.common.v1.KeyValue filtered_attributes = 7;
|
||||
|
||||
// time_unix_nano is the exact time when this exemplar was recorded
|
||||
//
|
||||
// Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January
|
||||
// 1970.
|
||||
fixed64 time_unix_nano = 2;
|
||||
|
||||
// The value of the measurement that was recorded. An exemplar is
|
||||
// considered invalid when one of the recognized value fields is not present
|
||||
// inside this oneof.
|
||||
oneof value {
|
||||
double as_double = 3;
|
||||
sfixed64 as_int = 6;
|
||||
}
|
||||
|
||||
// (Optional) Span ID of the exemplar trace.
|
||||
// span_id may be missing if the measurement is not recorded inside a trace
|
||||
// or if the trace is not sampled.
|
||||
bytes span_id = 4;
|
||||
|
||||
// (Optional) Trace ID of the exemplar trace.
|
||||
// trace_id may be missing if the measurement is not recorded inside a trace
|
||||
// or if the trace is not sampled.
|
||||
bytes trace_id = 5;
|
||||
}
|
||||
45
third-party/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto
vendored
Normal file
45
third-party/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2019, 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.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package opentelemetry.proto.resource.v1;
|
||||
|
||||
import "opentelemetry/proto/common/v1/common.proto";
|
||||
|
||||
option csharp_namespace = "OpenTelemetry.Proto.Resource.V1";
|
||||
option java_multiple_files = true;
|
||||
option java_package = "io.opentelemetry.proto.resource.v1";
|
||||
option java_outer_classname = "ResourceProto";
|
||||
option go_package = "go.opentelemetry.io/proto/otlp/resource/v1";
|
||||
|
||||
// Resource information.
|
||||
message Resource {
|
||||
// Set of attributes that describe the resource.
|
||||
// Attribute keys MUST be unique (it is not allowed to have more than one
|
||||
// attribute with the same key).
|
||||
// The behavior of software that receives duplicated keys can be unpredictable.
|
||||
repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;
|
||||
|
||||
// The number of dropped attributes. If the value is 0, then
|
||||
// no attributes were dropped.
|
||||
uint32 dropped_attributes_count = 2;
|
||||
|
||||
// Set of entities that participate in this Resource.
|
||||
//
|
||||
// Note: keys in the references MUST exist in attributes of this message.
|
||||
//
|
||||
// Status: [Development]
|
||||
repeated opentelemetry.proto.common.v1.EntityRef entity_refs = 3;
|
||||
}
|
||||
|
|
@ -152,7 +152,7 @@ static std::string FieldTypeToIcingaName(const Field& field, bool inner)
|
|||
if (field.Attributes & FAEnum)
|
||||
return "Number";
|
||||
|
||||
if (ftype == "int" || ftype == "double")
|
||||
if (ftype == "int" || ftype == "int64_t" || ftype == "double")
|
||||
return "Number";
|
||||
else if (ftype == "bool")
|
||||
return "Boolean";
|
||||
|
|
|
|||
Loading…
Reference in a new issue