diff --git a/.github/workflows/eumserver_release.yml b/.github/workflows/eumserver_release.yml index 39a4370..cdd135f 100644 --- a/.github/workflows/eumserver_release.yml +++ b/.github/workflows/eumserver_release.yml @@ -13,6 +13,7 @@ jobs: build_and_release: name: Build and release EUM Server runs-on: ubuntu-latest + environment: release container: openjdk:21-jdk-slim needs: [test_eum_server] steps: @@ -24,7 +25,7 @@ jobs: run: ./gradlew assemble -PbuildVersion=${{ github.ref_name }} # copy jar into Docker folder - name: Prepare Docker artifact - run: ./gradlew copyServerJar + run: ./gradlew copyServerJar -PbuildVersion=${{ github.ref_name }} - name: Upload Docker artifact uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/eumserver_test.yml b/.github/workflows/eumserver_test.yml index 4a389a2..5c532a8 100644 --- a/.github/workflows/eumserver_test.yml +++ b/.github/workflows/eumserver_test.yml @@ -4,12 +4,14 @@ on: push: branches: - main + - '3.0.0' paths-ignore: - '**.md' - '.github/**' pull_request: branches: - main + - '3.0.0' paths-ignore: - '**.md' - '.github/**' diff --git a/README.md b/README.md index 1a0e849..a3b9ab3 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,54 @@ -# inspectit-ocelot-eum-server +# inspectIT Ocelot EUM-server [![EUM Server Tests](https://github.com/inspectIT/inspectit-ocelot-eum-server/actions/workflows/eumserver_test.yml/badge.svg?branch=main)](https://github.com/inspectIT/inspectit-ocelot-eum-server/actions/workflows/eumserver_test.yml) -This server provides Enduser Monitoring data by using the [OpenCensus](https://github.com/census-instrumentation/opencensus-java) toolkit. +This server provides real user monitoring data by using [OpenTelemetry](https://opentelemetry.io/docs/languages/java/). + +For an extensive documentation, please check out https://inspectit.github.io/inspectit-ocelot/docs/enduser-monitoring/enduser-monitoring-server. ## Metrics -The inspectit-ocelot server offers a backend for Javascript monitoring with [Boomerang](https://developer.akamai.com/tools/boomerang/docs/index.html). -Boomerang is a Javascript metrics agent, which is able to capture arbitrary customizable metrics. -By injecting the following snipped in your webpage, all measured metrics are sent to the inspectit-ocelot-eum-server: + +The inspectIT Ocelot EUM-server offers a backend for JavaScript monitoring with [Boomerang](https://akamai.github.io/boomerang/akamai/). +Boomerang is a JavaScript metrics agent, which is able to capture arbitrary customizable metrics. +By injecting the following snipped in your webpage, all measured metrics are sent to the EUM-server: ```javascript - - - + + + + ``` -Boomerang recommends to use an advanced injection, where the boomerang agent is loaded in an asynchronous way. -For further information, please visit the [Boomerang documentation](https://developer.akamai.com/tools/boomerang/docs/index.html). +Boomerang recommends to use an advanced injection, where the boomerang agent is loaded in an **asynchronous** way. +For further information, please visit the [Boomerang documentation](https://akamai.github.io/boomerang/akamai/#toc8__anchor). -If enabled, the server exposes the metrics by using the [Prometheus exporter](https://github.com/census-instrumentation/opencensus-java/tree/master/exporters/stats/prometheus). -A tutorial on how to install Prometheus can be found [here](https://opencensus.io/codelabs/prometheus/#0). +If enabled, the server exposes the metrics via [metrics exporter](#metrics-exporters). ## Server Setup -Before starting the server, please build the server by cloning the repository and executing the following command or download the [latest release](https://github.com/inspectIT/inspectit-ocelot/releases). + +You can download the [latest release](https://github.com/inspectIT/inspectit-ocelot-eum-server/releases) in this repository. +Or build the server by cloning the repository and executing the following command: + ```bash -$ ./gradlew build +$ ./gradlew assemble ``` + Start the server with the following command: ```bash $ java -jar inspectit-ocelot-eum-{version}.jar ``` -By default, the server is starting with the port `8080`. + +By default, the server will start with the port `8080`. You can simply configure the port by using the Java property `-Dserver.port=[port]`: ```bash $ java -Dserver.port=[port] -jar inspectit-ocelot-eum-0.0.1-SNAPSHOT.jar ``` -Our server is delivered with a default configuration -supporting the metrics `t_page`, `t_done`, `rt.tstart`, `rt.nstart` and `rt.end` of the Boomerang plugin [RT](https://developer.akamai.com/tools/boomerang/docs/BOOMR.plugins.RT.html). + +Our server is delivered with a default configuration supporting the metrics +`t_page`, `t_done`, `rt.tstart`, `rt.nstart`, `rt.end` and `restiming` of the Boomerang [RT](https://akamai.github.io/boomerang/akamai/BOOMR.plugins.RT.html) plugin. In order to provide a custom configuration, please set the Java property `-Dspring.config.location=file:[path-to-config]`: ```bash @@ -47,213 +56,252 @@ $ java -Dserver.port=[port] -Dspring.config.location=file:[path-to-config] -jar ``` ## Configuration -The configuration file defines the mapping between the concrete Boomerang metric and a OpenCensus metric, as the following sample configuration file shows: + +The configuration file defines the mapping between the concrete Boomerang metric and a OpenTelemetry metric, +as the following sample configuration file shows: + ```yaml inspectit-eum-server: definitions: + + resource_time: + enabled: true + description: Response end time of the resource loading + instrument-type: HISTOGRAM + value-type: DOUBLE + value-expression: "{restiming}" + unit: ms + views: + resource_time: + aggregation: HISTOGRAM + attributes: + initiatorType: true + cached: true + crossOrigin: true + page_ready_time: - measure-type: LONG - beacon-field: t_page + instrument-type: HISTOGRAM + value-type: LONG + value-expression: "{t_page}" unit: ms views: - '[page_ready_time/SUM]': {aggregation: SUM} - '[page_ready_time/COUNT]': {aggregation: COUNT} + page_ready_time: { aggregation: HISTOGRAM } + load_time: - measure-type: LONG - beacon-field: t_done + instrument-type: HISTOGRAM + value-type: LONG + value-expression: "{t_done}" + beacon-requirements: + - field: rt.quit + requirement: NOT_EXISTS unit: ms views: - '[load_time/SUM]': {aggregation: SUM} - '[load_time/COUNT]': {aggregation: COUNT} + load_time: { aggregation: HISTOGRAM } + + calc_load_time: + instrument-type: HISTOGRAM + value-type: LONG + value-expression: "{rt.end} - {rt.tstart}" + beacon-requirements: + - field: rt.quit + requirement: NOT_EXISTS + unit: ms + views: + calc_load_time: { aggregation: HISTOGRAM } start_timestamp: - measure-type: LONG - beacon-field: rt.tstart + instrument-type: GAUGE + value-type: LONG + value-expression: "{rt.tstart}" unit: ms + views: + start_timestamp: + aggregation: LAST_VALUE + attributes: { APPLICATION: true } navigation_start_timestamp: - measure-type: LONG - beacon-field: rt.nstart + instrument-type: GAUGE + value-type: LONG + value-expression: "{rt.nstart}" unit: ms + views: + navigation_start_timestamp: + aggregation: LAST_VALUE + attributes: { APPLICATION: true } end_timestamp: - measure-type: LONG - beacon-field: rt.end + instrument-type: GAUGE + value-type: LONG + value-expression: "{rt.end}" unit: ms views: end_timestamp: aggregation: LAST_VALUE - tags: {APPLICATION : true} - tags: + attributes: { APPLICATION: true } + + attributes: extra: APPLICATION: my-application beacon: - URL: u - OS: ua.plt + URL: + input: u + null-as-empty: true + OS: + input: ua.plt + null-as-empty: true global: - URL - OS - COUNTRY_CODE + exporters: metrics: - prometheus: + otlp: enabled: ENABLED - host: localhost - port: 8888 + protocol: grpc + endpoint: localhost:4317 tracing: otlp: enabled: ENABLED protocol: grpc endpoint: localhost:4317 - - security: - enabled: false - authorization-header: Authorization - permitted-urls: - - "/actuator/health" - - "/boomerang/**" - auth-provider: - simple: - enabled: false - watch: true - frequency: 60s - token-directory: "" # Empty by default to force users to provide one - default-file-name: "default-token-file.yaml" ``` -##### Metrics Definition -A metric is defined through the following attributes: -* `name`: Defines the name of the metric. The name of the exposed view will have the used aggregation as suffix. -* `measure-type`: Can be either `LONG` or `DOUBLE`. + +#### Metrics Definition + +A metric is defined through the following properties: + +* `name`: Defines the name of the metric. +* `instrument-type`: The OpenTelemetry instrument to be used. Can be either `COUNTER`, `UP_DOWN_COUNTER`, `GAUGE` or `HISTOGRAM`. +* `value-type`: Can be either `LONG` or `DOUBLE`. * `beacon-field`: The beacon key name, which is used as source of metric. -* `description`: Optional. Defines an additional description of the exposed metric. * `unit`: The unit of the metric. -* `tag-keys`: Optional. Defines a list of tag keys, which are exposed with the current metric. -* `views`: A list of the views, which should be exposed. The aggregation can be either `SUM`, `COUNT`, `LAST_VALUE` or `HISTORGRAM`. For using `HISTOGRAM`, the field `bucket-boundaries` is mandatory. -* `bucket-boundaries`: Used for the `HISTOGRAM` aggregation, defines the bucket boundaries as list of Doubles. +* `description`: Defines an additional description of the exposed metric (optional). +* `views`: A list of the views, which should be exposed. If no view are specified, OpenTelemetry will create default views automatically. + +#### View Definition + +A view is defined through the following properties: + +* `name`: Defines the name of the view. +* `aggregation`: There are OTel aggregations (`SUM`, `LAST_VALUE`, `HISTORGRAM`; `EXPONENTIAL_HISTOGRAM`) as well as custom aggregations (`QUANTILES`, `SMOOTHED_AVERAGE`). +* `attributes`: Defines a list of attribute keys, which are exposed with the current metric. +* `bucket-boundaries`: Used for the `HISTOGRAM` aggregation, defines the bucket boundaries as list of doubles. +* `cardinality-limit`: Defines the maximum amount of unique combinations of attributes for this view. +* `max-buckets`: Used for `EXPONENTIAL_HISTOGRAM`, defines the max number of positive buckets and negative buckets +* `max-scale`: Used for `EXPONENTIAL_HISTOGRAM`, defines the maximum and initial scale. +* `quantiles`: Used for `QUANTILES`, defines which percentiles shall be captured as list of doubles. +* `drop-upper`: Used for `SMOOTHED_AVERAGE`, how many metrics in the upper range shall be dropped (0-1) +* `drop-lower`: Used for `SMOOTHED_AVERAGE`, how many metrics in the lower range shall be dropped (0-1) +* `time-window`: Used for `QUANTILES` & `SMOOTHED_AVERAGE` to group by metrics. +* `max-buffered-points`: Used for `QUANTILES` & `SMOOTHED_AVERAGE`, to set a maximum amount of metrics per time-window. +* `description`: Defines an additional description of the exposed view (optional). + +#### Attributes Definition -##### Tags Definition We distinguish between to different types of tags: * `extra`- tags: Extra tags define tags, which are manually set in the configuration. The field `extra` holds a list of key-value mappings. * `beacon`- tags: Beacon tags define tags, whose tag value is resolved by a beacon entry. The defined value of the `beacon` map will be resolved by using the provided beacon. In order to provide selected tags to each measurement by default, tags can be defined as global. `global` holds a list of already defined tags, which will be then exposed for each measurement. -##### Automated Geolocation Detection -By using the tag `COUNTRY_CODE`, the geolocation of the requester is resolved by using the requester IP and the [GeoLite2 database](https://www.maxmind.com). If the IP cannot be resolved, the tag value will be empty. +#### Automated Geolocation Detection + +By using the tag `COUNTRY_CODE`, the geolocation of the requester is resolved by using the requester IP +and the [GeoLite2 database](https://www.maxmind.com). If the IP cannot be resolved, the tag value will be empty. -##### Metrics Exporters +### Metrics Exporters The inspectIT Ocelot EUM Server currently supports the following metrics exporters: -|Exporter |Supports run-time updates| Push / Pull |Enabled by default| -|---|---|---|---| -|[Prometheus Exporter](#prometheus-exporter)|Yes|Pull|No| -|[InfluxDB Exporter](#influxdb-exporter)|Yes|Push|No| -|[OTLP Exporter (Metrics)](#otlp-exporter-metrics) [[Homepage](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/otlp/metrics)]|Yes|Push|No| +| Exporter | Push / Pull | Enabled by default | +|----------------------------------------------------------------------------------------------------------------------------------|-------------|--------------------| +| [Prometheus Exporter](#prometheus-exporter) | Pull | No | +| [OTLP Exporter (Metrics)](#otlp-exporter-metrics) [[Homepage](https://opentelemetry.io/docs/languages/java/sdk/#otlp-exporters)] | Push | No | + +##### Prometheus Exporter -###### Prometheus Exporter If `enabled` is set to `ENABLED`, the exporter exposes the metrics under ```bash http://[host]:[port]/metrics ``` Prometheus exporter exposes the metrics in Prometheus format. -When enabled, the EUM starts a Prometheus HTTP server in parallel with your application. +When enabled, the EUM-server starts a Prometheus HTTP server in parallel with your application. The server is by default started on the port `8888` and metrics can then be accessed by visiting http://localhost:8888/metrics. The following properties are nested properties below the `inspectit-eum-server.exporters.metrics.prometheus` property: -|Property | Default | Description -|---|------------|---| -|`.enabled`| `DISABLED` |If `ENABLED` or `IF_CONFIGURED`, the inspectIT Ocelot agent will try to start the Prometheus metrics exporter and Prometheus HTTP server. -|`.host`| `0.0.0.0` |The hostname or network address to which the Prometheus HTTP server should bind. -|`.port`| `8888` |The port the Prometheus HTTP server should use. +| Property | Default | Description | +|------------|------------|-------------------------------------------------------------------------------------------------------------------------------| +| `.enabled` | `DISABLED` | If `ENABLED` or `IF_CONFIGURED`, the EUM-server will try to start the Prometheus metrics exporter and Prometheus HTTP server. | +| `.host` | `0.0.0.0` | The hostname or network address to which the Prometheus HTTP server should bind. | +| `.port` | `8888` | The port the Prometheus HTTP server should use. | +##### OTLP Exporter (Metrics) -###### InfluxDB Exporter -If enabled, metrics are pushed at a specified interval directly to a given InfluxDB v1.x instance. -To enable the InfluxDB Exporters, it is only required to specify the `endpoint`. +The OpenTelemetry Protocol (OTLP) exporters export the metrics to the desired endpoint at a specified interval. +To enable the OTLP exporters, it is only required to specify the `endpoint`. -The InfluxDB exporter provides a special handling for counter and sum metrics which is enabled by default and can be disabled using the `counters-as-differences` option. -Usually, the absolute value of such counters is irrelevant when querying the data, instead you want to have the increase of it over a certain period of time. -With the `counters-as-differences` option enabled, counters are preprocessed before being exported. +The following properties are nested properties below the `inspectit-eum-server.exporters.metrics.otlp` property: -Instead of writing the absolute value of each counter into the InfluxDB, only the increase since the last export will be written. -In addition, no value will be exported, if the counter has not changed since the last export. -This can greatly reduce the amount of data written into the InfluxDB, especially if the metrics are quite constant and won't change much. +| Property | Default | Description | +|--------------------------|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `.enabled` | `IF_CONFIGURED` | If `ENABLED` or `IF_CONFIGURED`, the EUM-server will try to start the OTLP metrics exporter. | +| `.endpoint` | `null` | Target to which the exporter is going to send metrics, e.g. `http://localhost:4317` | +| `.protocol` | `null` | The transport protocol, see [OTel documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). Supported protocols are `grpc` and `http/protobuf`. | +| `.preferred-temporality` | `CUMULATIVE` | The preferred output aggregation temporality, see [OTel documentation](https://opentelemetry.io/docs/languages/java/configuration/). Supported values are `CUMULATIVE` and `DELTA`. | +| `.headers` | `null` | Key-value pairs to be used as headers associated with gRPC or HTTP requests, see [OTel documentation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md). | +| `.compression` | `NONE` | The compression method, see [OTel documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). Supported compression methods are `gzip` and `none`. | +| `.timeout` | `10s` | Maximum time the OTLP exporter will wait for each batch export, see [OTel documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). | -The following properties are nested properties below the `inspectit.exporters.metrics.influx` property: +##### InfluxDB Exporter (removed) +Since version 3.0.0 the InspectIT Ocelot EUM-Server does no longer support the InfluxDB exporter. +Please use the OTLP Exporter instead. -|Property | Default | Description| -|---|-----------------------------------------|---| -|`.enabled`| `IF_CONFIGURED` |If `ENABLED` or `IF_CONFIGURED`, the agent will try to start the Influx exporter. If the url is not set, it will log a warning if set to `ENABLED` but fail silently if set to `IF_CONFIGURED`.| -|`.endpoint`| `null` |The HTTP endpoint of the InfluxDB, e.g. `http://localhost:8086`.| -|`.user`| `null` | The user to use for connecting to the InfluxDB, can not be empty.| -|`.password`| `null` |The password to use for connecting to the InfluxDB, can be not be empty.| -|`.database`| `inspectit` | The InfluxDB database to which the metrics are pushed.| -|`.retention-policy`| `autogen` | The retention policy of the database to use for writing metrics.| -|`.create-database`| `true` | If enabled, the database defined by the `database` property is automatically created on startup with an `autogen` retention policy if it does not exist yet.| -|`.export-interval`| refers to `inspectit.metrics.frequency` |Defines how often metrics are pushed to the InfluxDB.| -|`.counters-as-differences`| `true` |Defines whether counters are exported using their absolute value or as the increase between exports| -|`buffer-size`| `40` | In case the InfluxDB is not reachable, failed writes will be buffered and written on the next export. This value defines the maximum number of batches to buffer.| +## Traces -###### OTLP Exporter (Metrics) +The inspectIT Ocelot EUM-server also offers a backend for JavaScript monitoring with the [OpenTelemetry-Boomerang-Plugin](https://github.com/inspectIT/boomerang-opentelemetry-plugin). +The self-made plugin allows to record traces via Boomerang and send them to the EUM-server. +You can include the plugin as any other boomerang plugin. +If enabled, the server exposes the traces via [trace exporters](#trace-exporters). -The OpenTelemetry Protocol (OTLP) exporters export the metrics to the desired endpoint at a specified interval. -To enable the OTLP exporters, it is only required to specify the `endpoint`. +### Trace Exporters -The following properties are nested properties below the `inspectit-eum-server.exporters.metrics.otlp` property: - -| Property | Default | Description | -|--------------------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `.enabled` | `IF_CONFIGURED` | If `ENABLED` or `IF_CONFIGURED`, the inspectIT Ocelot agent will try to start the OTLP gRPC metrics exporter. | -| `.endpoint` | `null` | Target to which the exporter is going to send metrics, e.g. `http://localhost:4317` | -| `.protocol` | `null` | The transport protocol, see [OTEL documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). Supported protocols are `grpc` and `http/protobuf`. | -| `.preferred-temporality` | `CUMULATIVE` | The preferred output aggregation temporality, see [OTEL documentation](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md). Supported values are `CUMULATIVE` and `DELTA`.| -| `.headers` | `null` | Key-value pairs to be used as headers associated with gRPC or HTTP requests, see [OTEL documentation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md).| -| `.compression` | `NONE` | The compression method, see [OTEL documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). Supported compression methods are `gzip` and `none`. | -| `.timeout` | `10s` | Maximum time the OTLP exporter will wait for each batch export, see [OTEL documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). | - -##### Trace Exporters - -Tracing exporters are responsible for passing the recorded tracing data to a corresponding storage. +Trace exporters are responsible for passing the recorded tracing data to a corresponding storage. The inspectIT Ocelot EUM Server currently supports the following trace exporters: -* [OTLP (Traces)](#otlp-exporter-traces) [[Homepage](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/otlp/trace)] +* [OTLP (Traces)](#otlp-exporter-traces) [[Homepage](https://opentelemetry.io/docs/languages/java/sdk/#otlp-exporters)] -###### General Trace Exporter Settings +Furthermore, you can configure all exporters to mask IP addresses within attributes by setting +`inspectit-eum-server.exporters.tracing.mask-span-ip-addresses` to `true`. -These settings apply to all trace exporters and can set below the `inspectit-eum-server.exporters.tracing` property. - -| Property | Default | Description | -|-----------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `.service-name` | `${inspectit.service-name}` | The value of this property will be used to identify the service a trace came from. Please note that changes of this property only take effect after restarting the agent. | - -###### OTLP Exporter (Traces) +##### OTLP Exporter (Traces) The OpenTelemetry Protocol (OTLP) exporters export the Traces in OTLP to the desired endpoint at a specified interval. By default, the OTLP exporters are enabled but the URL `endpoint` needed for the exporter to actually start is set to `null`. -The following properties are nested properties below the `inspectit.exporters.tracing.otlp` property: +The following properties are nested properties below the `inspectit-eum-server.exporters.tracing.otlp` property: | Property | Default | Description | |----------------|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `.enabled` | `IF_CONFIGURED` | If `ENABLED` or `IF_CONFIGURED`, the inspectIT Ocelot agent will try to start the OTLP gRPC trace exporter. | +| `.enabled` | `IF_CONFIGURED` | If `ENABLED` or `IF_CONFIGURED`, the EUM-server will try to start the OTLP gRPC trace exporter. | | `.endpoint` | `null` | Target to which the exporter is going to send traces, e.g. `http://localhost:4317` | -| `.protocol` | `grpc` | The transport protocol, see [OTEL documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). Supported protocols are `grpc` and `http/protobuf`. | -| `.headers` | `null` | Key-value pairs to be used as headers associated with gRPC or HTTP requests, see [OTEL documentation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md). | -| `.compression` | `NONE` | The compression method, see [OTEL documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). Supported compression methods are `gzip` and `none`. | -| `.timeout` | `10s` | Maximum time the OTLP exporter will wait for each batch export, see [OTEL documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). | +| `.protocol` | `grpc` | The transport protocol, see [OTel documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). Supported protocols are `grpc` and `http/protobuf`. | +| `.headers` | `null` | Key-value pairs to be used as headers associated with gRPC or HTTP requests, see [OTel documentation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md). | +| `.compression` | `NONE` | The compression method, see [OTel documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). Supported compression methods are `gzip` and `none`. | +| `.timeout` | `10s` | Maximum time the OTLP exporter will wait for each batch export, see [OTel documentation](https://opentelemetry.io/docs/reference/specification/protocol/exporter/). | -###### Jaeger Exporter (removed) +##### Jaeger Exporter (removed) Since version 2.6.2 the InspectIT EUM-Server does no longer support the Jaeger exporter. +Please use the OTLP Exporter instead. -##### Security -Currently, the EUM Server only supports a simple API token security concept. In future, additional authentication providers -will be supported. +### Security +Currently, the EUM Server only supports a simple API token security concept. +In the future, additional authentication providers will be supported. Security can be enabled by changing spring `application.yml`, using system properties or environment variables. ```yaml @@ -270,8 +318,8 @@ inspectit-eum-server: .... ``` -###### Simple Token Provider -The simple token provider can be enabled from config file... +##### Simple Token Provider +The simple token provider can be enabled from config file: ```yaml inspectit-eum-server: @@ -309,15 +357,15 @@ The format for the token files is as follows: token: "any token value you like" ``` +### Build Docker Image Locally. -##### Build Docker Image Locally. - -In order to build a docker image locally, the eum-server should be build locally and the resulting jar should be renamed +In order to build a docker image locally, the eum-server should be built locally and the resulting jar should be renamed to ```inspectit-ocelot-eum-server.jar``` and copied to the ./docker directory -##### How to Release +### +How to Release To create a new release, you have to create a new git tag and push it on to GitHub. -This Tag is the new version number of the release. Afterwards the release build will be automatically triggered. +This Tag is the new version number of the release. Afterward the release build will be automatically triggered. Important tasks to check first are `dependencyUpdates` and `dependencyUpdates[Major|Minor]` for newer (patch, minor, major) versions and `dependencyCheckAnalyze` for security issues in the used dependencies. diff --git a/build.gradle b/build.gradle index 5719624..be5f1e5 100644 --- a/build.gradle +++ b/build.gradle @@ -2,8 +2,8 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask plugins { id "org.springframework.boot" version "${springBootVersion}" - id "org.cyclonedx.bom" version "${cyclonedxBomVersion}" id "io.spring.dependency-management" version "${springDependencyManangementVersion}" + id "org.cyclonedx.bom" version "${cyclonedxBomVersion}" id "org.owasp.dependencycheck" version "${owaspDependencyCheckVersion}" id "com.github.ben-manes.versions" version "${versionsPlugin}" } @@ -13,7 +13,6 @@ repositories { } apply plugin: "java" -apply plugin: "jacoco" group = "rocks.inspectit.ocelot" @@ -55,7 +54,7 @@ tasks.register('generateVersionFile') { doLast { def currentDate = new Date().toString() ext.versionFile.withWriter("UTF-8") { writer -> - writer << "$version\n$currentDate\n$boomerangVersion" + writer << "$version\n$currentDate\n$boomerangVersion\n$openTelemetryVersion" } } } @@ -82,7 +81,7 @@ bootJar { } manifest { - attributes "Start-Class": "rocks.inspectit.oce.eum.server.EUMServerApplication" + attributes "Start-Class": "rocks.inspectit.ocelot.eum.server.EUMServerApplication" } // include version file @@ -120,46 +119,38 @@ test { dependencies { implementation( + // spring boot "org.springframework.boot:spring-boot-starter-web", "org.springframework.boot:spring-boot-starter-actuator", "org.springframework.boot:spring-boot-starter-validation", "org.springframework.boot:spring-boot-starter-security", "org.yaml:snakeyaml:${snakeYamlVersion}", - // Has to be included, but is transitive to spring + // has to be included, but is transitive to spring "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", - // pin Prometheus client to 0.6.0 to prevent auto prefixing counter metrics with "_total" - // see: https://github.com/prometheus/client_java/issues/640, https://github.com/prometheus/client_java/pull/653 - "io.prometheus:simpleclient:${prometheusClientVersion}", - "io.prometheus:simpleclient_common:${prometheusClientVersion}", - "io.prometheus:simpleclient_httpserver:${prometheusClientVersion}", - - "io.opencensus:opencensus-api:${openCensusVersion}", - "io.opencensus:opencensus-impl:${openCensusVersion}", - "io.opencensus:opencensus-exporter-stats-prometheus:${openCensusVersion}", - "rocks.inspectit:opencensus-influxdb-exporter:${openCensusInfluxdbExporterVersion}", - - "io.grpc:grpc-context:${grpcVersion}", - + // opentelemetry platform("io.opentelemetry:opentelemetry-bom-alpha:${openTelemetryAlphaVersion}"), platform("io.opentelemetry:opentelemetry-bom:${openTelemetryVersion}"), "io.opentelemetry:opentelemetry-sdk:${openTelemetryVersion}", + "io.opentelemetry:opentelemetry-api:${openTelemetryVersion}", + "io.opentelemetry:opentelemetry-sdk-metrics:${openTelemetryVersion}", "io.opentelemetry:opentelemetry-exporter-otlp:${openTelemetryVersion}", + "io.opentelemetry:opentelemetry-exporter-prometheus:${openTelemetryAlphaVersion}", "io.opentelemetry.semconv:opentelemetry-semconv:${openTelemetrySemConvVersion}", "io.opentelemetry.proto:opentelemetry-proto:${openTelemetryProtoVersion}", + // also opentelemetry related "com.google.protobuf:protobuf-java:${protobufVersion}", "com.google.protobuf:protobuf-java-util:${protobufVersion}", + // utils "com.google.guava:guava:${guavaVersion}", "com.maxmind.geoip2:geoip2:${geoip2Version}", "commons-net:commons-net:${commonsNetVersion}", "org.apache.commons:commons-lang3:${commonsLang3Version}", "org.apache.commons:commons-math3:${commonsMath3Version}", - "commons-io:commons-io:${commonsIoVersion}", - - "org.influxdb:influxdb-java:${influxdbJavaVersion}", + "commons-io:commons-io:${commonsIoVersion}" ) compileOnly "org.projectlombok:lombok:${lombokVersion}" @@ -167,14 +158,13 @@ dependencies { testImplementation( "org.springframework.boot:spring-boot-starter-test", - "io.opencensus:opencensus-impl:${openCensusVersion}", - "org.apache.httpcomponents:httpclient:${httpClientVersion}", + "org.apache.httpcomponents.client5:httpclient5:${httpClientVersion}", - // ServerExtension + // server-extension "com.linecorp.armeria:armeria-junit5:${armeriaVersion}", "com.linecorp.armeria:armeria-grpc-protocol:${armeriaVersion}", - // for docker test containers + // docker test containers "org.testcontainers:testcontainers:${testContainersVersion}", "org.testcontainers:junit-jupiter:${testContainersVersion}" ) diff --git a/dependencyCheckSuppression.xml b/dependencyCheckSuppression.xml index 07a6ee4..480d617 100644 --- a/dependencyCheckSuppression.xml +++ b/dependencyCheckSuppression.xml @@ -1,11 +1,16 @@ + + + CVE-2019-3826 + - e5bc2949679b6214e8d9a1e5b707f2b42bb3fa13 - CVE-2019-3826 + We do not use Spring Framework 5.0.5.RELEASE, but version 6+ + ]]> + CVE-2018-1258 This class is internal and is hence not for public use. Its APIs are unstable and can change - * at any time. - */ -public final class MetricAdapter { - private MetricAdapter() {} - // All OpenCensus metrics come from this shim. - // VisibleForTesting. - static final InstrumentationScopeInfo INSTRUMENTATION_SCOPE_INFO = - InstrumentationScopeInfo.create("io.opentelemetry.opencensusshim"); - - // Parser for string value of `io.opencensus.contrib.exemplar.util.AttachmentValueSpanContext` - // // SpanContext{traceId=TraceId{traceId=(id))}, spanId=SpanId{spanId=(id), ...} - private static final Pattern OPENCENSUS_TRACE_ATTACHMENT_PATTERN = - Pattern.compile( - "SpanContext\\{traceId=TraceId\\{traceId=([0-9A-Ga-g]+)\\}, spanId=SpanId\\{spanId=([0-9A-Ga-g]+)\\},.*\\}"); - /** - * Converts an open-census metric into the OTLP format. - * - * @param otelResource The resource associated with the opentelemetry SDK. - * @param censusMetric The OpenCensus metric to convert. - */ - public static MetricData convert(Resource otelResource, Metric censusMetric) { - // Note: we can't just adapt interfaces, we need to do full copy because OTel data API uses - // auto-value vs. pure interfaces. - switch (censusMetric.getMetricDescriptor().getType()) { - case GAUGE_INT64: - return ImmutableMetricData.createLongGauge( - otelResource, - INSTRUMENTATION_SCOPE_INFO, - censusMetric.getMetricDescriptor().getName(), - censusMetric.getMetricDescriptor().getDescription(), - censusMetric.getMetricDescriptor().getUnit(), - convertLongGauge(censusMetric)); - case GAUGE_DOUBLE: - return ImmutableMetricData.createDoubleGauge( - otelResource, - INSTRUMENTATION_SCOPE_INFO, - censusMetric.getMetricDescriptor().getName(), - censusMetric.getMetricDescriptor().getDescription(), - censusMetric.getMetricDescriptor().getUnit(), - convertDoubleGauge(censusMetric)); - case CUMULATIVE_INT64: - return ImmutableMetricData.createLongSum( - otelResource, - INSTRUMENTATION_SCOPE_INFO, - censusMetric.getMetricDescriptor().getName(), - censusMetric.getMetricDescriptor().getDescription(), - censusMetric.getMetricDescriptor().getUnit(), - convertLongSum(censusMetric)); - case CUMULATIVE_DOUBLE: - return ImmutableMetricData.createDoubleSum( - otelResource, - INSTRUMENTATION_SCOPE_INFO, - censusMetric.getMetricDescriptor().getName(), - censusMetric.getMetricDescriptor().getDescription(), - censusMetric.getMetricDescriptor().getUnit(), - convertDoubleSum(censusMetric)); - case CUMULATIVE_DISTRIBUTION: - return ImmutableMetricData.createDoubleHistogram( - otelResource, - INSTRUMENTATION_SCOPE_INFO, - censusMetric.getMetricDescriptor().getName(), - censusMetric.getMetricDescriptor().getDescription(), - censusMetric.getMetricDescriptor().getUnit(), - convertHistogram(censusMetric)); - case SUMMARY: - return ImmutableMetricData.createDoubleSummary( - otelResource, - INSTRUMENTATION_SCOPE_INFO, - censusMetric.getMetricDescriptor().getName(), - censusMetric.getMetricDescriptor().getDescription(), - censusMetric.getMetricDescriptor().getUnit(), - convertSummary(censusMetric)); - case GAUGE_DISTRIBUTION: - return ImmutableMetricData.createDoubleHistogram( - otelResource, - INSTRUMENTATION_SCOPE_INFO, - censusMetric.getMetricDescriptor().getName(), - censusMetric.getMetricDescriptor().getDescription(), - censusMetric.getMetricDescriptor().getUnit(), - convertGaugeHistogram(censusMetric)); - } - // Should be unreachable.... - throw new IllegalArgumentException( - "Unknown OpenCensus metric type: " + censusMetric.getMetricDescriptor().getType()); - } - - static GaugeData convertLongGauge(Metric censusMetric) { - return ImmutableGaugeData.create(convertLongPoints(censusMetric)); - } - - static GaugeData convertDoubleGauge(Metric censusMetric) { - return ImmutableGaugeData.create(convertDoublePoints(censusMetric)); - } - - static SumData convertLongSum(Metric censusMetric) { - return ImmutableSumData.create( - true, AggregationTemporality.CUMULATIVE, convertLongPoints(censusMetric)); - } - - static SumData convertDoubleSum(Metric censusMetric) { - return ImmutableSumData.create( - true, AggregationTemporality.CUMULATIVE, convertDoublePoints(censusMetric)); - } - - static HistogramData convertHistogram(Metric censusMetric) { - return ImmutableHistogramData.create( - AggregationTemporality.CUMULATIVE, convertHistogramPoints(censusMetric)); - } - - static HistogramData convertGaugeHistogram(Metric censusMetric) { - return ImmutableHistogramData.create( - AggregationTemporality.DELTA, convertHistogramPoints(censusMetric)); - } - - static SummaryData convertSummary(Metric censusMetric) { - return ImmutableSummaryData.create(convertSummaryPoints(censusMetric)); - } - - static Collection convertLongPoints(Metric censusMetric) { - // TODO - preallocate array to correct size. - List result = new ArrayList<>(); - for (TimeSeries ts : censusMetric.getTimeSeriesList()) { - long startTimestamp = mapTimestamp(ts.getStartTimestamp()); - Attributes attributes = - mapAttributes(censusMetric.getMetricDescriptor().getLabelKeys(), ts.getLabelValues()); - for (Point point : ts.getPoints()) { - result.add( - ImmutableLongPointData.create( - startTimestamp, mapTimestamp(point.getTimestamp()), attributes, longValue(point))); - } - } - return result; - } - - static Collection convertDoublePoints(Metric censusMetric) { - // TODO - preallocate array to correct size. - List result = new ArrayList<>(); - for (TimeSeries ts : censusMetric.getTimeSeriesList()) { - long startTimestamp = mapTimestamp(ts.getStartTimestamp()); - Attributes attributes = - mapAttributes(censusMetric.getMetricDescriptor().getLabelKeys(), ts.getLabelValues()); - for (Point point : ts.getPoints()) { - result.add( - ImmutableDoublePointData.create( - startTimestamp, - mapTimestamp(point.getTimestamp()), - attributes, - doubleValue(point))); - } - } - return result; - } - - static Collection convertHistogramPoints(Metric censusMetric) { - boolean isGauge = - censusMetric.getMetricDescriptor().getType() == MetricDescriptor.Type.GAUGE_DISTRIBUTION; - // TODO - preallocate array to correct size. - List result = new ArrayList<>(); - for (TimeSeries ts : censusMetric.getTimeSeriesList()) { - long startTimestamp = mapTimestamp(ts.getStartTimestamp()); - Attributes attributes = - mapAttributes(censusMetric.getMetricDescriptor().getLabelKeys(), ts.getLabelValues()); - for (Point point : ts.getPoints()) { - long endTimestamp = mapTimestamp(point.getTimestamp()); - HistogramPointData otelPoint = - point - .getValue() - .match( - doubleValue -> null, - longValue -> null, - distribution -> - ImmutableHistogramPointData.create( - // Report Gauge histograms as DELTA with "instantaneous" time window. - isGauge ? endTimestamp : startTimestamp, - endTimestamp, - attributes, - distribution.getSum(), - false, - -1, - false, - -1, - mapBoundaries(distribution.getBucketOptions()), - mapCounts(distribution.getBuckets()), - mapExemplars(distribution.getBuckets())), - summary -> null, - defaultValue -> null); - if (otelPoint != null) { - result.add(otelPoint); - } - } - } - return result; - } - - static Collection convertSummaryPoints(Metric censusMetric) { - List result = new ArrayList<>(); - for (TimeSeries ts : censusMetric.getTimeSeriesList()) { - long startTimestamp = mapTimestamp(ts.getStartTimestamp()); - Attributes attributes = - mapAttributes(censusMetric.getMetricDescriptor().getLabelKeys(), ts.getLabelValues()); - for (Point point : ts.getPoints()) { - SummaryPointData otelPoint = - point - .getValue() - .match( - dv -> null, - lv -> null, - distribution -> null, - summary -> - ImmutableSummaryPointData.create( - startTimestamp, - mapTimestamp(point.getTimestamp()), - attributes, - summary.getCount(), - summary.getSum(), - mapValueAtPercentiles(summary.getSnapshot().getValueAtPercentiles())), - defaultValue -> null); - if (otelPoint != null) { - result.add(otelPoint); - } - } - } - return result; - } - - static Attributes mapAttributes(List labels, List values) { - AttributesBuilder result = Attributes.builder(); - for (int i = 0; i < labels.size(); i++) { - result.put(labels.get(i).getKey(), values.get(i).getValue()); - } - return result.build(); - } - - static long longValue(Point point) { - return point - .getValue() - .match( - Double::longValue, - lv -> lv, - // Ignore these cases (logic error) - distribution -> 0, - summary -> 0, - defaultValue -> 0) - .longValue(); - } - - static double doubleValue(Point point) { - return point - .getValue() - .match( - d -> d, - Long::doubleValue, - // Ignore these cases (logic error) - distribution -> 0, - summary -> 0, - defaultValue -> 0) - .doubleValue(); - } - - static List mapBoundaries(Distribution.BucketOptions censusBuckets) { - return censusBuckets.match( - explicit -> explicit.getBucketBoundaries(), defaultOption -> Collections.emptyList()); - } - - static List mapCounts(List buckets) { - List result = new ArrayList<>(buckets.size()); - for (Distribution.Bucket bucket : buckets) { - result.add(bucket.getCount()); - } - return result; - } - - static List mapExemplars(List buckets) { - List result = new ArrayList<>(); - for (Distribution.Bucket bucket : buckets) { - Exemplar exemplar = bucket.getExemplar(); - if (exemplar != null) { - result.add(mapExemplar(exemplar)); - } - } - return result; - } - - private static DoubleExemplarData mapExemplar(Exemplar exemplar) { - // Look for trace/span id. - SpanContext spanContext = SpanContext.getInvalid(); - if (exemplar.getAttachments().containsKey("SpanContext")) { - // We need to use `io.opencensus.contrib.exemplar.util.AttachmentValueSpanContext` - // The `toString` will be the following: - // SpanContext{traceId=TraceId{traceId=(id))}, spanId=SpanId{spanId=(id), ...} - // We *attempt* parse it rather than pull in yet another dependency. - String spanContextToString = exemplar.getAttachments().get("SpanContext").getValue(); - Matcher m = OPENCENSUS_TRACE_ATTACHMENT_PATTERN.matcher(spanContextToString); - if (m.matches()) { - MatchResult mr = m.toMatchResult(); - String traceId = mr.group(1); - String spanId = mr.group(2); - spanContext = - SpanContext.create(traceId, spanId, TraceFlags.getDefault(), TraceState.getDefault()); - } - } - return ImmutableDoubleExemplarData.create( - Attributes.empty(), - mapTimestamp(exemplar.getTimestamp()), - spanContext, - exemplar.getValue()); - } - - static long mapTimestamp(Timestamp time) { - // Treat all empty timestamps as "0" (proto3) - if (time == null) { - return 0; - } - return TimeUnit.SECONDS.toNanos(time.getSeconds()) + time.getNanos(); - } - - private static List mapValueAtPercentiles( - List valueAtPercentiles) { - List result = new ArrayList<>(valueAtPercentiles.size()); - for (Summary.Snapshot.ValueAtPercentile censusValue : valueAtPercentiles) { - result.add( - ImmutableValueAtQuantile.create( - censusValue.getPercentile() / 100.0, censusValue.getValue())); - } - return result; - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/AppStartupRunner.java b/src/main/java/rocks/inspectit/oce/eum/server/AppStartupRunner.java deleted file mode 100644 index 9a8c467..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/AppStartupRunner.java +++ /dev/null @@ -1,76 +0,0 @@ -package rocks.inspectit.oce.eum.server; - -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; - -@Slf4j -@Component -@Data -public class AppStartupRunner implements ApplicationRunner { - - /** - * Version string of unknown versions. - */ - private static final String UNKNOWN = "UNKNOWN"; - - /** - * The file used to load the server's version. - */ - private static final String SERVER_VERSION_INFORMATION_FILE = "/eum-version.info"; - - /** - * The server's version. - */ - private String serverVersion; - - /** - * The date the server was built. - */ - private String serverBuildDate; - - /** - * The Boomerangjs version shipped with the server. - */ - private String bommerangjsVersion; - - /** - * The OpenTelemetry API version. - */ - private String openTelemetryVersion; - - @Override - public void run(ApplicationArguments args) { - readVersionInformation(); - - log.info("> Version Information"); - log.info("\tVersion: {}", serverVersion); - log.info("\tBuild Date: {}", serverBuildDate); - log.info("\tBoomerangjs Version: {}", bommerangjsVersion); - } - - /** - * Loads the agent's version information from the {@link #SERVER_VERSION_INFORMATION_FILE} file. - */ - private void readVersionInformation() { - try (InputStream inputStream = getClass().getResourceAsStream(SERVER_VERSION_INFORMATION_FILE)) { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - serverVersion = reader.readLine(); - serverBuildDate = reader.readLine(); - bommerangjsVersion = reader.readLine(); - openTelemetryVersion = reader.readLine(); - } catch (Exception e) { - log.warn("Could not read server version information file."); - serverVersion = UNKNOWN; - serverBuildDate = UNKNOWN; - bommerangjsVersion = UNKNOWN; - openTelemetryVersion = UNKNOWN; - } - } -} \ No newline at end of file diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/BooleanToExporterEnabledStateConverter.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/BooleanToExporterEnabledStateConverter.java deleted file mode 100644 index 78d17bc..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/BooleanToExporterEnabledStateConverter.java +++ /dev/null @@ -1,29 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.conversion; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.convert.converter.Converter; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; - -/** - * Before version 1.15, the enabled property of exporters was a Boolean, now it is a {@link ExporterEnabledState}. - * To support not-yet-updated configurations, this converter converts the old Boolean values to their equivalent new values, - * i.e. 'true' to 'IF_CONFIGURED' and 'false' to 'DISABLED'. - * The class is deprecated, since this conversion functionality will be removed and configurations using the old style - * made invalid in a future release of InspectIT Ocelot. - */ -@Slf4j -@Deprecated -public class BooleanToExporterEnabledStateConverter implements Converter { - - @Override - public ExporterEnabledState convert(Boolean source) { - // log deprecation warn - log.warn("You are using the deprecated Boolean-based style to define whether an exporter is enabled. This style will be invalid in future releases of InspectIT Ocelot."); - - if (source) { - return ExporterEnabledState.IF_CONFIGURED; - } else { - return ExporterEnabledState.DISABLED; - } - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/StringToExporterEnabledStateConverter.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/StringToExporterEnabledStateConverter.java deleted file mode 100644 index cc630c2..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/StringToExporterEnabledStateConverter.java +++ /dev/null @@ -1,32 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.conversion; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.convert.converter.Converter; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; - -/** - * Before version 1.15, the enabled property of exporters was a Boolean, now it is a {@link ExporterEnabledState}. - * To support not-yet-updated configurations, this {@link BooleanToExporterEnabledStateConverter} converts Boolean values - * to their equivalent new values, i.e. 'true' to 'IF_CONFIGURED' and 'false' to 'DISABLED'. - * However, in Spring Booleans can be defined using Strings, i.e. quoted Boolean values like '"false"', as well, so this - * converter is additionally needed to cover that case. However, because the correct new values are also Strings at first, - * that are then converted to ExporterEnabledStates, this class also needs to handle that correct usage. - * The class is deprecated, since the Boolean conversion functionality will be removed and configurations using the old style - * made invalid in a future release of InspectIT Ocelot, and the conversion for correct values will without this - * Converter work out-of-the-box in Spring. - */ -@Slf4j -@Deprecated -public class StringToExporterEnabledStateConverter implements Converter { - - @Override - public ExporterEnabledState convert(String source) { - if (source.equalsIgnoreCase("true") || source.equalsIgnoreCase("false")) { - // log deprecation warn - log.warn("You are using the deprecated Boolean-based style with quotes to define whether an exporter is enabled. This style will be invalid in future releases of InspectIT Ocelot."); - return source.equalsIgnoreCase("true") ? ExporterEnabledState.IF_CONFIGURED : ExporterEnabledState.DISABLED; - } else { - return ExporterEnabledState.valueOf(source); - } - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/ResourceTimingSettings.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/ResourceTimingSettings.java deleted file mode 100644 index 0e88520..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/ResourceTimingSettings.java +++ /dev/null @@ -1,27 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.model; - -import lombok.Data; -import org.springframework.validation.annotation.Validated; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.util.Map; - -/** - * Resource timing settings. - */ -@Data -@Validated -public class ResourceTimingSettings { - - /** - * If resource timing is enabled or not. - */ - private boolean enabled; - - /** - * Specifies which tags should be used for this view. - */ - private Map<@NotBlank String, @NotNull Boolean> tags; - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/InfluxExporterSettings.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/InfluxExporterSettings.java deleted file mode 100644 index 5a89926..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/InfluxExporterSettings.java +++ /dev/null @@ -1,81 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.metrics; - -import lombok.Data; -import org.hibernate.validator.constraints.time.DurationMin; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; - -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotBlank; -import java.time.Duration; - -/** - * Settings for the InfluxDB metrics exporter. - */ -@Data -public class InfluxExporterSettings { - - /** - * Whether the exporter should be started. - */ - private ExporterEnabledState enabled; - - /** - * This property is deprecated since v2.0. Please use {@link #endpoint} instead. - * The HTTP URL of influx (e.g. http://localhost:8086). - */ - @Deprecated - private String url; - - /** - * The HTTP URL endpoint of Influx (e.g., http://localhost:8086) - */ - private String endpoint; - - /** - * The database to which the values are pushed. - */ - @NotBlank - private String database; - - /** - * The retention policy to use. - */ - @NotBlank - private String retentionPolicy; - - /** - * The username to use for connecting to the influxDB, can be null. - */ - private String user; - - /** - * The password to use for connecting to the influxDB, can be null. - */ - private String password; - - /** - * Defines how often metrics are pushed to influx. - */ - @DurationMin(millis = 1) - private Duration exportInterval; - - /** - * If enabled, the Influx Exporter creates the specified database upon initial connection. - */ - private boolean createDatabase; - - /** - * If disabled, the raw values of each counter will be written to the InfluxDB on each export. - * When enabled, only the change of the counter in comparison to the previous export will be written. - * This difference will only be exposed if the counter has changed (=the difference is non-zero). - * This can greatly reduce the total data written to influx and makes writing queries easier. - */ - private boolean countersAsDifferences; - - /** - * The size of the buffer for failed batches. - * E.g. if the exportInterval is 15s and the buffer-size is 4, the export will keep up to one minute of data in memory. - */ - @Min(1) - private int bufferSize; -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/MetricDefinitionSettings.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/MetricDefinitionSettings.java deleted file mode 100644 index 631742a..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/MetricDefinitionSettings.java +++ /dev/null @@ -1,88 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.model.metric.definition; - -import lombok.*; -import org.springframework.util.CollectionUtils; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.time.Duration; -import java.util.Map; - -/** - * Defines an OpenCensus measure in combination with one or multiple views - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder(toBuilder = true) -public class MetricDefinitionSettings { - - public enum MeasureType { - LONG, DOUBLE - } - - /** - * Defines if this metric is enabled. - * If this metric is disabled: - * - no views for it are created - * - no measurements for it are collected in the instrumentation. However the actions are still executed! - */ - @Builder.Default - private boolean enabled = true; - - @NotBlank - private String unit; - - @NotNull - @Builder.Default - private MetricDefinitionSettings.MeasureType type = MeasureType.DOUBLE; - - /** - * The description of the measure. - * If this is null, the description is simply the name of the measure. - */ - private String description; - - /** - * Maps view names to their definitions for the measure defined by this {@link MetricDefinitionSettings}. - * If this is null, a default view is created which simply exposes the last value of the metric. - */ - @Singular - private Map<@NotBlank String, @Valid @NotNull ViewDefinitionSettings> views; - - /** - * Copies the settings of this object but applies the defaults, like creating a default view if no views were defined. - * Does not provide a default time window for windowed views. - * - * @param metricName the name of the measure, derived form the key in {@link MetricsSettings#getDefinitions()} - * - * @return a copy of this view definition with the default populated - */ - public MetricDefinitionSettings getCopyWithDefaultsPopulated(String metricName) { - return getCopyWithDefaultsPopulated(metricName, null); - } - - /** - * Copies the settings of this object but applies the defaults, like creating a default view if no views were defined. - * - * @param metricName the name of the measure, derived form the key in {@link MetricsSettings#getDefinitions()} - * @param defaultTimeWindow the size of the time window to use as default for windowed metrics (e.g. quantiles) - * - * @return a copy of this view definition with the default populated - */ - public MetricDefinitionSettings getCopyWithDefaultsPopulated(String metricName, Duration defaultTimeWindow) { - val resultDescription = description == null ? metricName : description; - val result = toBuilder().description(resultDescription).clearViews(); - if (!CollectionUtils.isEmpty(views)) { - views.forEach((name, def) -> result.view(name, def.getCopyWithDefaultsPopulated(resultDescription, unit, defaultTimeWindow))); - } else { - result.view(metricName, ViewDefinitionSettings.builder() - .aggregation(ViewDefinitionSettings.Aggregation.LAST_VALUE) - .build() - .getCopyWithDefaultsPopulated(resultDescription, unit, defaultTimeWindow)); - } - return result.build(); - } - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/ViewDefinitionSettings.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/ViewDefinitionSettings.java deleted file mode 100644 index 5bd9bb5..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/ViewDefinitionSettings.java +++ /dev/null @@ -1,151 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.model.metric.definition; - -import lombok.*; -import org.hibernate.validator.constraints.time.DurationMin; -import org.springframework.util.CollectionUtils; - -import jakarta.validation.constraints.*; -import java.time.Duration; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -/** - * Defines a single OpenCensus view for a measure. - * The name of the view is defined through the key in the map {@link MetricDefinitionSettings#getViews()}. - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Builder(toBuilder = true) -public class ViewDefinitionSettings { - - @AllArgsConstructor - public enum Aggregation { - LAST_VALUE("last value"), SUM("sum"), COUNT("count"), QUANTILES("quantiles"), SMOOTHED_AVERAGE("smoothed average"), - /** - * Corresponds to OpenCensus "Distribution" aggregation - */ - HISTOGRAM("histogram"); - - @Getter - private String readableName; - } - - @Builder.Default - private boolean enabled = true; - - /** - * Description of the view. - * If this is null a description is generated based on the name of the measure, the unit and the aggregation. - */ - private String description; - - /** - * The aggregation to use for this view - */ - @NotNull - @Builder.Default - private Aggregation aggregation = Aggregation.LAST_VALUE; - - /** - * Only relevant if aggregation is "HISTOGRAM". - * In this case this list defines the boundaries of the buckets in the histogram - */ - @Singular - private List<@NotNull Double> bucketBoundaries; - - /** - * In case the view is a quantile view, this list defines which quantiles shall be captured. - * 0 corresponds to the minimum, 1 to the maximum. - */ - @Builder.Default - private List<@NotNull Double> quantiles = Arrays.asList(0.0, 0.5, 0.9, 0.95, 0.99, 1.0); - - /** - * In case the view is a smoothed_average, this value (in percentage in the range (0,1)) defines, how many metrics in the upper range shall be dropped. - */ - @DecimalMax("1.0") - @DecimalMin("0.0") - @Builder.Default - private Double dropUpper = 0.0; - - /** - * In case the view is a smoothed_average, this value (in percentage in the range (0,1)) defines, how many metrics in the lower range shall be dropped. - */ - @DecimalMax("1.0") - @DecimalMin("0.0") - @Builder.Default - private Double dropLower = 0.0; - - /** - * The time window to use for windowed metrics (currently only quantiles). - * Can be null, in this case the default provided via {@link #getCopyWithDefaultsPopulated(String, String, Duration)}. - * is used. - */ - @DurationMin(millis = 1L) - private Duration timeWindow; - - /** - * The maximum number of points to be buffered by this View. - * Currently only relevant if the aggregation is QUANTILES. - *

- * If this number is exceeded, a warning will be printed and points will be rejected until space is free again. - */ - @Min(1) - @Builder.Default - private int maxBufferedPoints = 16384; - - /** - * Defines if this view should by default include all common tags. - * Individual tags can still be disabled via {@link #tags}. - */ - @Builder.Default - private boolean withCommonTags = true; - - /** - * Specifies which tags should be used for this view. - */ - @Singular - private Map<@NotBlank String, @NotNull Boolean> tags; - - public ViewDefinitionSettings getCopyWithDefaultsPopulated(String measureDescription, String unit, Duration defaultTimeWindow) { - val result = toBuilder(); - if (description == null) { - result.description(aggregation.getReadableName() + " of " + measureDescription + " [" + unit + "]"); - } - if (timeWindow == null) { - if (defaultTimeWindow == null && aggregation == Aggregation.QUANTILES) { - throw new IllegalArgumentException("A default time window must be provided for quantile views"); - } - result.timeWindow(defaultTimeWindow); - } - return result.build(); - } - - @AssertFalse(message = "When using QUANTILES aggregation you must specify the quantiles to use!") boolean isQuantilesNotSpecifiedForercentileType() { - return enabled && aggregation == Aggregation.QUANTILES && CollectionUtils.isEmpty(quantiles); - } - - @AssertFalse(message = "When using HISTOGRAM aggregation you must specify the bucket-boundaries!") boolean isBucketBoundariesNotSpecifiedForHistogram() { - return enabled && aggregation == Aggregation.HISTOGRAM && CollectionUtils.isEmpty(bucketBoundaries); - } - - @AssertTrue(message = "When using HISTOGRAM the specified bucket-boundaries must be sorted in ascending order and must contain each value at most once!") boolean isBucketBoundariesSorted() { - if (enabled && aggregation == Aggregation.HISTOGRAM && !CollectionUtils.isEmpty(bucketBoundaries)) { - Double previous = null; - for (double boundary : bucketBoundaries) { - if (previous != null && previous >= boundary) { - return false; - } - previous = boundary; - } - } - return true; - } - - @AssertTrue(message = "The quantiles must be in the range [0,1]") boolean isQuantilesInRange() { - return !enabled || aggregation != Aggregation.QUANTILES || quantiles.stream().noneMatch(q -> q < 0 || q > 1); - } - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/selfmonitoring/SelfMonitoringSettings.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/selfmonitoring/SelfMonitoringSettings.java deleted file mode 100644 index 2b915e6..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/selfmonitoring/SelfMonitoringSettings.java +++ /dev/null @@ -1,36 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.model.selfmonitoring; - -import lombok.Data; -import lombok.Singular; -import org.springframework.validation.annotation.Validated; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.MetricDefinitionSettings; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.util.Collections; -import java.util.Map; - -/** - * Self-monitoring settings. - */ -@Data -@Validated -public class SelfMonitoringSettings { - - /** - * If self-monitoring is enabled. - */ - private boolean enabled; - - /** - * Definition of the self-monitoring metrics. - */ - @Singular - private Map<@NotBlank String, @Valid @NotNull MetricDefinitionSettings> metrics = Collections.emptyMap(); - - /** - * The prefix used for the self-monitoring metrics. - */ - private String metricPrefix; -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/providers/EnvironmentTagsProviderSettings.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/providers/EnvironmentTagsProviderSettings.java deleted file mode 100644 index 0539884..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/providers/EnvironmentTagsProviderSettings.java +++ /dev/null @@ -1,25 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.model.tags.providers; - -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -public class EnvironmentTagsProviderSettings { - - /** - * If providers is enabled. - */ - private boolean enabled; - - /** - * If true tries to resolve the host name using {@link java.net.InetAddress}. - */ - private boolean resolveHostName; - - /** - * If true tries to resolve the host address using {@link java.net.InetAddress}. - */ - private boolean resolveHostAddress; - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/providers/TagsProvidersSettings.java b/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/providers/TagsProvidersSettings.java deleted file mode 100644 index 3b55546..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/providers/TagsProvidersSettings.java +++ /dev/null @@ -1,18 +0,0 @@ -package rocks.inspectit.oce.eum.server.configuration.model.tags.providers; - -import lombok.Data; -import lombok.NoArgsConstructor; - -import jakarta.validation.Valid; - -@Data -@NoArgsConstructor -public class TagsProvidersSettings { - - /** - * The environment tags providers. - */ - @Valid - private EnvironmentTagsProviderSettings environment; - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/events/RegisteredTagsEvent.java b/src/main/java/rocks/inspectit/oce/eum/server/events/RegisteredTagsEvent.java deleted file mode 100644 index d0ecfec..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/events/RegisteredTagsEvent.java +++ /dev/null @@ -1,27 +0,0 @@ -package rocks.inspectit.oce.eum.server.events; - -import lombok.Getter; -import org.springframework.context.ApplicationEvent; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -/** - * Event that is sent when new tags are registered. This usually happens when new metric views are registered. - */ -public class RegisteredTagsEvent extends ApplicationEvent { - - @Getter - private final Set registeredTags; - - /** - * Create a new ApplicationEvent. - * - * @param source the object on which the event initially occurred (never {@code null}) - */ - public RegisteredTagsEvent(Object source, Set registeredTags) { - super(source); - this.registeredTags = Collections.unmodifiableSet(new HashSet<>(registeredTags)); - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/exporters/InfluxExporterService.java b/src/main/java/rocks/inspectit/oce/eum/server/exporters/InfluxExporterService.java deleted file mode 100644 index 82187d9..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/exporters/InfluxExporterService.java +++ /dev/null @@ -1,94 +0,0 @@ -package rocks.inspectit.oce.eum.server.exporters; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.metrics.InfluxExporterSettings; -import rocks.inspectit.oce.eum.server.metrics.percentiles.TimeWindowViewManager; -import rocks.inspectit.opencensus.influx.InfluxExporter; - -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * COPIED FROM THE OCELOT CORE PROJECT! - * Uses the {@link InfluxExporter} to directly push metrics into a given InfluxDB version 1.x . - */ -@Slf4j -@Component -public class InfluxExporterService { - - @Autowired - private ScheduledExecutorService executor; - - @Autowired - private TimeWindowViewManager timeWindowViewManager; - - @Autowired - private EumServerConfiguration configuration; - - /** - * The currently active influx exporter, null if none is active. - */ - private InfluxExporter activeExporter; - - /** - * A task regularly invoking activeExporter.export() at the configured interval. - */ - private Future exporterTask; - - private boolean shouldEnable() { - InfluxExporterSettings influx = configuration.getExporters().getMetrics().getInflux(); - if (!influx.getEnabled().isDisabled()) { - if (StringUtils.hasText(influx.getEndpoint())) { - return true; - } else if (StringUtils.hasText(influx.getUrl())) { - log.warn("You are using the deprecated property 'url'. This property will be invalid in future releases of InspectIT Ocelot, please use `endpoint` instead."); - return true; - } else if (influx.getEnabled().equals(ExporterEnabledState.ENABLED)) { - log.warn("InfluxDB Exporter is enabled but 'endpoint' is not set."); - } - } - return false; - } - - @PostConstruct - private void doEnable() { - InfluxExporterSettings influx = configuration.getExporters().getMetrics().getInflux(); - if (shouldEnable()) { - String endpoint = StringUtils.hasText(influx.getEndpoint()) ? influx.getEndpoint() : influx.getUrl(); - log.info("Starting InfluxDB Exporter to '{}:{}' on '{}'", influx.getDatabase(), influx.getRetentionPolicy(), endpoint); - activeExporter = InfluxExporter.builder() - .url(endpoint) - .database(influx.getDatabase()) - .retention(influx.getRetentionPolicy()) - .user(influx.getUser()) - .password(influx.getPassword()) - .createDatabase(influx.isCreateDatabase()) - .exportDifference(influx.isCountersAsDifferences()) - .measurementNameProvider(timeWindowViewManager::getMeasureNameForSeries) - .bufferSize(influx.getBufferSize()) - .build(); - exporterTask = executor.scheduleAtFixedRate(activeExporter::export, 0, influx.getExportInterval() - .toMillis(), TimeUnit.MILLISECONDS); - } - } - - @PreDestroy - private void doDisable() { - if (exporterTask != null) { - log.info("Stopping InfluxDB Exporter"); - exporterTask.cancel(false); - } - if (activeExporter != null) { - activeExporter.close(); - activeExporter = null; - } - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/exporters/OtlpMetricsExporterService.java b/src/main/java/rocks/inspectit/oce/eum/server/exporters/OtlpMetricsExporterService.java deleted file mode 100644 index 4ca2147..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/exporters/OtlpMetricsExporterService.java +++ /dev/null @@ -1,197 +0,0 @@ -package rocks.inspectit.oce.eum.server.exporters; - -import io.opencensus.metrics.Metrics; -import io.opencensus.metrics.export.Metric; -import io.opencensus.metrics.export.MetricProducer; -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; -import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; -import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; -import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; -import io.opentelemetry.opencensusshim.internal.metrics.MetricAdapter; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.metrics.SdkMeterProvider; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; -import io.opentelemetry.sdk.metrics.export.MetricExporter; -import io.opentelemetry.sdk.metrics.export.PeriodicMetricReaderBuilder; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.semconv.ServiceAttributes; -import io.opentelemetry.semconv.TelemetryAttributes; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import rocks.inspectit.oce.eum.server.AppStartupRunner; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.metrics.OtlpMetricsExporterSettings; - -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; -import jakarta.validation.Valid; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -@Component -@Slf4j -public class OtlpMetricsExporterService { - - private final List SUPPORTED_PROTOCOLS = Arrays.asList(TransportProtocol.GRPC, TransportProtocol.HTTP_PROTOBUF); - - @Autowired - private EumServerConfiguration configuration; - - @Autowired - private ScheduledExecutorService executor; - - @Autowired - private AppStartupRunner appStartupRunner; - - private Supplier> metricProducerSupplier; - - MetricExporter metricExporter; - - PeriodicMetricReaderBuilder metricReaderBuilder; - - OpenTelemetrySdk openTelemetry; - - SdkMeterProvider meterProvider; - - private ScheduledFuture exporterTask; - - private Resource otelResource; - - OtlpMetricsExporterSettings otlpMetricsExporterSettings; - - @Getter - private boolean enabled; - - private boolean shouldEnable() { - @Valid OtlpMetricsExporterSettings otlp = configuration.getExporters().getMetrics().getOtlp(); - if (!otlp.getEnabled().isDisabled()) { - if (SUPPORTED_PROTOCOLS.contains(otlp.getProtocol())) { - if (StringUtils.hasText(otlp.getEndpoint())) { - return true; - } else if (StringUtils.hasText(otlp.getEndpoint())) { - log.warn("OTLP Metric Exporter is enabled but 'endpoint' is not set."); - return true; - } - } - if (otlp.getEnabled().equals(ExporterEnabledState.ENABLED)) { - if (!SUPPORTED_PROTOCOLS.contains(otlp.getProtocol())) { - log.warn("OTLP Metric Exporter is enabled, but wrong 'protocol' is specified. Supported values are {}", Arrays.toString(SUPPORTED_PROTOCOLS.stream() - .map(TransportProtocol::getConfigRepresentation) - .toArray())); - } - if (!StringUtils.hasText(otlp.getEndpoint())) { - log.warn("OTLP Metric Exporter is enabled but 'endpoint' is not set."); - } - } - } - - return false; - } - - @PostConstruct - void doEnable() { - - if (shouldEnable()) { - enabled = true; - otlpMetricsExporterSettings = configuration.getExporters().getMetrics().getOtlp(); - AggregationTemporalitySelector aggregationTemporalitySelector = otlpMetricsExporterSettings.getPreferredTemporality() == AggregationTemporality.DELTA ? AggregationTemporalitySelector.deltaPreferred() : AggregationTemporalitySelector.alwaysCumulative(); - otelResource = Resource.create(Attributes.of(ServiceAttributes.SERVICE_NAME, configuration.getExporters() - .getMetrics() - .getServiceName(), AttributeKey.stringKey("inspectit.eum-server.version"), appStartupRunner.getServerVersion(), TelemetryAttributes.TELEMETRY_SDK_VERSION, appStartupRunner.getOpenTelemetryVersion(), TelemetryAttributes.TELEMETRY_SDK_LANGUAGE, "java", TelemetryAttributes.TELEMETRY_SDK_NAME, "opentelemetry")); - String endpoint = configuration.getExporters().getMetrics().getOtlp().getEndpoint(); - // OTEL expects that the URI starts with 'http://' or 'https://' - if (!endpoint.startsWith("http")) { - endpoint = String.format("http://%s", endpoint); - } - try { - metricProducerSupplier = () -> Metrics.getExportComponent() - .getMetricProducerManager() - .getAllMetricProducer(); - - switch (otlpMetricsExporterSettings.getProtocol()) { - case GRPC: { - OtlpGrpcMetricExporterBuilder metricExporterBuilder = OtlpGrpcMetricExporter.builder() - .setAggregationTemporalitySelector(aggregationTemporalitySelector) - .setEndpoint(endpoint) - .setCompression(otlpMetricsExporterSettings.getCompression().toString()) - .setTimeout(otlpMetricsExporterSettings.getTimeout()); - if (otlpMetricsExporterSettings.getHeaders() != null) { - for (Map.Entry headerEntry : otlpMetricsExporterSettings.getHeaders() - .entrySet()) { - metricExporterBuilder.addHeader(headerEntry.getKey(), headerEntry.getValue()); - } - } - metricExporter = metricExporterBuilder.build(); - break; - } - case HTTP_PROTOBUF: { - OtlpHttpMetricExporterBuilder metricExporterBuilder = OtlpHttpMetricExporter.builder() - .setAggregationTemporalitySelector(aggregationTemporalitySelector) - .setEndpoint(endpoint) - .setCompression(otlpMetricsExporterSettings.getCompression().toString()) - .setTimeout(otlpMetricsExporterSettings.getTimeout()); - if (otlpMetricsExporterSettings.getHeaders() != null) { - for (Map.Entry headerEntry : otlpMetricsExporterSettings.getHeaders() - .entrySet()) { - metricExporterBuilder.addHeader(headerEntry.getKey(), headerEntry.getValue()); - } - } - metricExporter = metricExporterBuilder.build(); - break; - } - } - - exporterTask = executor.scheduleAtFixedRate(this::export, otlpMetricsExporterSettings.getExportInterval() - .toMillis(), otlpMetricsExporterSettings.getExportInterval().toMillis(), TimeUnit.MILLISECONDS); - - log.info("Starting OTLP metric exporter with {} on '{}'", otlpMetricsExporterSettings.getProtocol(), otlpMetricsExporterSettings.getEndpoint()); - } catch (Exception e) { - log.error("Error starting OTLP metric exporter", e); - throw e; - } - - } - } - - @PreDestroy - private void doDisable() { - enabled = false; - if (exporterTask != null) { - log.info("Stopping OTLP metric exporter"); - exporterTask.cancel(false); - } - if (metricExporter != null) { - metricExporter.flush(); - metricExporter.close(); - } - } - - private void export() { - List metrics = metricProducerSupplier.get() - .stream() - .flatMap(metricProducer -> metricProducer.getMetrics().stream()) - .collect(Collectors.toList()); - - List convertedMetrics = metrics.stream() - .map(metric -> MetricAdapter.convert(otelResource, metric)) - .collect(Collectors.toList()); - - metricExporter.export(convertedMetrics); - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/exporters/PrometheusExporterService.java b/src/main/java/rocks/inspectit/oce/eum/server/exporters/PrometheusExporterService.java deleted file mode 100644 index 09aa3c6..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/exporters/PrometheusExporterService.java +++ /dev/null @@ -1,57 +0,0 @@ -package rocks.inspectit.oce.eum.server.exporters; - -import io.opencensus.exporter.stats.prometheus.PrometheusStatsCollector; -import io.opencensus.exporter.stats.prometheus.PrometheusStatsConfiguration; -import io.prometheus.client.CollectorRegistry; -import io.prometheus.client.exporter.HTTPServer; -import lombok.extern.slf4j.Slf4j; -import lombok.val; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; - -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; - -/** - * Service for the Prometheus OpenCensus exporters. - * Is enabled, if exporters.metrics.prometheus.enabled is set to {@link rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState#ENABLED ENABLED} or {@link rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState IF_CONFIGURED}. - */ -@Component -@Slf4j -public class PrometheusExporterService { - - private HTTPServer prometheusClient = null; - - @Autowired - private EumServerConfiguration configuration; - - @PostConstruct - private void doEnable() { - val config = configuration.getExporters().getMetrics().getPrometheus(); - if (!config.getEnabled().isDisabled()) { - try { - String host = config.getHost(); - int port = config.getPort(); - log.info("Starting Prometheus Exporter on {}:{}", host, port); - PrometheusStatsCollector.createAndRegister(PrometheusStatsConfiguration.builder() - .setRegistry(CollectorRegistry.defaultRegistry) - .build()); - prometheusClient = new HTTPServer(host, port, true); - } catch (Exception e) { - log.error("Error Starting Prometheus HTTP Endpoint!", e); - CollectorRegistry.defaultRegistry.clear(); - } - } - } - - @PreDestroy - protected boolean doDisable() { - if (prometheusClient != null) { - log.info("Stopping Prometheus Exporter"); - prometheusClient.stop(); - CollectorRegistry.defaultRegistry.clear(); - } - return true; - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/BeaconMetricManager.java b/src/main/java/rocks/inspectit/oce/eum/server/metrics/BeaconMetricManager.java deleted file mode 100644 index 0bb57f6..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/BeaconMetricManager.java +++ /dev/null @@ -1,138 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics; - -import com.google.common.annotations.VisibleForTesting; -import io.opencensus.common.Scope; -import io.opencensus.tags.TagContextBuilder; -import io.opencensus.tags.TagKey; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; -import rocks.inspectit.oce.eum.server.arithmetic.RawExpression; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.beacon.recorder.BeaconRecorder; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.BeaconRequirement; -import rocks.inspectit.oce.eum.server.configuration.model.tags.BeaconTagSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.BeaconMetricDefinitionSettings; -import rocks.inspectit.oce.eum.server.events.RegisteredTagsEvent; -import rocks.inspectit.oce.eum.server.utils.TagUtils; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * Central component, which is responsible for writing beacon entries as OpenCensus views. - */ -@Component -@Slf4j -public class BeaconMetricManager { - - @Autowired - protected EumServerConfiguration configuration; - - @Autowired - private MeasuresAndViewsManager measuresAndViewsManager; - - @Autowired(required = false) - private List beaconRecorders; - - /** - * Set of all registered beacon tags - */ - @VisibleForTesting - Set registeredBeaconTags = Collections.emptySet(); - - /** - * Maps metric definitions to expressions. - */ - private final Map expressionCache = new HashMap<>(); - - @EventListener - public void processUsedTags(RegisteredTagsEvent registeredTagsEvent) { - Map beaconTagSettings = configuration.getTags().getBeacon(); - - registeredBeaconTags = registeredTagsEvent.getRegisteredTags() - .stream() - .filter(beaconTagSettings::containsKey) - .collect(Collectors.toSet()); - } - - /** - * Processes boomerang beacon - * - * @param beacon The beacon containing arbitrary key-value pairs. - * - * @return whether the beacon has been successfully parsed - */ - public boolean processBeacon(Beacon beacon) { - boolean successful = false; - - Map definitions = configuration.getDefinitions(); - if (CollectionUtils.isEmpty(definitions)) { - successful = true; - } else { - for (Map.Entry metricDefinitionEntry : definitions.entrySet()) { - String metricName = metricDefinitionEntry.getKey(); - BeaconMetricDefinitionSettings metricDefinition = metricDefinitionEntry.getValue(); - - if (BeaconRequirement.validate(beacon, metricDefinition.getBeaconRequirements())) { - recordMetric(metricName, metricDefinition, beacon); - successful = true; - } else { - log.debug("Skipping beacon because requirements are not fulfilled."); - } - } - } - - // allow each beacon recorder to record stuff - if (!CollectionUtils.isEmpty(beaconRecorders)) { - try (Scope scope = getTagContextForBeacon(beacon).buildScoped()) { - beaconRecorders.forEach(beaconRecorder -> beaconRecorder.record(beacon)); - } - } - - return successful; - } - - /** - * Extracts the metric value from the given beacon according to the specified metric definition. - * In case the metric definition's value expression is not solvable using the given beacon (not all required - * fields are existing) nothing is done. - * - * @param metricName the metric name - * @param metricDefinition the metric's definition - * @param beacon the current beacon - */ - private void recordMetric(String metricName, BeaconMetricDefinitionSettings metricDefinition, Beacon beacon) { - RawExpression expression = expressionCache.computeIfAbsent(metricDefinition, definition -> new RawExpression(definition - .getValueExpression())); - - if (expression.isSolvable(beacon)) { - Number value = expression.solve(beacon); - - if (value != null) { - measuresAndViewsManager.updateMetrics(metricName, metricDefinition); - try (Scope scope = getTagContextForBeacon(beacon).buildScoped()) { - measuresAndViewsManager.recordMeasure(metricName, metricDefinition, value); - } - } - } - } - - /** - * Builds TagContext for a given beacon. - * - * @param beacon Used to resolve tag values, which refer to a beacon entry - */ - private TagContextBuilder getTagContextForBeacon(Beacon beacon) { - TagContextBuilder tagContextBuilder = measuresAndViewsManager.getTagContext(); - for (String key : registeredBeaconTags) { - if (beacon.contains(key)) { - tagContextBuilder.putLocal(TagKey.create(key), TagUtils.createTagValue(key, beacon.get(key))); - } - } - return tagContextBuilder; - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/MeasuresAndViewsManager.java b/src/main/java/rocks/inspectit/oce/eum/server/metrics/MeasuresAndViewsManager.java deleted file mode 100644 index 05d8f59..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/MeasuresAndViewsManager.java +++ /dev/null @@ -1,252 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics; - -import com.google.common.annotations.VisibleForTesting; -import io.opencensus.stats.*; -import io.opencensus.tags.TagContextBuilder; -import io.opencensus.tags.TagKey; -import io.opencensus.tags.Tags; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.MetricDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.ViewDefinitionSettings; -import rocks.inspectit.oce.eum.server.events.RegisteredTagsEvent; -import rocks.inspectit.oce.eum.server.metrics.percentiles.TimeWindowViewManager; -import rocks.inspectit.oce.eum.server.utils.TagUtils; - -import java.time.Duration; -import java.util.*; -import java.util.stream.Collectors; - -/** - * Central component, which is responsible for writing communication with the OpenCensus. - */ -@Component -@Slf4j -public class MeasuresAndViewsManager { - - /** - * Measures, which are created. - */ - private HashMap metrics = new HashMap<>(); - - @Autowired - private EumServerConfiguration configuration; - - @Autowired - private StatsRecorder recorder; - - @Autowired - private ViewManager viewManager; - - @Autowired - private TimeWindowViewManager timeWindowViewManager; - - @Autowired - private ApplicationEventPublisher applicationEventPublisher; - - /** - * Set of all registered tags - */ - private Set registeredTags = new HashSet<>(); - - /** - * Set of all registered extra tags - */ - @VisibleForTesting - Set registeredExtraTags = Collections.emptySet(); - - /** - * Records the measure. - * - * @param measureName the name of the measure - * @param metricDefinition The configuration of the metric, which is activated - * @param value The value, which is going to be written. - */ - public void recordMeasure(String measureName, MetricDefinitionSettings metricDefinition, Number value) { - if (log.isDebugEnabled()) { - log.debug("Recording measure '{}' with value '{}'.", measureName, value); - } - - switch (metricDefinition.getType()) { - case LONG: - recorder.newMeasureMap() - .put((Measure.MeasureLong) metrics.get(measureName), value.longValue()) - .record(); - break; - case DOUBLE: - recorder.newMeasureMap() - .put((Measure.MeasureDouble) metrics.get(measureName), value.doubleValue()) - .record(); - break; - } - - timeWindowViewManager.recordMeasurement(measureName, value.doubleValue(), Tags.getTagger() - .getCurrentTagContext()); - } - - /** - * Updates the metrics - */ - public void updateMetrics(String name, MetricDefinitionSettings metricDefinition) { - if (!metrics.containsKey(name)) { - MetricDefinitionSettings populatedMetricDefinition = metricDefinition.getCopyWithDefaultsPopulated(name, Duration - .ofSeconds(15)); // Default value of 15s will be overridden by configuration. - Measure measure = createMeasure(name, populatedMetricDefinition); - metrics.put(name, measure); - updateViews(name, populatedMetricDefinition); - } - } - - private Measure createMeasure(String name, MetricDefinitionSettings metricDefinition) { - switch (metricDefinition.getType()) { - case LONG: - return Measure.MeasureLong.create(name, metricDefinition.getDescription(), metricDefinition.getUnit()); - case DOUBLE: - return Measure.MeasureDouble.create(name, metricDefinition.getDescription(), metricDefinition.getUnit()); - default: - throw new RuntimeException("Used measurement type is not supported"); - } - } - - /** - * Creates a new {@link View}, if a view for the given metricDefinition was not created, yet. - * - * @param metricDefinition the settings of the metric definition - */ - private void updateViews(String metricName, MetricDefinitionSettings metricDefinition) { - for (Map.Entry viewDefinitionSettingsEntry : metricDefinition.getViews() - .entrySet()) { - String viewName = viewDefinitionSettingsEntry.getKey(); - ViewDefinitionSettings viewDefinitionSettings = viewDefinitionSettingsEntry.getValue(); - if (viewManager.getAllExportedViews().stream().noneMatch(v -> v.getName().asString().equals(viewName))) { - Measure measure = metrics.get(metricName); - - boolean isRegistered = timeWindowViewManager.isViewRegistered(metricName, viewName); - boolean isQuantileAggregation = viewDefinitionSettings.getAggregation() == ViewDefinitionSettings.Aggregation.QUANTILES; - boolean isSmoothedAverageAggregation = viewDefinitionSettings.getAggregation() == ViewDefinitionSettings.Aggregation.SMOOTHED_AVERAGE; - if (isRegistered || isQuantileAggregation || isSmoothedAverageAggregation) { - addTimeWindowView(measure, viewName, viewDefinitionSettings); - } else { - registerNewView(measure, viewName, viewDefinitionSettings); - } - } - } - } - - private void addTimeWindowView(Measure measure, String viewName, ViewDefinitionSettings def) { - List viewTags = getTagKeysForView(def); - Set tagsAsStrings = viewTags.stream().map(TagKey::getName).collect(Collectors.toSet()); - if (def.getAggregation() == ViewDefinitionSettings.Aggregation.QUANTILES) { - boolean minEnabled = def.getQuantiles().contains(0.0); - boolean maxEnabled = def.getQuantiles().contains(1.0); - List percentilesFiltered = def.getQuantiles() - .stream() - .filter(p -> p > 0 && p < 1) - .collect(Collectors.toList()); - timeWindowViewManager.createOrUpdatePercentileView(measure.getName(), viewName, measure.getUnit(), def.getDescription(), minEnabled, maxEnabled, percentilesFiltered, def - .getTimeWindow() - .toMillis(), tagsAsStrings, def.getMaxBufferedPoints()); - } else { - timeWindowViewManager.createOrUpdateSmoothedAverageView(measure.getName(), viewName, measure.getUnit(), def.getDescription(), def - .getDropUpper(), def.getDropLower(), def.getTimeWindow() - .toMillis(), tagsAsStrings, def.getMaxBufferedPoints()); - } - - } - - private void registerNewView(Measure measure, String viewName, ViewDefinitionSettings def) { - Aggregation aggregation = createAggregation(def); - List tagKeys = getTagKeysForView(def); - View view = View.create(View.Name.create(viewName), def.getDescription(), measure, aggregation, tagKeys); - viewManager.registerView(view); - } - - /** - * Returns all tags, which are exposed for the given metricDefinition - */ - private List getTagKeysForView(ViewDefinitionSettings viewDefinitionSettings) { - Set tags = new HashSet<>(configuration.getTags().getDefineAsGlobal()); - tags.addAll(viewDefinitionSettings.getTags() - .entrySet() - .stream() - .filter(Map.Entry::getValue) - .map(Map.Entry::getKey) - .collect(Collectors.toList())); - - processRegisteredTags(tags); - - return tags.stream().map(TagKey::create).collect(Collectors.toList()); - } - - /** - * Publish all registered tags as event, if the getTagKeysForView method is called and verify the registered extra tags. - * - * @param tags the registered tags - */ - @VisibleForTesting - void processRegisteredTags(Set tags) { - registeredTags.addAll(tags); - - RegisteredTagsEvent registeredTagsEvent = new RegisteredTagsEvent(this, registeredTags); - applicationEventPublisher.publishEvent(registeredTagsEvent); - - registeredExtraTags = registeredTags.stream() - .filter(configuration.getTags().getExtra()::containsKey) - .collect(Collectors.toSet()); - } - - /** - * Builds TagContext. - */ - public TagContextBuilder getTagContext() { - TagContextBuilder tagContextBuilder = Tags.getTagger().currentBuilder(); - - for (String registeredExtraTag : registeredExtraTags) { - tagContextBuilder.putLocal(TagKey.create(registeredExtraTag), TagUtils.createTagValue(registeredExtraTag, configuration - .getTags() - .getExtra() - .get(registeredExtraTag))); - } - return tagContextBuilder; - } - - /** - * Builds TagContext with custom tags. - * - * @param customTags Map containing the custom tags. - * - * @return {@link TagContextBuilder} which contains the custom and global (extra) tags - */ - public TagContextBuilder getTagContext(Map customTags) { - TagContextBuilder tagContextBuilder = getTagContext(); - - for (Map.Entry customTag : customTags.entrySet()) { - tagContextBuilder.putLocal(TagKey.create(customTag.getKey()), TagUtils.createTagValue(customTag.getKey(), customTag - .getValue())); - } - - return tagContextBuilder; - } - - /** - * Creates an aggregation depending on the given {@link Aggregation} - */ - private static Aggregation createAggregation(ViewDefinitionSettings viewDefinitionSettings) { - switch (viewDefinitionSettings.getAggregation()) { - case COUNT: - return Aggregation.Count.create(); - case SUM: - return Aggregation.Sum.create(); - case HISTOGRAM: - return Aggregation.Distribution.create(BucketBoundaries.create(viewDefinitionSettings.getBucketBoundaries())); - case LAST_VALUE: - return Aggregation.LastValue.create(); - default: - throw new RuntimeException("Unhandled aggregation type: " + viewDefinitionSettings.getAggregation()); - } - } - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/SelfMonitoringMetricManager.java b/src/main/java/rocks/inspectit/oce/eum/server/metrics/SelfMonitoringMetricManager.java deleted file mode 100644 index d6b7e26..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/SelfMonitoringMetricManager.java +++ /dev/null @@ -1,76 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics; - -import com.google.common.annotations.VisibleForTesting; -import io.opencensus.common.Scope; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.MetricDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.selfmonitoring.SelfMonitoringSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; - -import jakarta.annotation.PostConstruct; -import java.util.Collections; -import java.util.Map; - -/** - * Central component, which is responsible for recording self monitoring metrics. - */ -@Component -@Slf4j -public class SelfMonitoringMetricManager { - - @Autowired - private EumServerConfiguration configuration; - - @Autowired - private MeasuresAndViewsManager measuresAndViewsManager; - - @PostConstruct - @VisibleForTesting - void initMetrics() { - SelfMonitoringSettings selfMonitoringSettings = configuration.getSelfMonitoring(); - for (Map.Entry metricEntry : selfMonitoringSettings.getMetrics().entrySet()) { - String measureName = metricEntry.getKey(); - MetricDefinitionSettings metricDefinitionSettings = metricEntry.getValue(); - - String metricName = selfMonitoringSettings.getMetricPrefix() + measureName; - log.info("Registering self-monitoring metric: {}", metricName); - - measuresAndViewsManager.updateMetrics(metricName, metricDefinitionSettings); - } - } - - /** - * Records a self-monitoring measurement with the common tags. - * Only records a measurement if self monitoring is enabled. - * - * @param measureName the name of the measure, excluding the metrics prefix - * @param value the actual value - * @param customTags custom tags - */ - public void record(String measureName, Number value, Map customTags) { - SelfMonitoringSettings selfMonitoringSettings = configuration.getSelfMonitoring(); - if (selfMonitoringSettings.isEnabled() && selfMonitoringSettings.getMetrics().containsKey(measureName)) { - MetricDefinitionSettings metricDefinitionSettings = selfMonitoringSettings.getMetrics().get(measureName); - - String metricName = selfMonitoringSettings.getMetricPrefix() + measureName; - - try (Scope scope = measuresAndViewsManager.getTagContext(customTags).buildScoped()) { - measuresAndViewsManager.recordMeasure(metricName, metricDefinitionSettings, value); - } - } - } - - /** - * Records a self-monitoring measurement with the common tags. - * Only records a measurement if self monitoring is enabled. - * - * @param measureName the name of the measure, excluding the metrics prefix - * @param value the actual value - */ - public void record(String measureName, Number value) { - record(measureName, value, Collections.emptyMap()); - } -} - diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/AsyncMetricRecorder.java b/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/AsyncMetricRecorder.java deleted file mode 100644 index da8ebb3..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/AsyncMetricRecorder.java +++ /dev/null @@ -1,89 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; - -import com.google.common.annotations.VisibleForTesting; -import io.opencensus.common.Timestamp; -import io.opencensus.tags.TagContext; -import lombok.Value; -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.ArrayBlockingQueue; - -/** - * COPIED FROM THE OCELOT CORE PROJECT! - *

- * Consumer thread for asynchronously processing measurement observations. - */ -@Slf4j -class AsyncMetricRecorder { - - private static final int QUEUE_CAPACITY = 8096; - - private final MetricConsumer metricConsumer; - - private volatile boolean overflowLogged = false; - - private volatile boolean isDestroyed = false; - - @VisibleForTesting - final ArrayBlockingQueue recordsQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); - - @VisibleForTesting - final Thread worker; - - AsyncMetricRecorder(MetricConsumer consumer) { - metricConsumer = consumer; - worker = new Thread(this::doRecord); - worker.setDaemon(true); - worker.setName("InspectIT Ocelot percentile Recorder"); - worker.start(); - } - - void record(String measureName, double value, Timestamp time, TagContext tags) { - boolean success = recordsQueue.offer(new MetricRecord(value, measureName, time, tags)); - if (!success && !overflowLogged) { - overflowLogged = true; - log.warn("Measurement for time-window views has been dropped because queue is full. This message will not be shown for further drops!"); - } - } - - void destroy() { - isDestroyed = true; - worker.interrupt(); - } - - private void doRecord() { - while (true) { - try { - MetricRecord record = recordsQueue.take(); - metricConsumer.record(record.measure, record.value, record.time, record.tagContext); - } catch (InterruptedException e) { - if (isDestroyed) { - return; - } else { - log.error("Unexpected interrupt", e); - } - } catch (Exception e) { - log.error("Error processing record: ", e); - } - } - } - - public interface MetricConsumer { - - void record(String measure, double value, Timestamp time, TagContext tags); - } - - @Value - private static class MetricRecord { - - double value; - - String measure; - - Timestamp time; - - TagContext tagContext; - - } - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/CachingMetricProducer.java b/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/CachingMetricProducer.java deleted file mode 100644 index f839849..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/CachingMetricProducer.java +++ /dev/null @@ -1,54 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; - -import io.opencensus.metrics.export.Metric; -import io.opencensus.metrics.export.MetricProducer; - -import java.time.Duration; -import java.util.Collection; -import java.util.function.Supplier; - -/** - * COPIED FROM THE OCELOT CORE PROJECT! - *

- * A metric producer which caches the metrics for a specified amount of time. - */ -public class CachingMetricProducer extends MetricProducer { - - /** - * The function invoked to generate the metrics. - */ - private final Supplier> computeMetricsFunction; - - /** - * The duration for which cached metrics are kept. - */ - private final long cacheDurationNanos; - - /** - * The timestamp when the metrics were computed the last time. - */ - private long cacheTimestamp; - - private Collection cachedMetrics = null; - - /** - * Constructor. - * - * @param computeMetricsFunction the function to invoke for computing the metrics - * @param cacheDuration the duration for which the values shall be cached. - */ - public CachingMetricProducer(Supplier> computeMetricsFunction, Duration cacheDuration) { - this.computeMetricsFunction = computeMetricsFunction; - cacheDurationNanos = cacheDuration.toNanos(); - } - - @Override - public synchronized Collection getMetrics() { - long now = System.nanoTime(); - if (cachedMetrics == null || (now - cacheTimestamp) > cacheDurationNanos) { - cachedMetrics = computeMetricsFunction.get(); - cacheTimestamp = now; - } - return cachedMetrics; - } -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/PercentileView.java b/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/PercentileView.java deleted file mode 100644 index c7a9840..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/PercentileView.java +++ /dev/null @@ -1,173 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; - -import com.google.common.annotations.VisibleForTesting; -import io.opencensus.common.Timestamp; -import io.opencensus.metrics.LabelKey; -import io.opencensus.metrics.export.MetricDescriptor; -import lombok.Getter; -import org.apache.commons.math3.stat.descriptive.rank.Percentile; - -import java.lang.reflect.Array; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.*; - -/** - * For the data within this window, percentiles and min / max values can be computed. - */ -public class PercentileView extends TimeWindowView { - - /** - * The tag to use for the percentile or "min","max" respectively. - */ - private static final String PERCENTILE_TAG_KEY = "quantile"; - - /** - * The tag value to use for {@link #PERCENTILE_TAG_KEY} for the "minimum" series. - */ - private static final String MIN_METRIC_SUFFIX = "_min"; - - /** - * The tag value to use for {@link #PERCENTILE_TAG_KEY} for the "maximum" series. - */ - private static final String MAX_METRIC_SUFFIX = "_max"; - - /** - * The formatter used to print percentiles to tags. - */ - private static final DecimalFormat PERCENTILE_TAG_FORMATTER = new DecimalFormat("#.#####", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); - - /** - * The descriptor of the metric for this view, if percentile. - */ - private MetricDescriptor percentileMetricDescriptor; - - /** - * If not null, the minimum value will be exposed as this gauge. - */ - private MetricDescriptor minMetricDescriptor; - - /** - * If not null, the maximum value will be exposed as this gauge. - */ - private MetricDescriptor maxMetricDescriptor; - - /** - * The percentiles to compute in the range (0,1) - */ - @Getter - private Set percentiles; - - /** - * Constructor. - * - * @param includeMin true, if the minimum value should be exposed as metric - * @param includeMax true, if the maximum value should be exposed as metric - * @param percentiles the set of percentiles in the range (0,1) which shall be provided as metrics - * @param tags the tags to use for this view - * @param timeWindowMillis the time range in milliseconds to use for computing minimum / maximum and percentile values - * @param viewName the prefix to use for the names of all exposed metrics - * @param unit the unit of the measure - * @param description the description of this view - * @param bufferLimit the maximum number of measurements to be buffered by this view - */ - PercentileView(boolean includeMin, boolean includeMax, Set percentiles, Set tags, long timeWindowMillis, String viewName, String unit, String description, int bufferLimit) { - super(tags, timeWindowMillis, viewName, unit, description, bufferLimit); - validateConfiguration(includeMin, includeMax, percentiles); - - this.percentiles = new HashSet<>(percentiles); - - List percentileLabelKeys = getLabelKeysInOrder(PERCENTILE_TAG_KEY); - List minMaxLabelKeys = getLabelKeysInOrder(); - if (!percentiles.isEmpty()) { - percentileMetricDescriptor = MetricDescriptor.create(viewName, description, unit, MetricDescriptor.Type.GAUGE_DOUBLE, percentileLabelKeys); - } - if (includeMin) { - minMetricDescriptor = MetricDescriptor.create(viewName + MIN_METRIC_SUFFIX, description, unit, MetricDescriptor.Type.GAUGE_DOUBLE, minMaxLabelKeys); - } - if (includeMax) { - maxMetricDescriptor = MetricDescriptor.create(viewName + MAX_METRIC_SUFFIX, description, unit, MetricDescriptor.Type.GAUGE_DOUBLE, minMaxLabelKeys); - } - } - - private void validateConfiguration(boolean includeMin, boolean includeMax, Set percentiles) { - percentiles.stream().filter(p -> p <= 0.0 || p >= 1.0).forEach(p -> { - throw new IllegalArgumentException("Percentiles must be in range (0,1)"); - }); - if (percentiles.isEmpty() && !includeMin && !includeMax) { - throw new IllegalArgumentException("You must specify at least one percentile or enable minimum or maximum computation!"); - } - } - - @Override - Set getSeriesNames() { - Set result = new HashSet<>(); - if (minMetricDescriptor != null) { - result.add(minMetricDescriptor.getName()); - } - if (maxMetricDescriptor != null) { - result.add(maxMetricDescriptor.getName()); - } - if (!percentiles.isEmpty()) { - result.add(percentileMetricDescriptor.getName()); - } - return result; - } - - boolean isMinEnabled() { - return minMetricDescriptor != null; - } - - boolean isMaxEnabled() { - return maxMetricDescriptor != null; - } - - @VisibleForTesting - static String getPercentileTag(double percentile) { - return PERCENTILE_TAG_FORMATTER.format(percentile); - } - - @Override - protected List getMetrics() { - List metrics = new ArrayList<>(); - if (isMinEnabled()) { - metrics.add(minMetricDescriptor); - } - if (isMaxEnabled()) { - metrics.add(maxMetricDescriptor); - } - if (!percentiles.isEmpty()) { - metrics.add(percentileMetricDescriptor); - } - return metrics; - } - - @Override - protected void computeSeries(List tagValues, double[] data, Timestamp time, ResultSeriesCollector resultSeries) { - if (isMinEnabled() || isMaxEnabled()) { - double minValue = Double.MAX_VALUE; - double maxValue = -Double.MAX_VALUE; - for (double value : data) { - minValue = Math.min(minValue, value); - maxValue = Math.max(maxValue, value); - } - if (isMinEnabled()) { - resultSeries.add(minMetricDescriptor, minValue, time, tagValues); - } - if (isMaxEnabled()) { - resultSeries.add(maxMetricDescriptor, maxValue, time, tagValues); - } - } - if (!percentiles.isEmpty()) { - Percentile percentileComputer = new Percentile(); - percentileComputer.setData(data); - for (double percentile : percentiles) { - double percentileValue = percentileComputer.evaluate(percentile * 100); - List percentileTagValues = new ArrayList<>(tagValues); - percentileTagValues.add(getPercentileTag(percentile)); - resultSeries.add(percentileMetricDescriptor, percentileValue, time, percentileTagValues); - } - } - } - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowView.java b/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowView.java deleted file mode 100644 index 452459e..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowView.java +++ /dev/null @@ -1,307 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; - -import io.opencensus.common.Timestamp; -import io.opencensus.metrics.LabelKey; -import io.opencensus.metrics.LabelValue; -import io.opencensus.metrics.export.*; -import io.opencensus.tags.InternalUtils; -import io.opencensus.tags.Tag; -import io.opencensus.tags.TagContext; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -import java.time.Duration; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; - -/** - * COPIED FROM THE OCELOT CORE PROJECT AND MODIFIED! - *

- * Holds the data for a given measurement splitted by a provided set of tags over a given time window. - */ -@Slf4j -public abstract class TimeWindowView { - - private static final Duration CLEANUP_INTERVAL = Duration.ofSeconds(1); - - /** - * Defines the tags which are used for the view. - * E.g. if the tag "http_path" is used, percentiles will be computed for each http_path individually. - *

- * The tag values are stored in a fixed order in the keys of {@link #seriesValues} for each series. - * The values within {@link #tagIndices} define at which position within these arrays the corresponding tag value is found. - * E.g. if tagIndex["http_path"] = 2, this means that the values for http_path will be at index 2 in the keys of {@link #seriesValues}. - */ - private Map tagIndices; - - /** - * Stores the buffered data of the sliding time window for each time series. - */ - private ConcurrentHashMap, WindowedDoubleQueue> seriesValues; - - /** - * Defines the size of the sliding window in milliseconds. - * E.g. a value of 15000 means that percentiles will be computed based on the last 15 seconds. - */ - @Getter - private long timeWindowMillis; - - /** - * The name of the view, used as prefix for all individual metrics. - */ - @Getter - private String viewName; - - /** - * The unit of the measure. - */ - @Getter - private String unit; - - /** - * The description of this view. - */ - @Getter - private String description; - - /** - * The maximum amount of measurement points to buffer. - * If this limit is reached, new measuremetns will be rejected until there is space again. - */ - @Getter - private int bufferLimit; - - private boolean overflowWarningPrinted = false; - - /** - * The current number of points stored in this view, limited by {@link #bufferLimit}. - */ - private AtomicInteger numberOfPoints; - - /** - * The timestamp when the last full cleanup happened. - */ - private AtomicLong lastCleanupTimeMs; - - /** - * Constructor. - * - * @param tags the tags to use for this view - * @param timeWindowMillis the time range in milliseconds to use for computing minimum / maximum and percentile values - * @param viewName the prefix to use for the names of all exposed metrics - * @param unit the unit of the measure - * @param description the description of this view - * @param bufferLimit the maximum number of measurements to be buffered by this view - */ - TimeWindowView(Set tags, long timeWindowMillis, String viewName, String unit, String description, int bufferLimit) { - validateConfiguration(timeWindowMillis, viewName, unit, description, bufferLimit); - assignTagIndices(tags); - seriesValues = new ConcurrentHashMap<>(); - this.timeWindowMillis = timeWindowMillis; - this.viewName = viewName; - this.unit = unit; - this.description = description; - this.bufferLimit = bufferLimit; - numberOfPoints = new AtomicInteger(0); - lastCleanupTimeMs = new AtomicLong(0); - } - - private void validateConfiguration(long timeWindowMillis, String baseViewName, String unit, String description, int bufferLimit) { - if (StringUtils.isBlank(baseViewName)) { - throw new IllegalArgumentException("View name must not be blank!"); - } - if (StringUtils.isBlank(description)) { - throw new IllegalArgumentException("Description must not be blank!"); - } - if (StringUtils.isBlank(unit)) { - throw new IllegalArgumentException("Unit must not be blank!"); - } - if (timeWindowMillis <= 0) { - throw new IllegalArgumentException("Time window must not be positive!"); - } - if (bufferLimit < 1) { - throw new IllegalArgumentException("The buffer limit must be greater than or equal to 1!"); - } - } - - private void assignTagIndices(Set tags) { - tagIndices = new HashMap<>(); - int idx = 0; - for (String tag : tags) { - tagIndices.put(tag, idx); - idx++; - } - } - - /** - * Adds the provided value to the sliding window of data. - * - * @param value the value of the measure - * @param time the timestamp when this value was observed - * @param tagContext the tags with which this value was observed - * - * @return true, if the point could be added, false otherwise. - */ - boolean insertValue(double value, Timestamp time, TagContext tagContext) { - removeStalePointsIfTimeThresholdExceeded(time); - List tags = getTagsList(tagContext); - WindowedDoubleQueue queue = seriesValues.computeIfAbsent(tags, (key) -> new WindowedDoubleQueue(timeWindowMillis)); - synchronized (queue) { - long timeMillis = getInMillis(time); - int removed = queue.removeStaleValues(timeMillis); - int currentSize = numberOfPoints.addAndGet(-removed); - if (currentSize < bufferLimit) { - numberOfPoints.incrementAndGet(); - queue.insert(value, timeMillis); - } else { - if (!overflowWarningPrinted) { - overflowWarningPrinted = true; - log.warn("Dropping points for Percentiles-View '{}' because the buffer limit has been reached!" + " Quantiles/Min/Max will be meaningless." + " This warning will not be shown for future drops!", viewName); - } - return false; - } - } - return true; - } - - /** - * Returns the name of the series exposed by this view. - * This can be up to three series, depending on whether min/max and quantiles are enabled. - * - * @return the names of the exposed series. - */ - abstract Set getSeriesNames(); - - /** - * Removes all data which has fallen out of the time window based on the given timestamp. - * - * @param time the current time - */ - private void removeStalePoints(Timestamp time) { - long timeMillis = getInMillis(time); - lastCleanupTimeMs.set(timeMillis); - for (WindowedDoubleQueue queue : seriesValues.values()) { - synchronized (queue) { - int removed = queue.removeStaleValues(timeMillis); - numberOfPoints.getAndAdd(-removed); - } - } - } - - /** - * Removes all data which has fallen out of the time window based on the given timestamp. - * Only performs the cleanup if the last cleanup has been done more than {@link #CLEANUP_INTERVAL} ago - * and the buffer is running on it's capacity limit. - * - * @param time the current time - */ - private void removeStalePointsIfTimeThresholdExceeded(Timestamp time) { - long timeMillis = getInMillis(time); - long lastCleanupTime = lastCleanupTimeMs.get(); - boolean timeThresholdExceeded = timeMillis - lastCleanupTime > CLEANUP_INTERVAL.toMillis(); - if (timeThresholdExceeded && numberOfPoints.get() >= bufferLimit) { - removeStalePoints(time); - } - } - - /** - * @return the tags used for this view - */ - Set getTagKeys() { - return tagIndices.keySet(); - } - - protected abstract List getMetrics(); - - /** - * Computes the defined percentile and min / max metrics. - * - * @param time the current timestamp - * - * @return the metrics containing the percentiles and min / max - */ - Collection computeMetrics(Timestamp time) { - removeStalePoints(time); - ResultSeriesCollector resultSeries = new ResultSeriesCollector(getMetrics()); - for (Map.Entry, WindowedDoubleQueue> series : seriesValues.entrySet()) { - List tagValues = series.getKey(); - WindowedDoubleQueue queue = series.getValue(); - double[] data = null; - synchronized (queue) { - int size = queue.size(); - if (size > 0) { - data = queue.copy(); - } - } - if (data != null) { - computeSeries(tagValues, data, time, resultSeries); - } - } - - List resultMetrics = new ArrayList<>(); - for (Map.Entry> metric : resultSeries.seriesMap.entrySet()) { - resultMetrics.add(Metric.create(metric.getKey(), metric.getValue())); - } - return resultMetrics; - } - - protected abstract void computeSeries(List tagValues, double[] data, Timestamp time, ResultSeriesCollector resultSeries); - - private List getTagsList(TagContext tagContext) { - String[] tagValues = new String[tagIndices.size()]; - Arrays.fill(tagValues, ""); - for (Iterator it = InternalUtils.getTags(tagContext); it.hasNext(); ) { - Tag tag = it.next(); - String tagKey = tag.getKey().getName(); - Integer index = tagIndices.get(tagKey); - if (index != null) { - tagValues[index] = tag.getValue().asString(); - } - } - return Arrays.asList(tagValues); - } - - private List toLabelValues(List tagValues) { - return tagValues.stream().map(LabelValue::create).collect(Collectors.toList()); - } - - protected List getLabelKeysInOrder() { - LabelKey[] keys = new LabelKey[tagIndices.size()]; - tagIndices.forEach((tag, index) -> keys[index] = LabelKey.create(tag, "")); - return Arrays.asList(keys); - } - - protected List getLabelKeysInOrder(String extraTag) { - LabelKey[] keys = new LabelKey[tagIndices.size() + 1]; - tagIndices.forEach((tag, index) -> keys[index] = LabelKey.create(tag, "")); - keys[keys.length - 1] = LabelKey.create(extraTag, ""); - return Arrays.asList(keys); - } - - private long getInMillis(Timestamp time) { - return Duration.ofSeconds(time.getSeconds()).toMillis() + Duration.ofNanos(time.getNanos()).toMillis(); - } - - protected class ResultSeriesCollector { - - private Map> seriesMap = new HashMap<>(); - - public ResultSeriesCollector(List metrics) { - if (metrics != null) { - metrics.forEach(metric -> seriesMap.put(metric, new ArrayList<>())); - } - } - - void add(MetricDescriptor metric, double value, Timestamp time, List tags) { - List series = seriesMap.computeIfAbsent(metric, m -> new ArrayList<>()); - Point pt = Point.create(Value.doubleValue(value), time); - series.add(TimeSeries.createWithOnePoint(toLabelValues(tags), pt, time)); - } - - } - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowViewManager.java b/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowViewManager.java deleted file mode 100644 index 83de5c6..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowViewManager.java +++ /dev/null @@ -1,321 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; - -import com.google.common.annotations.VisibleForTesting; -import io.opencensus.common.Timestamp; -import io.opencensus.metrics.Metrics; -import io.opencensus.metrics.export.Metric; -import io.opencensus.metrics.export.MetricProducer; -import io.opencensus.stats.MeasureMap; -import io.opencensus.tags.TagContext; -import io.opencensus.tags.Tags; -import org.springframework.stereotype.Component; - -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -/** - * COPIED FROM THE OCELOT CORE PROJECT! - *

- * Allows the creation, update and removal of percentile views on metrics. - * Note that these views coexist to opencensus {@link io.opencensus.stats.View}s. - * For this reason observation must be reported via {@link #recordMeasurement(String, double)} - * in addition to {@link MeasureMap#record()}. - */ -@Component -public class TimeWindowViewManager { - - /** - * Maps the name of measures to registered percentile views. - */ - private ConcurrentHashMap> measuresToViewsMap = new ConcurrentHashMap<>(); - - /** - * Computation of percentiles can be expensive. - * For this reason we cache computed metrics for 1 second before recomputing them. - * Otherwise e.g. spamming F5 on the prometheus endpoint could lead to an increased CPU usage. - */ - private final MetricProducer producer = new CachingMetricProducer(this::computeMetrics, Duration.ofSeconds(1)); - - /** - * The clock used for timing metrics. - */ - private final Supplier clock; - - /** - * Maps series names to the corresponding names of measures. - * Acts as a cache which is invalidated when views are added changed or removed. - */ - private Map seriesToMeasuresCache; - - /** - * Recording observation takes amortized O(1) time. - * However, the worst-case time of a recording is O(n), which is why we decouple the recording from the application threads. - * This worker maintains a fixed-size queue of observations which are then added via {@link #recordSynchronous(String, double, Timestamp, TagContext)}. - */ - @VisibleForTesting - AsyncMetricRecorder worker = new AsyncMetricRecorder(this::recordSynchronous); - - public TimeWindowViewManager() { - this(System::currentTimeMillis); - } - - @VisibleForTesting - TimeWindowViewManager(Supplier clock) { - this.clock = clock; - } - - @PostConstruct - void init() { - Metrics.getExportComponent().getMetricProducerManager().add(producer); - } - - @PreDestroy - void destroy() { - worker.destroy(); - Metrics.getExportComponent().getMetricProducerManager().remove(producer); - } - - /** - * Records a measurement observation for a given measure. - * Tags are expected to be given via teh OC TagContext. - * - * @param measureName the name of the measure, e.g. http/responsetime - * @param value the observation to record - */ - public void recordMeasurement(String measureName, double value) { - recordMeasurement(measureName, value, Tags.getTagger().getCurrentTagContext()); - } - - /** - * Records a measurement observation for a given measure. - * - * @param measureName the name of the measure, e.g. http/responsetime - * @param value the observation to record - * @param tags the TagContext to use - */ - public void recordMeasurement(String measureName, double value, TagContext tags) { - if (areAnyViewsRegisteredForMeasure(measureName)) { - synchronized (this) { - worker.record(measureName, value, getCurrentTime(), tags); - } - } - } - - /** - * Creates a new smoothed_average view if no view with the given name exists for the given measure. - * If a view with the given name already exists for the given measure, it is updated instead. - * When a view is updated, all buffered observation are lost. - * - * @param measureName the name of the measure, e.g. "http/responsetime" - * @param viewName the name of the view, e.g. "http/responsetime/distribution" - * @param unit the unit of the view - * @param description the description for the view - * @param dropUpper value in percentage in the range (0,1) which indicates how many metrics in the upper range shall be dropped - * @param dropLower value in percentage in the range (0,1) which indicates how many metrics in the lower range shall be dropped - * @param timeWindowMillis the length of the sliding time window to use for computing min / max and the percentiles - * @param tags the tags to use for the view - * @param bufferLimit the maximum number of points this view is allowed to buffer - */ - public void createOrUpdateSmoothedAverageView(String measureName, String viewName, String unit, String description, double dropUpper, double dropLower, long timeWindowMillis, Collection tags, int bufferLimit) { - createOrUpdateView(SmoothedAverageView.class, measureName, viewName, unit, description, false, false, null, dropUpper, dropLower, timeWindowMillis, tags, bufferLimit); - } - - /** - * Creates a new percentile view if no view with the given name exists for the given measure. - * If a view with the given name already exists for the given measure, it is updated instead. - * When a view is updated, all buffered observation are lost. - * - * @param measureName the name of the measure, e.g. "http/responsetime" - * @param viewName the name of the view, e.g. "http/responsetime/distribution" - * @param unit the unit of the view - * @param description the description for the view - * @param minEnabled true, if the minimum shall be exposed as metric - * @param maxEnabled true, if the minimum shall be exposed as metric - * @param percentiles specified which percentiles shall be exposed as metric, values are in the range (0,1) - * @param timeWindowMillis the length of the sliding time window to use for computing min / max and the percentiles - * @param tags the tags to use for the view - * @param bufferLimit the maximum number of points this view is allowed to buffer - */ - public void createOrUpdatePercentileView(String measureName, String viewName, String unit, String description, boolean minEnabled, boolean maxEnabled, Collection percentiles, long timeWindowMillis, Collection tags, int bufferLimit) { - createOrUpdateView(PercentileView.class, measureName, viewName, unit, description, minEnabled, maxEnabled, percentiles, -1, -1, timeWindowMillis, tags, bufferLimit); - } - - private synchronized void createOrUpdateView(Class viewType, String measureName, String viewName, String unit, String description, boolean minEnabled, boolean maxEnabled, Collection percentiles, double dropUpper, double dropLower, long timeWindowMillis, Collection tags, int bufferLimit) { - List views = measuresToViewsMap.computeIfAbsent(measureName, (name) -> new CopyOnWriteArrayList<>()); - Optional existingView = views.stream() - .filter(view -> view.getViewName().equalsIgnoreCase(viewName)) - .findFirst(); - Optional updatedView; - if (existingView.isPresent()) { - updatedView = updateView(viewType, existingView.get(), unit, description, minEnabled, maxEnabled, percentiles, dropUpper, dropLower, timeWindowMillis, tags, bufferLimit); - } else { - updatedView = Optional.of(createView(viewType, viewName, unit, description, minEnabled, maxEnabled, percentiles, dropUpper, dropLower, timeWindowMillis, tags, bufferLimit)); - } - if (updatedView.isPresent()) { - existingView.ifPresent(views::remove); - views.add(updatedView.get()); - } - seriesToMeasuresCache = null; - } - - public synchronized boolean isViewRegistered(String measureName, String viewName) { - List views = measuresToViewsMap.get(measureName); - if (views != null) { - return views.stream().map(TimeWindowView::getViewName).anyMatch(name -> name.equals(viewName)); - } - return false; - } - - /** - * Each percentile view can expose a set of series. - * Given the name of such a series, this method returns the measure for which the corresponding view is registered. - * - * @param seriesName the name of the series - * - * @return the name of the source measure or null if this series does not originate from a percentile view. - */ - public String getMeasureNameForSeries(String seriesName) { - return getSeriesToMeasuresCache().get(seriesName); - } - - /** - * Removes the given view from the given measure, if it exists. - * - * @param measureName the name of the measure - * @param viewName the name of the view - * - * @return true, if the view existed and has been removed, false otherwise - */ - public synchronized boolean removeView(String measureName, String viewName) { - List views = measuresToViewsMap.get(measureName); - if (views != null) { - Optional existingView = views.stream() - .filter(view -> view.getViewName().equalsIgnoreCase(viewName)) - .findFirst(); - if (existingView.isPresent()) { - views.remove(existingView.get()); - if (views.isEmpty()) { - measuresToViewsMap.remove(measureName); - } - seriesToMeasuresCache = null; - return true; - } - } - return false; - } - - /** - * Returns true, if any percentile view is registered for the given measure. - * - * @param measureName the name of the measure to check - * - * @return true, if any percentile views exist - */ - @VisibleForTesting - boolean areAnyViewsRegisteredForMeasure(String measureName) { - return measuresToViewsMap.containsKey(measureName); - } - - /** - * Synchronously records the specified measure observation. - * - * @param measure the name of the measure - * @param value the observed value - * @param time the timestamp of the observation - * @param tagContext the tag context of the observation - */ - private void recordSynchronous(String measure, double value, Timestamp time, TagContext tagContext) { - List views = measuresToViewsMap.get(measure); - if (views != null) { - views.forEach(view -> view.insertValue(value, time, tagContext)); - } - } - - private synchronized Map getSeriesToMeasuresCache() { - if (seriesToMeasuresCache == null) { - seriesToMeasuresCache = new HashMap<>(); - measuresToViewsMap.forEach((measure, views) -> views.stream() - .flatMap(view -> view.getSeriesNames().stream()) - .forEach(series -> seriesToMeasuresCache.put(series, measure))); - } - return seriesToMeasuresCache; - } - - /** - * @return the current time as OC timestamp. - */ - private Timestamp getCurrentTime() { - return Timestamp.fromMillis(clock.get()); - } - - private Optional updateView(Class viewType, TimeWindowView existingView, String unit, String description, boolean minEnabled, boolean maxEnabled, Collection percentiles, double dropUpper, double dropLower, long timeWindowMillis, Collection tags, int bufferLimit) { - Supplier creator = () -> createView(viewType, existingView.getViewName(), unit, description, minEnabled, maxEnabled, percentiles, dropUpper, dropLower, timeWindowMillis, tags, bufferLimit); - - if (!unit.equals(existingView.getUnit())) { - return Optional.of(creator.get()); - } - if (!description.equals(existingView.getDescription())) { - return Optional.of(creator.get()); - } - if (timeWindowMillis != existingView.getTimeWindowMillis()) { - return Optional.of(creator.get()); - } - if (!existingView.getTagKeys().equals(new HashSet<>(tags))) { - return Optional.of(creator.get()); - } - if (existingView.getBufferLimit() != bufferLimit) { - return Optional.of(creator.get()); - } - - if (existingView instanceof PercentileView) { - PercentileView percentileView = (PercentileView) existingView; - if (minEnabled != percentileView.isMinEnabled()) { - return Optional.of(creator.get()); - } - if (maxEnabled != percentileView.isMaxEnabled()) { - return Optional.of(creator.get()); - } - if (!percentileView.getPercentiles().equals(new HashSet<>(percentiles))) { - return Optional.of(creator.get()); - } - } - - if (existingView instanceof SmoothedAverageView) { - SmoothedAverageView saView = (SmoothedAverageView) existingView; - - if (dropUpper != saView.getDropUpper()) { - return Optional.of(creator.get()); - } - if (dropLower != saView.getDropLower()) { - return Optional.of(creator.get()); - } - } - - return Optional.empty(); - } - - private T createView(Class viewType, String viewName, String unit, String description, boolean minEnabled, boolean maxEnabled, Collection percentiles, double dropUpper, double dropLower, long timeWindowMillis, Collection tags, int bufferLimit) { - if (PercentileView.class.equals(viewType)) { - return (T) new PercentileView(minEnabled, maxEnabled, new HashSet<>(percentiles), new HashSet<>(tags), timeWindowMillis, viewName, unit, description, bufferLimit); - } else { - return (T) new SmoothedAverageView(dropUpper, dropLower, new HashSet<>(tags), timeWindowMillis, viewName, unit, description, bufferLimit); - } - } - - @VisibleForTesting - Collection computeMetrics() { - Timestamp now = getCurrentTime(); - return measuresToViewsMap.values() - .stream() - .flatMap(Collection::stream) - .flatMap(view -> view.computeMetrics(now).stream()) - .collect(Collectors.toList()); - } - -} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/utils/TagUtils.java b/src/main/java/rocks/inspectit/oce/eum/server/utils/TagUtils.java deleted file mode 100644 index f1873a5..0000000 --- a/src/main/java/rocks/inspectit/oce/eum/server/utils/TagUtils.java +++ /dev/null @@ -1,77 +0,0 @@ -package rocks.inspectit.oce.eum.server.utils; - -import com.google.common.annotations.VisibleForTesting; -import io.opencensus.internal.StringUtils; -import io.opencensus.tags.TagValue; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public final class TagUtils { - - /** - * Counter for the number of warnings that have already been printed - */ - @VisibleForTesting - static int printedWarningCounter = 0; - - /** - * The time in ms when the last warning was printed - */ - @VisibleForTesting - static long lastWarningTime = 0; - - /** - * The number of maximum warnings that are to be printed - */ - private final static int MAX_WARNING_PRINTS = 10; - - /** - * The duration in ms that needs to pass, if MAX_WARNING_PRINTS has been reached - */ - private final static int WAITING_TIME_IN_MILLI_SECONDS = 600_000; - - /** - * Boolean that indicates whether the user gets a message that further logs are suppressed - */ - private static boolean PRINT_FURTHER_MESSAGE = true; - - private TagUtils() { - // empty private default constructor for util class - } - - /** - * Constructs a {@code io.opencensus.tags.TagValue} from the given string. - * If String is not valid an <invalid> TagName is created. - * - * @param tagKey the tag key - * @param value the tag value - * - * @return the created TagValue with 'v' or '<invalid>' - */ - public static TagValue createTagValue(String tagKey, String value) { - if (isTagValueValid(value)) { - return TagValue.create(value); - } - printWarning(tagKey, value); - return TagValue.create(""); - } - - private static boolean isTagValueValid(String value) { - return value.length() <= TagValue.MAX_LENGTH && StringUtils.isPrintableString(value); - } - - private static void printWarning(String tagKey, String value) { - if ((System.currentTimeMillis() - lastWarningTime) > WAITING_TIME_IN_MILLI_SECONDS) { - printedWarningCounter = 0; - PRINT_FURTHER_MESSAGE = true; - } - if (printedWarningCounter < MAX_WARNING_PRINTS) { - log.warn("Error creating value for tag <{}>: illegal tag value <{}> converted to ", tagKey, value); - printedWarningCounter++; - lastWarningTime = System.currentTimeMillis(); - } else if (PRINT_FURTHER_MESSAGE) { - log.warn("Further log messages are suppressed"); - PRINT_FURTHER_MESSAGE = false; - } - } -} \ No newline at end of file diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/AppStartupRunner.java b/src/main/java/rocks/inspectit/ocelot/eum/server/AppStartupRunner.java new file mode 100644 index 0000000..3b67f01 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/AppStartupRunner.java @@ -0,0 +1,23 @@ +package rocks.inspectit.ocelot.eum.server; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.utils.VersionUtil; + +@Slf4j +@Getter +@Component +public class AppStartupRunner implements ApplicationRunner { + + @Override + public void run(ApplicationArguments args) { + log.info("> Version Information"); + log.info("\tVersion: {}", VersionUtil.getServerVersion()); + log.info("\tBuild Date: {}", VersionUtil.getServerBuildDate()); + log.info("\tOpenTelemetry Version: {}", VersionUtil.getOpenTelemetryVersion()); + log.info("\tBoomerangjs Version: {}", VersionUtil.getBoomerangVersion()); + } +} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/EUMServerApplication.java b/src/main/java/rocks/inspectit/ocelot/eum/server/EUMServerApplication.java similarity index 88% rename from src/main/java/rocks/inspectit/oce/eum/server/EUMServerApplication.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/EUMServerApplication.java index e5b7da0..8cf8343 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/EUMServerApplication.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/EUMServerApplication.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server; +package rocks.inspectit.ocelot.eum.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/arithmetic/ArithmeticExpression.java b/src/main/java/rocks/inspectit/ocelot/eum/server/arithmetic/ArithmeticExpression.java similarity index 98% rename from src/main/java/rocks/inspectit/oce/eum/server/arithmetic/ArithmeticExpression.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/arithmetic/ArithmeticExpression.java index 7ab9d88..377a0cc 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/arithmetic/ArithmeticExpression.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/arithmetic/ArithmeticExpression.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.arithmetic; +package rocks.inspectit.ocelot.eum.server.arithmetic; /** * Class used to solve arithmetic expressions. diff --git a/src/main/java/rocks/inspectit/oce/eum/server/arithmetic/RawExpression.java b/src/main/java/rocks/inspectit/ocelot/eum/server/arithmetic/RawExpression.java similarity index 97% rename from src/main/java/rocks/inspectit/oce/eum/server/arithmetic/RawExpression.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/arithmetic/RawExpression.java index dbd9ef6..f805078 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/arithmetic/RawExpression.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/arithmetic/RawExpression.java @@ -1,8 +1,8 @@ -package rocks.inspectit.oce.eum.server.arithmetic; +package rocks.inspectit.ocelot.eum.server.arithmetic; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import java.util.ArrayList; import java.util.Collections; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/Beacon.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/Beacon.java similarity index 93% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/Beacon.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/Beacon.java index 31ca0be..374ad0b 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/Beacon.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/Beacon.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.beacon; +package rocks.inspectit.ocelot.eum.server.beacon; import java.util.Arrays; import java.util.Collection; @@ -77,7 +77,7 @@ public Beacon merge(Beacon beacon) { } /** - * Checks whether the beacon contains all of the given fields. + * Checks whether the beacon contains all the given fields. * * @param fieldKeys field keys to check * @@ -88,7 +88,7 @@ public boolean contains(String... fieldKeys) { } /** - * Checks whether the beacon contains all of the fields contained in the given list. + * Checks whether the beacon contains all the fields contained in the given list. * * @param fieldKeys List containing field keys to check * diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/BeaconECSSerializer.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/BeaconECSSerializer.java similarity index 95% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/BeaconECSSerializer.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/BeaconECSSerializer.java index b6e7f52..ff3fde3 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/BeaconECSSerializer.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/BeaconECSSerializer.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.beacon; +package rocks.inspectit.ocelot.eum.server.beacon; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/BeaconProcessor.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BeaconProcessor.java similarity index 74% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/BeaconProcessor.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BeaconProcessor.java index 2d21cf3..b5907e4 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/BeaconProcessor.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BeaconProcessor.java @@ -1,6 +1,6 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; /** * Interface for all components acting as {@link BeaconProcessor}. diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/BmrBeaconProcessor.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BmrBeaconProcessor.java similarity index 95% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/BmrBeaconProcessor.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BmrBeaconProcessor.java index a0f640d..362dd9d 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/BmrBeaconProcessor.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BmrBeaconProcessor.java @@ -1,11 +1,11 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import java.util.HashMap; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/ClientHeaderBeaconProcessor.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/ClientHeaderBeaconProcessor.java similarity index 89% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/ClientHeaderBeaconProcessor.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/ClientHeaderBeaconProcessor.java index 16b624a..7c14864 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/ClientHeaderBeaconProcessor.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/ClientHeaderBeaconProcessor.java @@ -1,10 +1,10 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.utils.RequestUtils; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.utils.RequestUtils; import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CompositeBeaconProcessor.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CompositeBeaconProcessor.java similarity index 88% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CompositeBeaconProcessor.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CompositeBeaconProcessor.java index bc3b55d..7906087 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CompositeBeaconProcessor.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CompositeBeaconProcessor.java @@ -1,12 +1,12 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.metrics.SelfMonitoringMetricManager; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.metrics.SelfMonitoringMetricManager; import java.util.List; import java.util.concurrent.TimeUnit; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CountryCodeBeaconProcessor.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CountryCodeBeaconProcessor.java similarity index 86% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CountryCodeBeaconProcessor.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CountryCodeBeaconProcessor.java index 26d0370..17f76b2 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CountryCodeBeaconProcessor.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CountryCodeBeaconProcessor.java @@ -1,14 +1,14 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import com.google.common.collect.ImmutableMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.utils.GeolocationResolver; -import rocks.inspectit.oce.eum.server.utils.IPUtils; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.utils.GeolocationResolver; +import rocks.inspectit.ocelot.eum.server.utils.IPUtils; import java.util.List; import java.util.Map; @@ -63,7 +63,7 @@ private String resolveCountryCode() { * @return the CountryCode */ private String resolveCustomIPMapping(String ip) { - Map> customIpMapping = configuration.getTags().getCustomIPMapping(); + Map> customIpMapping = configuration.getAttributes().getCustomIPMapping(); if (customIpMapping != null) { for (Map.Entry> customCountryCodeDefinition : customIpMapping.entrySet()) { if (customCountryCodeDefinition.getValue().stream() diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessor.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessor.java similarity index 93% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessor.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessor.java index 95647fe..bf21ec9 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessor.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessor.java @@ -1,7 +1,7 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import java.util.Collections; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessor.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessor.java similarity index 66% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessor.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessor.java index 3eec77a..5c4633d 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessor.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessor.java @@ -1,14 +1,14 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import lombok.Builder; import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.tags.BeaconTagSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.tags.PatternAndReplacement; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.BeaconAttributeSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.PatternAndReplacement; import java.util.*; import java.util.function.Function; @@ -23,17 +23,17 @@ @Component public class RegexReplacementBeaconProcessor implements BeaconProcessor { - private List derivedTags; + private List derivedAttributes; @Autowired public RegexReplacementBeaconProcessor(EumServerConfiguration config) { - Map unorderedTags = config.getTags().getBeacon().entrySet().stream() - .map(e -> RegexDerivedTag.fromSettings(e.getKey(), e.getValue())) - .collect(Collectors.toMap(RegexDerivedTag::getTagName, t -> t)); - derivedTags = getInTopologicalOrder(unorderedTags.values(), tag -> { - String input = tag.getInputBeaconField(); - if (unorderedTags.containsKey(input)) { - return Collections.singletonList(unorderedTags.get(input)); + Map unorderedAttributes = config.getAttributes().getBeacon().entrySet().stream() + .map(e -> RegexDerivedAttribute.fromSettings(e.getKey(), e.getValue())) + .collect(Collectors.toMap(RegexDerivedAttribute::getAttributeName, attr -> attr)); + derivedAttributes = getInTopologicalOrder(unorderedAttributes.values(), attribute -> { + String input = attribute.getInputBeaconField(); + if (unorderedAttributes.containsKey(input)) { + return Collections.singletonList(unorderedAttributes.get(input)); } else { return Collections.emptyList(); } @@ -42,24 +42,24 @@ public RegexReplacementBeaconProcessor(EumServerConfiguration config) { @Override public Beacon process(Beacon beacon) { - Map newTags = new HashMap<>(); + Map newAttributes = new HashMap<>(); - for (RegexDerivedTag derivedTag : derivedTags) { - String input = newTags.get(derivedTag.getInputBeaconField()); + for (RegexDerivedAttribute derivedAttribute : derivedAttributes) { + String input = newAttributes.get(derivedAttribute.getInputBeaconField()); if (input == null) { - input = beacon.get(derivedTag.getInputBeaconField()); + input = beacon.get(derivedAttribute.getInputBeaconField()); } - String tagValue = deriveTag(derivedTag, input); + String tagValue = deriveAttribute(derivedAttribute, input); if (tagValue != null) { - newTags.put(derivedTag.getTagName(), tagValue); + newAttributes.put(derivedAttribute.getAttributeName(), tagValue); } } - return beacon.merge(newTags); + return beacon.merge(newAttributes); } - private String deriveTag(RegexDerivedTag tag, String input) { + private String deriveAttribute(RegexDerivedAttribute tag, String input) { String value = input; if (value == null) { if (tag.isNullAsEmpty()) { @@ -102,7 +102,7 @@ private String applyReplacement(PatternAndReplacement replacement, String value) /** * Returns the given elements as a sorted list, - * ensuring that each element appears after it's dependencies. + * ensuring that each element appears after its dependencies. * * @param elements the elements to sort * @param getDependencies a function given an element returning its dependencies @@ -135,12 +135,12 @@ private void addInTopologicalOrder(T current, Function> get @Value @Builder - private static class RegexDerivedTag { + private static class RegexDerivedAttribute { /** - * The name of the resulting tag / beacon field under which the result is stored + * The name of the resulting attribute / beacon field under which the result is stored */ - String tagName; + String attributeName; /** * The input beacon field. @@ -154,9 +154,9 @@ private static class RegexDerivedTag { List replacements; - private static RegexDerivedTag fromSettings(String tagName, BeaconTagSettings settings) { - return RegexDerivedTag.builder() - .tagName(tagName) + private static RegexDerivedAttribute fromSettings(String attributeName, BeaconAttributeSettings settings) { + return RegexDerivedAttribute.builder() + .attributeName(attributeName) .inputBeaconField(settings.getInput()) .nullAsEmpty(settings.isNullAsEmpty()) .replacements(settings.getAllReplacements()) diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/recorder/BeaconRecorder.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/BeaconRecorder.java similarity index 54% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/recorder/BeaconRecorder.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/BeaconRecorder.java index 30702eb..06cf793 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/recorder/BeaconRecorder.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/BeaconRecorder.java @@ -1,6 +1,6 @@ -package rocks.inspectit.oce.eum.server.beacon.recorder; +package rocks.inspectit.ocelot.eum.server.beacon.recorder; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; /** * Interface for all components acting as {@link BeaconRecorder}. @@ -16,4 +16,12 @@ public interface BeaconRecorder { */ void record(Beacon beacon); + /** + * Checks, if the recorder is able to record the metric. A recorder should support only one metric. + * + * @param metricName the metric name + * + * @return true, if the recorder can record the metric + */ + boolean canRecord(String metricName); } diff --git a/src/main/java/rocks/inspectit/oce/eum/server/beacon/recorder/ResourceTimingBeaconRecorder.java b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/ResourceTimingBeaconRecorder.java similarity index 80% rename from src/main/java/rocks/inspectit/oce/eum/server/beacon/recorder/ResourceTimingBeaconRecorder.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/ResourceTimingBeaconRecorder.java index 16baf9c..1ceffd7 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/beacon/recorder/ResourceTimingBeaconRecorder.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/ResourceTimingBeaconRecorder.java @@ -1,8 +1,8 @@ -package rocks.inspectit.oce.eum.server.beacon.recorder; +package rocks.inspectit.ocelot.eum.server.beacon.recorder; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.opencensus.common.Scope; +import io.opentelemetry.context.Scope; import lombok.Builder; import lombok.RequiredArgsConstructor; import lombok.Value; @@ -15,11 +15,11 @@ import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.MetricDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.ViewDefinitionSettings; -import rocks.inspectit.oce.eum.server.metrics.MeasuresAndViewsManager; +import rocks.inspectit.ocelot.eum.server.arithmetic.RawExpression; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.metrics.InstrumentManager; import jakarta.annotation.PostConstruct; import java.io.IOException; @@ -30,15 +30,15 @@ /** * This {@link BeaconRecorder} processes plain resource timing entry from the {@link Beacon} and exposes metric that: *

    - *
  • reports number of resources loaded sliced by type, cross-origin and cached tags
  • + *
  • reports number of resources loaded sliced by type, cross-origin and cached attributes
  • *
*

* The impl depends heavily on the Boomerang compression of the resource timing entries in the beacon. Please read - * ResourceTiming + * ResourceTiming * Boomerang documentation first. */ @Component -@ConditionalOnProperty(value = "inspectit-eum-server.resource-timing.enabled", havingValue = "true") +@ConditionalOnProperty(value = "inspectit-eum-server.definitions.resource_time.enabled", havingValue = "true") @RequiredArgsConstructor @Slf4j public class ResourceTimingBeaconRecorder implements BeaconRecorder { @@ -50,10 +50,10 @@ public class ResourceTimingBeaconRecorder implements BeaconRecorder { private final ObjectMapper objectMapper; /** - * {@link MeasuresAndViewsManager} for exposing metrics. + * {@link InstrumentManager} for exposing metrics */ @Autowired - private final MeasuresAndViewsManager measuresAndViewsManager; + private final InstrumentManager instrumentManager; @Autowired private final EumServerConfiguration configuration; @@ -64,38 +64,17 @@ public class ResourceTimingBeaconRecorder implements BeaconRecorder { public final String RESOURCE_TIME_METRIC_NAME = "resource_time"; /** - * Metric definition for the resource timing metric. + * The raw expression to extract resource timing data from the beacon. + * Default: {@code {restiming}} */ - private MetricDefinitionSettings RESOURCE_TIME; + private RawExpression resourceTimeExpression = new RawExpression("{restiming}"); - /** - * Init metric(s). - */ @PostConstruct - public void initMetric() { - Map tags = new HashMap<>(); - if (configuration.getResourceTiming().getTags() != null) { - tags.putAll(configuration.getResourceTiming().getTags()); - } - tags.put("initiatorType", true); - tags.put("cached", true); - tags.put("crossOrigin", true); - - RESOURCE_TIME = MetricDefinitionSettings.builder() - .type(MetricDefinitionSettings.MeasureType.DOUBLE) - .description("Response end time of the resource loading") - .unit("ms") - .view(RESOURCE_TIME_METRIC_NAME + "/SUM", ViewDefinitionSettings.builder() - .tags(tags) - .aggregation(ViewDefinitionSettings.Aggregation.SUM) - .build()) - .view(RESOURCE_TIME_METRIC_NAME + "/COUNT", ViewDefinitionSettings.builder() - .tags(tags) - .aggregation(ViewDefinitionSettings.Aggregation.COUNT) - .build()) - .build(); - - measuresAndViewsManager.updateMetrics(RESOURCE_TIME_METRIC_NAME, RESOURCE_TIME); + void setUp() { + String expression = configuration.getDefinitions() + .get(RESOURCE_TIME_METRIC_NAME) + .getValueExpression(); + resourceTimeExpression = new RawExpression(expression); } /** @@ -109,30 +88,36 @@ public void record(Beacon beacon) { // this is the URL where the resources have been loaded String url = beacon.get("u"); - String resourceTimings = beacon.get("restiming"); + String resourceTimings = beacon.get(resourceTimeExpression.getFields().getFirst()); if (resourceTimings != null) { decodeResourceTimings(resourceTimings).forEach(rs -> this.record(rs, url)); } } + @Override + public boolean canRecord(String metricName) { + return RESOURCE_TIME_METRIC_NAME.equals(metricName); + } + /** * Records one {@link ResourceTimingEntry} to the exposed metric(s). * * @param resourceTimingEntry entry - * @param url URL of the page where the resource has been loaded from. + * @param url URL of the page where the resource has been loaded from */ private void record(ResourceTimingEntry resourceTimingEntry, String url) { - Map extra = new HashMap<>(); + MetricDefinitionSettings metricDefinition = configuration.getDefinitions().get(RESOURCE_TIME_METRIC_NAME); + Map attributes = new HashMap<>(); boolean sameOrigin = isSameOrigin(url, resourceTimingEntry.url); - extra.put("crossOrigin", String.valueOf(!sameOrigin)); - extra.put("initiatorType", resourceTimingEntry.getInitiatorType().toString()); + attributes.put("crossOrigin", String.valueOf(!sameOrigin)); + attributes.put("initiatorType", resourceTimingEntry.getInitiatorType().toString()); if (sameOrigin) { - extra.put("cached", String.valueOf(resourceTimingEntry.isCached(true))); + attributes.put("cached", String.valueOf(resourceTimingEntry.isCached(true))); } - try (Scope scope = measuresAndViewsManager.getTagContext(extra).buildScoped()) { + try (Scope scope = instrumentManager.getBaggage(attributes).makeCurrent()) { Optional responseEnd = resourceTimingEntry.getResponseEnd(); - measuresAndViewsManager.recordMeasure("resource_time", RESOURCE_TIME, responseEnd.orElse(0)); + instrumentManager.recordMetric(RESOURCE_TIME_METRIC_NAME, metricDefinition, responseEnd.orElse(0)); } } @@ -148,7 +133,7 @@ private Stream decodeResourceTimings(String resourceTiming) try { rootNode = objectMapper.readTree(resourceTiming); } catch (IOException e) { - log.error("Error converting resource timing json to tree.", e); + log.error("Error converting resource timing json to tree", e); return Stream.empty(); } diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/InspectitConfigConversionService.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/conversion/InspectitConfigConversionService.java similarity index 74% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/InspectitConfigConversionService.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/conversion/InspectitConfigConversionService.java index 96fbe84..5869374 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/InspectitConfigConversionService.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/conversion/InspectitConfigConversionService.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.conversion; +package rocks.inspectit.ocelot.eum.server.configuration.conversion; import org.springframework.boot.convert.ApplicationConversionService; @@ -15,8 +15,6 @@ public static InspectitConfigConversionService getInstance() { private InspectitConfigConversionService() { super(); - addConverter(new BooleanToExporterEnabledStateConverter()); - addConverter(new StringToExporterEnabledStateConverter()); addConverter(new StringToTransportProtocolConverter()); } } diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/StringToTransportProtocolConverter.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/conversion/StringToTransportProtocolConverter.java similarity index 79% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/StringToTransportProtocolConverter.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/conversion/StringToTransportProtocolConverter.java index cf78676..1995f61 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/conversion/StringToTransportProtocolConverter.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/conversion/StringToTransportProtocolConverter.java @@ -1,8 +1,8 @@ -package rocks.inspectit.oce.eum.server.configuration.conversion; +package rocks.inspectit.ocelot.eum.server.configuration.conversion; import org.springframework.core.convert.converter.Converter; import org.springframework.util.StringUtils; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; /** * A {@link Converter} to convert from the String-representation to a {@link TransportProtocol}. diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/BeanConfiguration.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/BeanConfiguration.java similarity index 63% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/BeanConfiguration.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/BeanConfiguration.java index 1c7db7e..d6cd9e0 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/BeanConfiguration.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/BeanConfiguration.java @@ -1,14 +1,11 @@ -package rocks.inspectit.oce.eum.server.configuration.model; +package rocks.inspectit.ocelot.eum.server.configuration.model; -import io.opencensus.stats.Stats; -import io.opencensus.stats.StatsRecorder; -import io.opencensus.stats.ViewManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.ConversionService; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import rocks.inspectit.oce.eum.server.configuration.conversion.InspectitConfigConversionService; +import rocks.inspectit.ocelot.eum.server.configuration.conversion.InspectitConfigConversionService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -19,22 +16,6 @@ @Configuration public class BeanConfiguration { - /** - * Instance of the OpenCensus {@link StatsRecorder} which should be used. - */ - @Bean - public StatsRecorder statsRecorder() { - return Stats.getStatsRecorder(); - } - - /** - * Instance of the OpenCensus {@link ViewManager} which should be used. - */ - @Bean - public ViewManager viewManager() { - return Stats.getViewManager(); - } - /** * Scheduled Executor service to be used by components for asynchronous tasks. */ diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/CompressionMethod.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/CompressionMethod.java similarity index 76% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/CompressionMethod.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/CompressionMethod.java index 8555a69..66a1a38 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/CompressionMethod.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/CompressionMethod.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model; +package rocks.inspectit.ocelot.eum.server.configuration.model; /** * The compression method used in OTLP exporters. diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/EumServerConfiguration.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/EumServerConfiguration.java similarity index 58% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/EumServerConfiguration.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/EumServerConfiguration.java index 5136dab..c902e22 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/EumServerConfiguration.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/EumServerConfiguration.java @@ -1,14 +1,14 @@ -package rocks.inspectit.oce.eum.server.configuration.model; +package rocks.inspectit.ocelot.eum.server.configuration.model; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExportersSettings; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.BeaconMetricDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.security.SecuritySettings; -import rocks.inspectit.oce.eum.server.configuration.model.selfmonitoring.SelfMonitoringSettings; -import rocks.inspectit.oce.eum.server.configuration.model.tags.TagsSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExportersSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.BeaconMetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.security.SecuritySettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.selfmonitoring.SelfMonitoringSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.AttributeSettings; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -17,7 +17,7 @@ import java.util.Map; /** - * The configuration of the EUM server. + * The configuration of the EUM server */ @ConfigurationProperties("inspectit-eum-server") @Component @@ -33,10 +33,10 @@ public class EumServerConfiguration { private Map<@NotBlank String, @NotNull @Valid BeaconMetricDefinitionSettings> definitions = Collections.emptyMap(); /** - * Map of tags + * Map of attributes */ @Valid - private TagsSettings tags; + private AttributeSettings attributes; /** * Self Monitoring @@ -51,12 +51,8 @@ public class EumServerConfiguration { private ExportersSettings exporters; /** - * The resource timing settings. + * The security settings for the REST API */ - @Valid - private ResourceTimingSettings resourceTiming; - @Valid private SecuritySettings security; - } diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/TagsSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/AttributeSettings.java similarity index 57% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/TagsSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/AttributeSettings.java index 4268d00..0ca5341 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/TagsSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/AttributeSettings.java @@ -1,38 +1,30 @@ -package rocks.inspectit.oce.eum.server.configuration.model.tags; +package rocks.inspectit.ocelot.eum.server.configuration.model.attributes; import lombok.Data; import lombok.NoArgsConstructor; -import rocks.inspectit.oce.eum.server.configuration.model.tags.providers.TagsProvidersSettings; -import rocks.inspectit.oce.eum.server.utils.IPUtils; +import rocks.inspectit.ocelot.eum.server.utils.IPUtils; -import jakarta.validation.Valid; import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.Pattern; import java.util.*; /** - * Holds an additional map of tags, which will be resolved based on the EUM beacon. + * Holds an additional map of attributes, which will be resolved based on the EUM beacon. */ @Data @NoArgsConstructor -public class TagsSettings { +public class AttributeSettings { /** - * Settings for available tags providers. - */ - @Valid - private TagsProvidersSettings providers; - - /** - * Map of arbitrary user defined tags. + * Map of arbitrary user defined attributes */ private Map extra = new HashMap<>(); private static final String IP_PATTERN = "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])($|(\\/[1-9]$|\\/[1-2][0-9]$|\\/3[0-2]$))"; /** - * List of tags, which are defined as global + * List of attributes, which are defined as global */ private final Set defineAsGlobal = new HashSet<>(); @@ -42,46 +34,47 @@ public class TagsSettings { private final Map> customIPMapping = new HashMap<>(); /** - * Tags which are derived using regex-replace operations. + * Attributes which are derived using regex-replace operations. * The keys are the names of the beacon fields under which the results of the given replacement operation will be stored. - * Tags via regexes can depend on each other, as long as no cyclic dependency is involved. + * Attributes via regexes can depend on each other, as long as no cyclic dependency is involved. */ - private Map beacon = new HashMap<>(); + private Map beacon = new HashMap<>(); /** * IPUtils */ private IPUtils ipUtils = new IPUtils(); - @AssertFalse(message = "All defined global tags should exist either in extra tags or beacon tags") - public boolean isGlobalTagMissing() { + @AssertFalse(message = "All defined global attributes should exist either in extra tags or beacon tags") + public boolean isGlobalAttributeMissing() { return defineAsGlobal.stream() - .anyMatch(globalTag -> - !(getExtra().containsKey(globalTag) - || getBeacon().containsKey(globalTag) + .anyMatch(globalAttribute -> + !(getExtra().containsKey(globalAttribute) + || getBeacon().containsKey(globalAttribute) ) ); } - @AssertTrue(message = "Each tag should only be defined once") - public boolean isCheckUniquenessOfTags() { - return getExtra().keySet().stream().allMatch(extraTag -> !beacon.containsKey(extraTag)); + @AssertTrue(message = "Each attribute should only be defined once") + public boolean isCheckUniquenessOfAttributes() { + return getExtra().keySet().stream().allMatch(extraAttribute -> !beacon.containsKey(extraAttribute)); } @AssertTrue(message = "The ip definitions between the different categories must not overlap") public boolean isCheckIpRangesDoNotOverlap() { return customIPMapping.values().stream() .allMatch(ipList -> ipList.stream() - .allMatch(adresse -> customIPMapping.values().stream() - .allMatch(listToCompare -> listToCompare == ipList || listToCompare.stream().noneMatch(adresseToCompare -> areOverlapping(adresse, adresseToCompare))))); + .allMatch(address -> customIPMapping.values().stream() + .allMatch(listToCompare -> listToCompare == ipList || + listToCompare.stream() + .noneMatch(addressToCompare -> areOverlapping(address, addressToCompare)) + ) + ) + ); } /** * Helper method, which compares two address entries. - * - * @param address1 - * @param address2 - * @return */ private boolean areOverlapping(String address1, String address2) { if (address1.contains("/") && address2.contains("/")) { diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/BeaconTagSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/BeaconAttributeSettings.java similarity index 92% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/BeaconTagSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/BeaconAttributeSettings.java index 5640b50..a328d2e 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/BeaconTagSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/BeaconAttributeSettings.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.tags; +package rocks.inspectit.ocelot.eum.server.configuration.model.attributes; import lombok.AllArgsConstructor; import lombok.Builder; @@ -11,15 +11,15 @@ import java.util.Collections; import java.util.List; -@Data -@NoArgsConstructor -@Builder -@AllArgsConstructor /** * Defines how a custom beacon field is derived using a RegEx replaceAll operation from an existing beacon field. * If no regex is specified, the provided input field will simply be copied. */ -public class BeaconTagSettings { +@Data +@NoArgsConstructor +@Builder +@AllArgsConstructor +public class BeaconAttributeSettings { /** * Deprecated in favor of {@link #replacements}. @@ -58,13 +58,13 @@ public class BeaconTagSettings { private String replacement = ""; /** - * Specify whether the input field should be considered as an empty string if it does not exists. + * Specify whether the input field should be considered as an empty string if it does not exist. */ @Builder.Default private boolean nullAsEmpty = false; /** - * After the tag value has been extracted, these replacements will be applied in order to the tag value. + * After the tag value has been extracted, these replacements will be applied in order to the attribute value. */ @Builder.Default private List replacements = Collections.emptyList(); diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/PatternAndReplacement.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/PatternAndReplacement.java similarity index 88% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/PatternAndReplacement.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/PatternAndReplacement.java index 9b736ad..45a7116 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/tags/PatternAndReplacement.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/attributes/PatternAndReplacement.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.tags; +package rocks.inspectit.ocelot.eum.server.configuration.model.attributes; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/ExporterEnabledState.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/ExporterEnabledState.java similarity index 94% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/ExporterEnabledState.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/ExporterEnabledState.java index 82cb355..cb36996 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/ExporterEnabledState.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/ExporterEnabledState.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters; /** * Enum to change behaviour of exporters. diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/ExportersSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/ExportersSettings.java similarity index 53% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/ExportersSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/ExportersSettings.java index fae9555..3452378 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/ExportersSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/ExportersSettings.java @@ -1,10 +1,10 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters; import lombok.Data; import org.springframework.validation.annotation.Validated; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon.BeaconExporterSettings; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.metrics.MetricsExportersSettings; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.trace.TraceExportersSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon.BeaconExporterSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.metrics.MetricsExportersSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.tracing.TraceExportersSettings; import jakarta.validation.Valid; @@ -31,4 +31,9 @@ public class ExportersSettings { */ @Valid private TraceExportersSettings tracing; + + /** + * The service name used for exporters + */ + private String serviceName; } diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/TransportProtocol.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/TransportProtocol.java similarity index 96% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/TransportProtocol.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/TransportProtocol.java index fc72190..bca96da 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/TransportProtocol.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/TransportProtocol.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters; import lombok.Getter; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/BeaconExporterSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/BeaconExporterSettings.java similarity index 80% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/BeaconExporterSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/BeaconExporterSettings.java index ba423d9..2a00711 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/BeaconExporterSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/BeaconExporterSettings.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon; import lombok.Data; import org.springframework.validation.annotation.Validated; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/BeaconHttpExporterSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/BeaconHttpExporterSettings.java similarity index 89% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/BeaconHttpExporterSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/BeaconHttpExporterSettings.java index d2993fe..898c3a3 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/BeaconHttpExporterSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/BeaconHttpExporterSettings.java @@ -1,8 +1,8 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon; import lombok.Data; import org.springframework.validation.annotation.Validated; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.Min; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/InitiatorType.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/InitiatorType.java similarity index 90% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/InitiatorType.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/InitiatorType.java index 462ff90..63adbe6 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/beacon/InitiatorType.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/beacon/InitiatorType.java @@ -1,6 +1,6 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; /** * Types of initiators for beacons. diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/MetricsExportersSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/MetricsExportersSettings.java similarity index 60% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/MetricsExportersSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/MetricsExportersSettings.java index f8b9597..2ff5087 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/MetricsExportersSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/MetricsExportersSettings.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.metrics; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters.metrics; import lombok.Data; import lombok.NoArgsConstructor; @@ -15,14 +15,6 @@ public class MetricsExportersSettings { @Valid private PrometheusExporterSettings prometheus; - @Valid - private InfluxExporterSettings influx; - @Valid private OtlpMetricsExporterSettings otlp; - - /** - * The service name. - */ - private String serviceName; } diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/OtlpMetricsExporterSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/OtlpMetricsExporterSettings.java similarity index 80% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/OtlpMetricsExporterSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/OtlpMetricsExporterSettings.java index b01f548..a401873 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/OtlpMetricsExporterSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/OtlpMetricsExporterSettings.java @@ -1,12 +1,12 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.metrics; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters.metrics; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.time.DurationMin; -import rocks.inspectit.oce.eum.server.configuration.model.CompressionMethod; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.configuration.model.CompressionMethod; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; import java.time.Duration; import java.util.Map; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/PrometheusExporterSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/PrometheusExporterSettings.java similarity index 74% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/PrometheusExporterSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/PrometheusExporterSettings.java index 1f95958..9543a09 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/metrics/PrometheusExporterSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/metrics/PrometheusExporterSettings.java @@ -1,15 +1,15 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.metrics; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters.metrics; import lombok.Data; import lombok.NoArgsConstructor; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; /** - * Settings for the OpenCensus Prometheus metrics exporter. + * Settings for the Prometheus metrics exporter. */ @Data @NoArgsConstructor diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/trace/OtlpTraceExporterSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/tracing/OtlpTraceExporterSettings.java similarity index 66% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/trace/OtlpTraceExporterSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/tracing/OtlpTraceExporterSettings.java index 2746e72..5253d19 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/trace/OtlpTraceExporterSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/tracing/OtlpTraceExporterSettings.java @@ -1,17 +1,18 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.trace; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters.tracing; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.time.DurationMin; -import rocks.inspectit.oce.eum.server.configuration.model.CompressionMethod; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.configuration.model.CompressionMethod; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.exporters.tracing.TraceExportersConfiguration; import java.time.Duration; import java.util.Map; /** - * Settings for {@link rocks.inspectit.oce.eum.server.exporters.configuration.TraceExportersConfiguration} + * Settings for {@link TraceExportersConfiguration} */ @Data @NoArgsConstructor diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/trace/TraceExportersSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/tracing/TraceExportersSettings.java similarity index 60% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/trace/TraceExportersSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/tracing/TraceExportersSettings.java index b234d1e..271ce22 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/exporters/trace/TraceExportersSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/exporters/tracing/TraceExportersSettings.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.exporters.trace; +package rocks.inspectit.ocelot.eum.server.configuration.model.exporters.tracing; import lombok.Data; import org.springframework.validation.annotation.Validated; @@ -16,9 +16,4 @@ public class TraceExportersSettings { * Specifies whether client IP addresses which are added to spans should be masked. */ private boolean maskSpanIpAddresses; - - /** - * The service name. Used in {@link rocks.inspectit.oce.eum.server.exporters.configuration.TraceExportersConfiguration} - */ - private String serviceName; } diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/BeaconMetricDefinitionSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/BeaconMetricDefinitionSettings.java similarity index 66% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/BeaconMetricDefinitionSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/BeaconMetricDefinitionSettings.java index 6728a81..ca6b18c 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/BeaconMetricDefinitionSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/BeaconMetricDefinitionSettings.java @@ -1,21 +1,26 @@ -package rocks.inspectit.oce.eum.server.configuration.model.metric.definition; +package rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.InstrumentValueType; import lombok.*; -import rocks.inspectit.oce.eum.server.arithmetic.RawExpression; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.arithmetic.RawExpression; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import jakarta.validation.Valid; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import rocks.inspectit.ocelot.eum.server.arithmetic.ArithmeticExpression; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; + import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; /** - * Defines the mapping of a beacon value to a OpenCensus Measure and the corresponding views. + * Defines the mapping of a beacon value to a OpenTelemetry instrument and the corresponding views. */ @Data @EqualsAndHashCode(callSuper = true) @@ -25,7 +30,7 @@ public class BeaconMetricDefinitionSettings extends MetricDefinitionSettings { /** * The expression to extract a value from a beacon. - * See {@link rocks.inspectit.oce.eum.server.arithmetic.ArithmeticExpression} for more details. + * See {@link ArithmeticExpression} for more details. */ @NotEmpty private String valueExpression; @@ -38,10 +43,11 @@ public class BeaconMetricDefinitionSettings extends MetricDefinitionSettings { private List beaconRequirements; @Builder(builderMethodName = "beaconMetricBuilder") - public BeaconMetricDefinitionSettings(boolean enabled, @NotBlank String unit, @NotNull MeasureType type, String description, - Map<@NotBlank String, @Valid @NotNull ViewDefinitionSettings> views, @NotEmpty List beaconRequirements, - String valueExpression) { - super(enabled, unit, type, description, views); + public BeaconMetricDefinitionSettings(boolean enabled, @NotBlank String unit, @NotNull InstrumentType instrumentType, + @NotNull InstrumentValueType valueType, @NotNull String description, + Map<@NotBlank String, @Valid @NotNull ViewDefinitionSettings> views, + @NotEmpty List beaconRequirements, String valueExpression) { + super(enabled, unit, instrumentType, valueType, description, views); this.beaconRequirements = beaconRequirements; this.valueExpression = valueExpression; } @@ -55,7 +61,8 @@ public BeaconMetricDefinitionSettings getCopyWithDefaultsPopulated(String metric .valueExpression(getValueExpression()) .description(metricDefinition.getDescription()) .unit(metricDefinition.getUnit()) - .type(metricDefinition.getType()) + .instrumentType(metricDefinition.getInstrumentType()) + .valueType(metricDefinition.getValueType()) .enabled(metricDefinition.isEnabled()) .views(metricDefinition.getViews()) .build(); diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/BeaconRequirement.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/BeaconRequirement.java similarity index 93% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/BeaconRequirement.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/BeaconRequirement.java index 98a5f60..f00721c 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/metric/definition/BeaconRequirement.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/BeaconRequirement.java @@ -1,11 +1,11 @@ -package rocks.inspectit.oce.eum.server.configuration.model.metric.definition; +package rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon.InitiatorType; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon.InitiatorType; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/MetricDefinitionSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/MetricDefinitionSettings.java new file mode 100644 index 0000000..318d027 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/MetricDefinitionSettings.java @@ -0,0 +1,100 @@ +package rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition; + +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.InstrumentValueType; +import lombok.*; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.springframework.util.CollectionUtils; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.AggregationType; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; + +import java.util.HashMap; +import java.util.Map; + +/** + * Defines an OpenTelemetry metric in combination with one or multiple views + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +public class MetricDefinitionSettings { + + /** + * Defines if this metric is enabled. + * If this metric is disabled: + * - no views for it are created + */ + @Builder.Default + private boolean enabled = true; + + @NotBlank + private String unit; + + @NotNull + @Builder.Default + private InstrumentType instrumentType = InstrumentType.GAUGE; + + @NotNull + @Builder.Default + private InstrumentValueType valueType = InstrumentValueType.LONG; + + /** + * The description of the metric. + * If this is null, the description is simply the name of the metric. + */ + private String description; + + /** + * Maps view names to their definitions for the metric defined by this {@link MetricDefinitionSettings}. + */ + @Singular + private Map<@NotBlank String, @Valid @NotNull ViewDefinitionSettings> views; + + /** + * Copies the settings of this object but applies the defaults, like creating a default view if no views were defined. + * + * @param metricName the name of the metric + * + * @return a copy of this view definition with the default populated + */ + public MetricDefinitionSettings getCopyWithDefaultsPopulated(String metricName) { + val resultDescription = description == null ? metricName : description; + val result = toBuilder().description(resultDescription).clearViews(); + + if(!CollectionUtils.isEmpty(views)) { + views.forEach((name, def) -> result.view(name, def.getCopyWithDefaultsPopulated(resultDescription, unit))); + } + else { + /* + If there are no views specified, we will create a default view which only sets the default + OpenTelemetry aggregation for the particular instrument type. + Otherwise, we would not know about this view and could not set a proper AttributesProcessor + in the ViewManager to regulate, which attributes are allowed for the metric + */ + Map defaultView = new HashMap<>(); + val builder = ViewDefinitionSettings.builder(); + switch (instrumentType) { + case GAUGE: + ViewDefinitionSettings gaugeView = builder.aggregation(AggregationType.LAST_VALUE).build(); + defaultView.put(metricName, gaugeView); + break; + case COUNTER: + case UP_DOWN_COUNTER: + ViewDefinitionSettings counterView = builder.aggregation(AggregationType.SUM).build(); + defaultView.put(metricName, counterView); + break; + case HISTOGRAM: + ViewDefinitionSettings histogramView = builder.aggregation(AggregationType.HISTOGRAM).build(); + defaultView.put(metricName, histogramView); + break; + } + result.views(defaultView); + } + + return result.build(); + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/view/AggregationType.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/view/AggregationType.java new file mode 100644 index 0000000..5edddb7 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/view/AggregationType.java @@ -0,0 +1,35 @@ +package rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +public enum AggregationType { + + LAST_VALUE("Last value"), + SUM("Sum"), + HISTOGRAM("Explicit histogram"), + EXPONENTIAL_HISTOGRAM("Exponential histogram"), + + // Calculate percentiles with raw values + QUANTILES("Quantiles"), + // Dropping lower or higher values for average + SMOOTHED_AVERAGE("Smoothed average"); + + @Getter + private final String readableName; + + /** + * @return true, if this is a OpenTelemetry aggregation + */ + public boolean isOpenTelemetryAggregation() { + return this.equals(LAST_VALUE) || this.equals(SUM) || this.equals(HISTOGRAM) || this.equals(EXPONENTIAL_HISTOGRAM); + } + + /** + * @return true, if this is a custom time-window aggregation + */ + public boolean isTimeWindowAggregation() { + return this.equals(QUANTILES) || this.equals(SMOOTHED_AVERAGE); + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/view/ViewDefinitionSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/view/ViewDefinitionSettings.java new file mode 100644 index 0000000..9fb1919 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/metrics/definition/view/ViewDefinitionSettings.java @@ -0,0 +1,168 @@ +package rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view; + +import io.opentelemetry.sdk.metrics.internal.view.Base2ExponentialHistogramAggregation; +import lombok.*; +import org.hibernate.validator.constraints.time.DurationMin; +import org.springframework.util.CollectionUtils; + +import jakarta.validation.constraints.*; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static io.opentelemetry.sdk.metrics.internal.aggregator.ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES; + +/** + * Defines a single OpenTelemetry view for a metric. + * The name of the view is defined through the key in the map {@link MetricDefinitionSettings#getViews()}. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder(toBuilder = true) +public class ViewDefinitionSettings { + + @Builder.Default + private boolean enabled = true; + + /** + * Description of the view. + * If this is null a description is generated based on the name of the instrument, the unit and the aggregation + */ + private String description; + + /** + * The aggregation to use for this view + */ + @NotNull + @Builder.Default + private AggregationType aggregation = AggregationType.LAST_VALUE; + + /** + * The maximum amount of unique combinations of attributes for this view. + * OpenTelemetry uses a default value of 2000 + */ + @Builder.Default + private Integer cardinalityLimit = 2000; + + /** + * Only relevant if aggregation is "HISTOGRAM". + * In this case this list defines the boundaries of the buckets in the histogram + */ + @Builder.Default + private List<@NotNull Double> bucketBoundaries = DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES; + + /** + * Only relevant if aggregation is "EXPONENTIAL_HISTOGRAM". + * In this case this defines the max number of positive buckets and negative buckets + * (max total buckets is 2 * maxBuckets + 1 zero bucket). + * Default from {@link Base2ExponentialHistogramAggregation}. + */ + @Builder.Default + private Integer maxBuckets = 160; + + /** + * Only relevant if aggregation is "EXPONENTIAL_HISTOGRAM". + * In this case this defines the maximum and initial scale. + * Default from {@link Base2ExponentialHistogramAggregation}. + */ + @Builder.Default + private Integer maxScale = 20; + + /** + * In case the view is a quantile view, this list defines which percentiles shall be captured. + * 0 corresponds to the minimum, 1 to the maximum + */ + @Builder.Default + private Set<@NotNull Double> quantiles = Set.of(0.0, 0.5, 0.9, 0.95, 0.99, 1.0); + + /** + * In case the view is a smoothed_average, this value (in percentage in the range (0,1)) defines, + * how many metrics in the upper range shall be dropped + */ + @DecimalMax("1.0") + @DecimalMin("0.0") + @Builder.Default + private Double dropUpper = 0.0; + + /** + * In case the view is a smoothed_average, this value (in percentage in the range (0,1)) defines, + * how many metrics in the lower range shall be dropped + */ + @DecimalMax("1.0") + @DecimalMin("0.0") + @Builder.Default + private Double dropLower = 0.0; + + /** + * The time window to use for windowed metrics. + * Can be null, in this case the default provided via {@link #getCopyWithDefaultsPopulated(String, String)} + * is used. + */ + @DurationMin(millis = 1L) + @Builder.Default + private Duration timeWindow = Duration.ofSeconds(15); + + /** + * The maximum number of points to be buffered by this View. + * Currently only relevant if the aggregation is QUANTILES or SMOOTHED_AVERAGE. + *

+ * If this number is exceeded, a warning will be printed and points will be rejected until space is free again. + */ + @Min(1) + @Builder.Default + private int maxBufferedPoints = 16384; + + /** + * Defines if this view should by default include all common attributes. + * Individual attributes can still be disabled via {@link #attributes}. + */ + @Builder.Default + private boolean withCommonAttributes = true; + + /** + * Specifies which attributes should be used for this view. + */ + @Singular + private Map<@NotBlank String, @NotNull Boolean> attributes; + + public ViewDefinitionSettings getCopyWithDefaultsPopulated(String metricDescription, String unit) { + val result = toBuilder(); + if (description == null) { + result.description(aggregation.getReadableName() + " of " + metricDescription + " [" + unit + "]"); + } + return result.build(); + } + + @AssertFalse(message = "When using QUANTILES aggregation you must specify the quantiles to use!") + boolean isQuantilesNotSpecifiedForQuantileType() { + return enabled && aggregation == AggregationType.QUANTILES && CollectionUtils.isEmpty(quantiles); + } + + @AssertFalse(message = "When using HISTOGRAM aggregation you must specify the bucket-boundaries!") + boolean isBucketBoundariesNotSpecifiedForHistogram() { + return enabled && aggregation == AggregationType.HISTOGRAM && CollectionUtils.isEmpty(bucketBoundaries); + } + + @AssertTrue(message = "When using HISTOGRAM the specified bucket-boundaries must be sorted in ascending order and must contain each value at most once!") + boolean isBucketBoundariesSorted() { + if (enabled && aggregation == AggregationType.HISTOGRAM && !CollectionUtils.isEmpty(bucketBoundaries)) { + Double previous = null; + for (double boundary : bucketBoundaries) { + if (previous != null && previous >= boundary) { + return false; + } + previous = boundary; + } + } + return true; + } + + @AssertTrue(message = "The quantiles must be in the range [0,1]") + boolean isQuantilesInRange() { + return !enabled || aggregation != AggregationType.QUANTILES || quantiles.stream().noneMatch(q -> q < 0 || q > 1); + } +} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/SecuritySettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/SecuritySettings.java similarity index 76% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/SecuritySettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/SecuritySettings.java index 617b003..28d6eab 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/SecuritySettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/SecuritySettings.java @@ -1,8 +1,8 @@ -package rocks.inspectit.oce.eum.server.configuration.model.security; +package rocks.inspectit.ocelot.eum.server.configuration.model.security; import lombok.Data; import org.springframework.validation.annotation.Validated; -import rocks.inspectit.oce.eum.server.configuration.model.security.authProvider.AuthenticationProviderSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.security.authProvider.AuthenticationProviderSettings; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/authProvider/AuthenticationProviderSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/authProvider/AuthenticationProviderSettings.java similarity index 75% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/authProvider/AuthenticationProviderSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/authProvider/AuthenticationProviderSettings.java index 2b85a31..73ddb63 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/authProvider/AuthenticationProviderSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/authProvider/AuthenticationProviderSettings.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.security.authProvider; +package rocks.inspectit.ocelot.eum.server.configuration.model.security.authProvider; import lombok.Data; import org.springframework.validation.annotation.Validated; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/authProvider/SimpleApiTokenAuthenticationProviderSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/authProvider/SimpleApiTokenAuthenticationProviderSettings.java similarity index 81% rename from src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/authProvider/SimpleApiTokenAuthenticationProviderSettings.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/authProvider/SimpleApiTokenAuthenticationProviderSettings.java index 9db2ee7..979be33 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/security/authProvider/SimpleApiTokenAuthenticationProviderSettings.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/security/authProvider/SimpleApiTokenAuthenticationProviderSettings.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.configuration.model.security.authProvider; +package rocks.inspectit.ocelot.eum.server.configuration.model.security.authProvider; import lombok.Data; import org.hibernate.validator.constraints.time.DurationMin; @@ -6,6 +6,8 @@ import org.springframework.validation.annotation.Validated; import jakarta.validation.constraints.AssertTrue; +import rocks.inspectit.ocelot.eum.server.security.authprovider.SimpleApiTokenAuthenticationProvider; + import java.time.Duration; @Data @@ -13,7 +15,7 @@ public class SimpleApiTokenAuthenticationProviderSettings { /** - * Flag indicates if the {@link rocks.inspectit.oce.eum.server.security.authprovider.SimpleApiTokenAuthenticationProvider} should be enabled. + * Flag indicates if the {@link SimpleApiTokenAuthenticationProvider} should be enabled. */ private boolean enabled; diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/selfmonitoring/SelfMonitoringSettings.java b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/selfmonitoring/SelfMonitoringSettings.java new file mode 100644 index 0000000..a15eed6 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/configuration/model/selfmonitoring/SelfMonitoringSettings.java @@ -0,0 +1,66 @@ +package rocks.inspectit.ocelot.eum.server.configuration.model.selfmonitoring; + +import lombok.Data; +import lombok.Singular; +import org.springframework.validation.annotation.Validated; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Self-monitoring settings. + */ +@Data +@Validated +public class SelfMonitoringSettings { + + /** + * If self-monitoring is enabled. + */ + private boolean enabled; + + /** + * Definition of the self-monitoring metrics. + */ + @Singular + private Map<@NotBlank String, @Valid @NotNull MetricDefinitionSettings> metrics = Collections.emptyMap(); + + /** + * The prefix used for the self-monitoring metrics. + */ + private String metricPrefix; + + /** + * Marker to tell if the {@link #metricPrefix} has been applied to views + */ + private boolean prefixNotApplied = true; + + /** + * Adds the {@link #metricPrefix} for all view names. The prefix to the metrics has to be added manually. + * + * @return the metrics with prefixed views + */ + public Map getMetricsWithPrefixedViews() { + if (prefixNotApplied) { + metrics.forEach((metricName, metricDefinition) -> { + + Map prefixedViews = new LinkedHashMap<>(); + metricDefinition.getViews() + .forEach((viewName, viewDefinition) -> + prefixedViews.put(metricPrefix + viewName, viewDefinition) + ); + metricDefinition.setViews(prefixedViews); + + }); + prefixNotApplied = false; + } + return metrics; + } +} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporter.java b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporter.java similarity index 90% rename from src/main/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporter.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporter.java index 2394e9a..50b4243 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporter.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporter.java @@ -1,13 +1,13 @@ -package rocks.inspectit.oce.eum.server.exporters.beacon; +package rocks.inspectit.ocelot.eum.server.exporters.beacon; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon.BeaconHttpExporterSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon.BeaconHttpExporterSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; @@ -19,7 +19,7 @@ @Component @Slf4j @ConditionalOnProperty({"inspectit-eum-server.exporters.beacons.http.enabled"}) -@ConditionalOnExpression("NOT new String('${inspectit-eum-server.exporters.beacons.http.enabled}').toUpperCase().equals(T(rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState).DISABLED.toString())") +@ConditionalOnExpression("NOT new String('${inspectit-eum-server.exporters.beacons.http.enabled}').toUpperCase().equals(T(rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState).DISABLED.toString())") public class BeaconHttpExporter { /** diff --git a/src/main/java/rocks/inspectit/oce/eum/server/exporters/beacon/ExportWorkerFactory.java b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/ExportWorkerFactory.java similarity index 89% rename from src/main/java/rocks/inspectit/oce/eum/server/exporters/beacon/ExportWorkerFactory.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/ExportWorkerFactory.java index c4d8884..802baf1 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/exporters/beacon/ExportWorkerFactory.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/ExportWorkerFactory.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters.beacon; +package rocks.inspectit.ocelot.eum.server.exporters.beacon; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; @@ -13,10 +13,10 @@ import org.springframework.stereotype.Component; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon.BeaconHttpExporterSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.metrics.SelfMonitoringMetricManager; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon.BeaconHttpExporterSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.metrics.SelfMonitoringMetricManager; import jakarta.annotation.PostConstruct; import java.net.URI; @@ -30,7 +30,7 @@ @Slf4j @Component @ConditionalOnProperty({"inspectit-eum-server.exporters.beacons.http.enabled"}) -@ConditionalOnExpression("NOT new String('${inspectit-eum-server.exporters.beacons.http.enabled}').toUpperCase().equals(T(rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState).DISABLED.toString())") +@ConditionalOnExpression("NOT new String('${inspectit-eum-server.exporters.beacons.http.enabled}').toUpperCase().equals(T(rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState).DISABLED.toString())") public class ExportWorkerFactory { /** diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpMetricsExporterService.java b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpMetricsExporterService.java new file mode 100644 index 0000000..3afd53d --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpMetricsExporterService.java @@ -0,0 +1,139 @@ +package rocks.inspectit.ocelot.eum.server.exporters.metrics; + +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.export.*; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.metrics.OtlpMetricsExporterSettings; + +import jakarta.annotation.PreDestroy; +import jakarta.validation.Valid; +import rocks.inspectit.ocelot.eum.server.opentelemetry.OpenTelemetryController; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; + +/** + * Is enabled, if exporters.metrics.otlp.enabled is set to {@link ExporterEnabledState#ENABLED ENBLED} + * or {@link ExporterEnabledState IF_CONFIGURED}. + * The metric reader will be registered in OpenTelemetry via {@link OpenTelemetryController}. + */ +@Component +@Slf4j +public class OtlpMetricsExporterService { + + private final List SUPPORTED_PROTOCOLS = Arrays.asList(TransportProtocol.GRPC, TransportProtocol.HTTP_PROTOBUF); + + @Autowired + private EumServerConfiguration configuration; + + @Autowired + private ScheduledExecutorService executor; + + private MetricExporter metricExporter; + + private ScheduledFuture exporterTask; + + OtlpMetricsExporterSettings otlpMetricsExporterSettings; + + @Getter + private boolean enabled; + + @Bean + @ConditionalOnProperty({"inspectit-eum-server.exporters.metrics.otlp.enabled", "inspectit-eum-server.exporters.metrics.otlp.endpoint"}) + @ConditionalOnExpression("(NOT new String('${inspectit-eum-server.exporters.metrics.otlp.enabled}').toUpperCase().equals(T(rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState).DISABLED.toString())) AND (new String('${inspectit-eum-server.exporters.metrics.otlp.endpoint}').length() > 0)") + public MetricReader otlpService() { + checkProtocol(); + enabled = true; + otlpMetricsExporterSettings = configuration.getExporters().getMetrics().getOtlp(); + AggregationTemporalitySelector aggregationTemporalitySelector = otlpMetricsExporterSettings.getPreferredTemporality() == AggregationTemporality.DELTA ? AggregationTemporalitySelector.deltaPreferred() : AggregationTemporalitySelector.alwaysCumulative(); + + String endpoint = configuration.getExporters().getMetrics().getOtlp().getEndpoint(); + // OTEL expects that the URI starts with 'http://' or 'https://' + if (!endpoint.startsWith("http")) { + endpoint = String.format("http://%s", endpoint); + } + try { + switch (otlpMetricsExporterSettings.getProtocol()) { + case GRPC: { + OtlpGrpcMetricExporterBuilder metricExporterBuilder = OtlpGrpcMetricExporter.builder() + .setAggregationTemporalitySelector(aggregationTemporalitySelector) + .setEndpoint(endpoint) + .setCompression(otlpMetricsExporterSettings.getCompression().toString()) + .setTimeout(otlpMetricsExporterSettings.getTimeout()); + if (otlpMetricsExporterSettings.getHeaders() != null) { + for (Map.Entry headerEntry : otlpMetricsExporterSettings.getHeaders() + .entrySet()) { + metricExporterBuilder.addHeader(headerEntry.getKey(), headerEntry.getValue()); + } + } + metricExporter = metricExporterBuilder.build(); + break; + } + case HTTP_PROTOBUF: { + OtlpHttpMetricExporterBuilder metricExporterBuilder = OtlpHttpMetricExporter.builder() + .setAggregationTemporalitySelector(aggregationTemporalitySelector) + .setEndpoint(endpoint) + .setCompression(otlpMetricsExporterSettings.getCompression().toString()) + .setTimeout(otlpMetricsExporterSettings.getTimeout()); + if (otlpMetricsExporterSettings.getHeaders() != null) { + for (Map.Entry headerEntry : otlpMetricsExporterSettings.getHeaders() + .entrySet()) { + metricExporterBuilder.addHeader(headerEntry.getKey(), headerEntry.getValue()); + } + } + metricExporter = metricExporterBuilder.build(); + break; + } + } + + log.info("Starting OTLP metric exporter with {} on '{}'", otlpMetricsExporterSettings.getProtocol(), otlpMetricsExporterSettings.getEndpoint()); + return PeriodicMetricReader + .builder(metricExporter) + .setInterval(otlpMetricsExporterSettings.getExportInterval()) + .build(); + + } catch (Exception e) { + log.error("Error starting OTLP metric exporter", e); + throw e; + } + } + + private void checkProtocol() { + @Valid OtlpMetricsExporterSettings otlp = configuration.getExporters().getMetrics().getOtlp(); + if (!otlp.getEnabled().isDisabled()) { + if (!SUPPORTED_PROTOCOLS.contains(otlp.getProtocol())) { + throw new IllegalArgumentException("OTLP Metric Exporter is enabled, but wrong 'protocol' is specified. " + + "Supported values are " + Arrays.toString(SUPPORTED_PROTOCOLS.stream().map(TransportProtocol::getConfigRepresentation).toArray())); + } + } + } + + @PreDestroy + private void doDisable() { + enabled = false; + if (exporterTask != null) { + log.info("Stopping OTLP metric exporter"); + exporterTask.cancel(false); + } + if (metricExporter != null) { + metricExporter.flush(); + metricExporter.close(); + } + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/PrometheusExporterService.java b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/PrometheusExporterService.java new file mode 100644 index 0000000..0b2c623 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/PrometheusExporterService.java @@ -0,0 +1,60 @@ +package rocks.inspectit.ocelot.eum.server.exporters.metrics; + +import io.opentelemetry.exporter.prometheus.PrometheusHttpServer; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; + +import jakarta.annotation.PreDestroy; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.opentelemetry.OpenTelemetryController; + +/** + * Is enabled, if exporters.metrics.prometheus.enabled is set to {@link ExporterEnabledState#ENABLED ENBLED} + * or {@link ExporterEnabledState IF_CONFIGURED}. + * The metric reader will be registered in OpenTelemetry via {@link OpenTelemetryController}. + */ +@Component +@Slf4j +public class PrometheusExporterService { + + private PrometheusHttpServer httpServer; + + @Autowired + private EumServerConfiguration configuration; + + @Bean + @ConditionalOnProperty({"inspectit-eum-server.exporters.metrics.prometheus.enabled"}) + @ConditionalOnExpression("(NOT new String('${inspectit-eum-server.exporters.metrics.prometheus.enabled}').toUpperCase().equals(T(rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState).DISABLED.toString()))") + public MetricReader prometheusService() { + val config = configuration.getExporters().getMetrics().getPrometheus(); + try { + String host = config.getHost(); + int port = config.getPort(); + log.info("Starting Prometheus Exporter on {}:{}", host, port); + httpServer = PrometheusHttpServer.builder() + .setHost(host) + .setPort(port) + .build(); + + } catch (Exception e) { + log.error("Error Starting Prometheus HTTP Endpoint!", e); + } + return httpServer; + } + + @PreDestroy + protected boolean doDisable() { + if (httpServer != null) { + log.info("Stopping Prometheus Exporter"); + httpServer.close(); + } + return true; + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/DelegatingSpanExporter.java b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/DelegatingSpanExporter.java new file mode 100644 index 0000000..a5432fb --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/DelegatingSpanExporter.java @@ -0,0 +1,50 @@ +package rocks.inspectit.ocelot.eum.server.exporters.tracing; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import lombok.AllArgsConstructor; + +import java.util.Collection; + +/** + * Delegate class to insert additional logic to span exporters. + */ +@AllArgsConstructor +public class DelegatingSpanExporter implements SpanExporter { + + private final SpanExporter delegate; + + private final Attributes attributes; + + @Override + public CompletableResultCode export(Collection spans) { + Collection enrichedSpans = spans.stream() + .map(this::enrichSpanData) + .toList(); + return delegate.export(enrichedSpans); + } + + @Override + public CompletableResultCode flush() { + return delegate.flush(); + } + + @Override + public CompletableResultCode shutdown() { + return delegate.shutdown(); + } + + @Override + public void close() { + delegate.close(); + } + + /** + * Adds additional data to the original span. + */ + private SpanData enrichSpanData(SpanData span) { + return new EnrichedSpanData(span, attributes); + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/EnrichedSpanData.java b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/EnrichedSpanData.java new file mode 100644 index 0000000..ceb7e37 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/EnrichedSpanData.java @@ -0,0 +1,26 @@ +package rocks.inspectit.ocelot.eum.server.exporters.tracing; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.trace.data.DelegatingSpanData; +import io.opentelemetry.sdk.trace.data.SpanData; + +/** + * Wrapper class to add custom attributes - like resource attributes - to span data. + */ +public class EnrichedSpanData extends DelegatingSpanData { + + private final Attributes attributes; + + protected EnrichedSpanData(SpanData delegate, Attributes customAttributes) { + super(delegate); + this.attributes = Attributes.builder() + .putAll(delegate.getAttributes()) + .putAll(customAttributes) + .build(); + } + + @Override + public Attributes getAttributes() { + return attributes; + } +} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/exporters/configuration/TraceExportersConfiguration.java b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/TraceExportersConfiguration.java similarity index 79% rename from src/main/java/rocks/inspectit/oce/eum/server/exporters/configuration/TraceExportersConfiguration.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/TraceExportersConfiguration.java index 8b32e02..0df849d 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/exporters/configuration/TraceExportersConfiguration.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/TraceExportersConfiguration.java @@ -1,19 +1,21 @@ -package rocks.inspectit.oce.eum.server.exporters.configuration; +package rocks.inspectit.ocelot.eum.server.exporters.tracing; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import io.opentelemetry.sdk.trace.export.SpanExporter; -import io.opentelemetry.semconv.ServiceAttributes; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.trace.OtlpTraceExporterSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.tracing.OtlpTraceExporterSettings; +import rocks.inspectit.ocelot.eum.server.opentelemetry.OpenTelemetryController; +import rocks.inspectit.ocelot.eum.server.opentelemetry.resource.ResourceManager; import java.util.Map; @@ -24,9 +26,12 @@ public class TraceExportersConfiguration { @Autowired private EumServerConfiguration configuration; + @Autowired + private ResourceManager resourceManager; + @Bean(destroyMethod = "shutdown") @ConditionalOnProperty({"inspectit-eum-server.exporters.tracing.otlp.enabled", "inspectit-eum-server.exporters.tracing.otlp.endpoint"}) - @ConditionalOnExpression("(NOT new String('${inspectit-eum-server.exporters.tracing.otlp.enabled}').toUpperCase().equals(T(rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState).DISABLED.toString())) AND (new String('${inspectit-eum-server.exporters.tracing.otlp.endpoint}').length() > 0)") + @ConditionalOnExpression("(NOT new String('${inspectit-eum-server.exporters.tracing.otlp.enabled}').toUpperCase().equals(T(rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState).DISABLED.toString())) AND (new String('${inspectit-eum-server.exporters.tracing.otlp.endpoint}').length() > 0)") public SpanExporter otlpSpanExporter() { SpanExporter spanExporter = null; OtlpTraceExporterSettings otlpTraceExporterSettings = configuration.getExporters().getTracing().getOtlp(); @@ -66,11 +71,8 @@ public SpanExporter otlpSpanExporter() { } log.info("Starting OTLP span exporter on {} '{}'", otlpTraceExporterSettings.getProtocol() .getConfigRepresentation(), endpoint); - System.setProperty("otel.resource.attributes", ServiceAttributes.SERVICE_NAME.getKey() + "=" + configuration.getExporters() - .getTracing() - .getServiceName()); - return spanExporter; + Attributes resourceAttributes = resourceManager.getResource().getAttributes(); + return new DelegatingSpanExporter(spanExporter, resourceAttributes); } - } diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/AttributesRegistry.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/AttributesRegistry.java new file mode 100644 index 0000000..0dc6ab5 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/AttributesRegistry.java @@ -0,0 +1,58 @@ +package rocks.inspectit.ocelot.eum.server.metrics; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Stores attribute keys for various purposes. + */ +@Component +@Getter +public class AttributesRegistry { + + @Autowired + private EumServerConfiguration configuration; + + /** Set of all registered extra attribute keys */ + private final Set registeredExtraAttributes = new HashSet<>(); + + /** Set of all registered attribute keys */ + private final Set registeredAttributes = new HashSet<>(); + + /** + * Processes all attributes, which are exposed for the given view. + */ + public void processAttributeKeysForView(ViewDefinitionSettings viewSettings) { + Set attributes = getAttributeKeysForView(viewSettings); + registeredAttributes.addAll(attributes); + + Set extraAttributes = attributes.stream() + .filter(configuration.getAttributes().getExtra()::containsKey) + .collect(Collectors.toSet()); + registeredExtraAttributes.addAll(extraAttributes); + } + + /** + * @return the registered attribute keys for the provided view + */ + public Set getAttributeKeysForView(ViewDefinitionSettings viewSettings) { + Set attributes = viewSettings.getAttributes() + .entrySet() + .stream() + .filter(Map.Entry::getValue) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + + attributes.addAll(configuration.getAttributes().getDefineAsGlobal()); + + return attributes; + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/BeaconMetricManager.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/BeaconMetricManager.java new file mode 100644 index 0000000..5e01baa --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/BeaconMetricManager.java @@ -0,0 +1,199 @@ +package rocks.inspectit.ocelot.eum.server.metrics; + +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +import io.opentelemetry.context.Scope; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import rocks.inspectit.ocelot.eum.server.arithmetic.RawExpression; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.recorder.BeaconRecorder; +import rocks.inspectit.ocelot.eum.server.beacon.recorder.ResourceTimingBeaconRecorder; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.BeaconRequirement; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.BeaconAttributeSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.BeaconMetricDefinitionSettings; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Central component, which is responsible for writing beacon entries as OpenTelemetry metrics. + */ +@Component +@Slf4j +public class BeaconMetricManager { + + @Autowired + protected EumServerConfiguration configuration; + + @Autowired + private InstrumentManager instrumentManager; + + @Autowired + private AttributesRegistry attributesRegistry; + + @Autowired + private SelfMonitoringMetricManager selfMonitoringMetricManager; + + /** + * Currently just {@link ResourceTimingBeaconRecorder} + */ + @Autowired(required = false) + private List beaconRecorders; + + /** + * Set of all registered beacon attributes + */ + @VisibleForTesting + Set registeredBeaconAttributes = new HashSet<>(); + + /** + * Maps metric definitions to expressions + */ + private final Map expressionCache = new ConcurrentHashMap<>(); + + @PostConstruct + void initMetrics() { + Map definitions = configuration.getDefinitions(); + for (Map.Entry metricDefinitionEntry : definitions.entrySet()) { + String metricName = metricDefinitionEntry.getKey(); + BeaconMetricDefinitionSettings metricDefinition = metricDefinitionEntry.getValue(); + + log.debug("Registering beacon metric: {}", metricName); + instrumentManager.createInstrument(metricName, metricDefinition); + } + // Initialize self-monitoring metrics after beacon metrics + selfMonitoringMetricManager.initMetrics(); + // Register beacon attributes after all metrics are initialized + registerBeaconAttributes(); + log.info("Registration of metrics completed"); + } + + /** + * Processes boomerang beacon. + * + * @param beacon The beacon containing arbitrary key-value pairs. + * + * @return whether the beacon has been successfully parsed + */ + public boolean processBeacon(Beacon beacon) { + boolean successful = false; + + Map definitions = configuration.getDefinitions(); + if (CollectionUtils.isEmpty(definitions)) { + successful = true; + } else { + for (Map.Entry metricDefinitionEntry : definitions.entrySet()) { + String metricName = metricDefinitionEntry.getKey(); + BeaconMetricDefinitionSettings metricDefinition = metricDefinitionEntry.getValue(); + + if (BeaconRequirement.validate(beacon, metricDefinition.getBeaconRequirements())) { + recordMetric(metricName, metricDefinition, beacon); + successful = true; + } else { + log.debug("Skipping beacon because requirements are not fulfilled"); + } + } + } + + return successful; + } + + /** + * Registers all configured beacon attributes + */ + @VisibleForTesting + void registerBeaconAttributes() { + Map beaconAttributeSettings = configuration.getAttributes().getBeacon(); + + Set registeredAttributes = attributesRegistry.getRegisteredAttributes() + .stream() + .filter(beaconAttributeSettings::containsKey) + .collect(Collectors.toSet()); + + registeredBeaconAttributes.addAll(registeredAttributes); + } + + /** + * Records the metric via {@link BeaconRecorder} or directly. + * + * @param metricName the current metric name + * @param metricDefinition the current metric definition + * @param beacon the entire beacon + */ + private void recordMetric(String metricName, BeaconMetricDefinitionSettings metricDefinition, Beacon beacon) { + boolean recorded = recordWithBeaconRecorder(metricName, beacon); + + if(!recorded) recordBeaconMetric(metricName, metricDefinition, beacon); + } + + /** + * Tries to record the metric with a {@link BeaconRecorder}. Return true, if a fitting recorder was found. + * + * @param metricName the current metric name + * @param beacon the entire beacon + * + * @return true, if the metric was recorder via {@link BeaconRecorder} + */ + private boolean recordWithBeaconRecorder(String metricName, Beacon beacon) { + if (!CollectionUtils.isEmpty(beaconRecorders)) { + for (BeaconRecorder recorder : beaconRecorders) { + if (recorder.canRecord(metricName)) { + try (Scope scope = getBaggageForBeacon(beacon).makeCurrent()) { + recorder.record(beacon); + return true; + } + } + } + } + return false; + } + + /** + * Extracts the metric value from the given beacon according to the specified metric definition. + * In case the metric definition's value expression is not solvable using the given beacon (not all required + * fields are existing) nothing is done. + * + * @param metricName the metric name + * @param metricDefinition the metric's definition + * @param beacon the current beacon + */ + private void recordBeaconMetric(String metricName, BeaconMetricDefinitionSettings metricDefinition, Beacon beacon) { + RawExpression expression = expressionCache.computeIfAbsent(metricDefinition, definition -> new RawExpression(definition + .getValueExpression())); + + if (expression.isSolvable(beacon)) { + Number value = expression.solve(beacon); + + if (value != null) { + try (Scope scope = getBaggageForBeacon(beacon).makeCurrent()) { + instrumentManager.recordMetric(metricName, metricDefinition, value); + } + } + } + } + + /** + * Builds baggage for a given beacon. + * + * @param beacon Used to resolve baggage values, which refer to a beacon entry + * + * @return the baggage for the provided beacon + */ + private Baggage getBaggageForBeacon(Beacon beacon) { + Map attributes = new HashMap<>(); + for (String key : registeredBeaconAttributes) { + if (beacon.contains(key)) { + attributes.put(key, beacon.get(key)); + } + } + Baggage baggage = instrumentManager.getBaggage(attributes); + return baggage; + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentFactory.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentFactory.java new file mode 100644 index 0000000..a7ee702 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentFactory.java @@ -0,0 +1,83 @@ +package rocks.inspectit.ocelot.eum.server.metrics; + +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.opentelemetry.OpenTelemetryController; + +@Component +public class InstrumentFactory { + + @Autowired + private OpenTelemetryController openTelemetry; + + /** + * Creates a new instrument. Since {@code AbstractInstrument} is package-private, we return an {@code Object} + * + * @param name the instrument name + * @param metricDefinition the instrument settings + * + * @return the created instrument as {@code Object} + */ + public Object createInstrument(String name, MetricDefinitionSettings metricDefinition) { + return switch (metricDefinition.getInstrumentType()) { + case COUNTER -> createCounter(name, metricDefinition); + case UP_DOWN_COUNTER -> createUpDownCounter(name, metricDefinition); + case GAUGE -> createGauge(name, metricDefinition); + case HISTOGRAM -> createHistogram(name, metricDefinition); + default -> throw new IllegalArgumentException("Tried to create unsupported instrument type:" + metricDefinition.getInstrumentType().name()); + }; + } + + private Object createCounter(String name, MetricDefinitionSettings metricDefinition) { + LongCounterBuilder builder = openTelemetry.getMeter() + .counterBuilder(name) + .setDescription(metricDefinition.getDescription()) + .setUnit(metricDefinition.getUnit()); + + return switch (metricDefinition.getValueType()) { + case LONG -> builder.build(); + case DOUBLE -> builder.ofDoubles().build(); + }; + } + + private Object createUpDownCounter(String name, MetricDefinitionSettings metricDefinition) { + LongUpDownCounterBuilder builder = openTelemetry.getMeter() + .upDownCounterBuilder(name) + .setDescription(metricDefinition.getDescription()) + .setUnit(metricDefinition.getUnit()); + + return switch (metricDefinition.getValueType()) { + case LONG -> builder.build(); + case DOUBLE -> builder.ofDoubles().build(); + }; + } + + private Object createGauge(String name, MetricDefinitionSettings metricDefinition) { + DoubleGaugeBuilder builder = openTelemetry.getMeter() + .gaugeBuilder(name) + .setDescription(metricDefinition.getDescription()) + .setUnit(metricDefinition.getUnit()); + + return switch (metricDefinition.getValueType()) { + case LONG -> builder.ofLongs().build(); + case DOUBLE -> builder.build(); + }; + } + + private Object createHistogram(String name, MetricDefinitionSettings metricDefinition) { + DoubleHistogramBuilder builder = openTelemetry.getMeter() + .histogramBuilder(name) + .setDescription(metricDefinition.getDescription()) + .setUnit(metricDefinition.getUnit()); + + return switch (metricDefinition.getValueType()) { + case LONG -> builder.ofLongs().build(); + case DOUBLE -> builder.build(); + }; + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentManager.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentManager.java new file mode 100644 index 0000000..ad8ea92 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentManager.java @@ -0,0 +1,211 @@ +package rocks.inspectit.ocelot.eum.server.metrics; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.baggage.BaggageBuilder; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.*; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.worker.TimeWindowRecorder; +import rocks.inspectit.ocelot.eum.server.opentelemetry.metrics.ViewManager; +import rocks.inspectit.ocelot.eum.server.utils.AttributeUtil; + +import java.util.*; + +/** + * Central component, which is responsible for writing communication with the OpenTelemetry instruments. + * Views will be registered via {@link ViewManager}. + * Note: The EUM-server cannot update metric definitions during runtime. + */ +@Component +@Slf4j +public class InstrumentManager { + + @Autowired + private EumServerConfiguration configuration; + + @Autowired + private InstrumentFactory instrumentFactory; + + @Autowired + private AttributesRegistry attributesRegistry; + + @Autowired + private TimeWindowRecorder timeWindowRecorder; + + /** + * Created OpenTelemetry instruments referenced by their metric name. + * Since {@code AbstractInstrument} is package-private, we store instruments as {@code Object} + * and cast them to proper data types during recording. + */ + private final Map instruments = new HashMap<>(); + + /** + * Creates the instrument in {@link #instruments} if necessary. + * + * @param metricName the metric name + * @param metricDefinition the configuration for the metric + */ + public void createInstrument(String metricName, MetricDefinitionSettings metricDefinition) { + if (!isInstrumentRegistered(metricName)) { + MetricDefinitionSettings populatedMetricDefinition = metricDefinition.getCopyWithDefaultsPopulated(metricName); + + if (shouldCreateInstrument(metricDefinition)) { + Object instrument = instrumentFactory.createInstrument(metricName, populatedMetricDefinition); + instruments.put(metricName, instrument); + } + + val views = populatedMetricDefinition.getViews(); + if (!CollectionUtils.isEmpty(views)) { + views.values().forEach(view -> attributesRegistry.processAttributeKeysForView(view)); + } + } + } + + /** + * Records a value for the metric via OpenTelemetry {@link Meter} or/and via {@link TimeWindowRecorder}. + * + * @param metricName the name of the metric + * @param metricDefinition the configuration of the metric, which is activated + * @param value the value, which is going to be written + */ + public void recordMetric(String metricName, MetricDefinitionSettings metricDefinition, Number value) { + if (log.isDebugEnabled()) + log.debug("Recording metric '{}' with value '{}'", metricName, value); + + Baggage baggage = Baggage.current(); + Attributes attributes = AttributeUtil.toAttributes(baggage); + + if (isInstrumentRegistered(metricName)) { + switch (metricDefinition.getInstrumentType()) { + case COUNTER -> recordCounter(metricName, metricDefinition, value, attributes); + case UP_DOWN_COUNTER -> recordUpDownCounter(metricName, metricDefinition, value, attributes); + case GAUGE -> recordGauge(metricName, metricDefinition, value, attributes); + case HISTOGRAM -> recordHistogram(metricName, metricDefinition, value, attributes); + default -> throw new IllegalArgumentException("Tried to record unsupported instrument type: " + metricDefinition.getInstrumentType().name()); + } + } + + timeWindowRecorder.recordMetric(metricName, value.doubleValue(), baggage); + } + + private void recordCounter(String instrumentName, MetricDefinitionSettings metricDefinition, Number value, Attributes attributes) { + switch (metricDefinition.getValueType()) { + case LONG -> { + LongCounter counter = (LongCounter) instruments.get(instrumentName); + counter.add(value.longValue(), attributes); + } + case DOUBLE -> { + DoubleCounter counter = (DoubleCounter) instruments.get(instrumentName); + counter.add(value.doubleValue(), attributes); + } + } + } + + private void recordUpDownCounter(String instrumentName, MetricDefinitionSettings metricDefinition, Number value, Attributes attributes) { + switch (metricDefinition.getValueType()) { + case LONG -> { + LongUpDownCounter counter = (LongUpDownCounter) instruments.get(instrumentName); + counter.add(value.longValue(), attributes); + } + case DOUBLE -> { + DoubleUpDownCounter counter = (DoubleUpDownCounter) instruments.get(instrumentName); + counter.add(value.doubleValue(), attributes); + } + } + } + + private void recordGauge(String instrumentName, MetricDefinitionSettings metricDefinition, Number value, Attributes attributes) { + switch (metricDefinition.getValueType()) { + case LONG -> { + LongGauge gauge = (LongGauge) instruments.get(instrumentName); + gauge.set(value.longValue(), attributes); + } + case DOUBLE -> { + DoubleGauge gauge = (DoubleGauge) instruments.get(instrumentName); + gauge.set(value.doubleValue(), attributes); + } + } + } + + private void recordHistogram(String instrumentName, MetricDefinitionSettings metricDefinition, Number value, Attributes attributes) { + switch (metricDefinition.getValueType()) { + case LONG -> { + LongHistogram histogram = (LongHistogram) instruments.get(instrumentName); + histogram.record(value.longValue(), attributes); + } + case DOUBLE -> { + DoubleHistogram histogram = (DoubleHistogram) instruments.get(instrumentName); + histogram.record(value.doubleValue(), attributes); + } + } + } + + /** + * Checks, if we should create an instrument for the metric. We only need an instrument, if the metric definition + * contains a view, which uses an OpenTelemetry aggregation OR + * if there are no views specified at all. Then we will use the default OpenTelemetry views, + * which OpenTelemetry handles by itself automatically. + * For time-window aggregations we will record metrics via {@link TimeWindowRecorder} later. + * + * @param metricDefinition the metric definition + * + * @return true, if we should create an instrument + */ + private boolean shouldCreateInstrument(MetricDefinitionSettings metricDefinition) { + if (metricDefinition.isEnabled()) { + boolean useDefaultView = CollectionUtils.isEmpty(metricDefinition.getViews()); + return useDefaultView || metricDefinition.getViews() + .values().stream() + .anyMatch(view -> view.getAggregation().isOpenTelemetryAggregation()); + } + return false; + } + + /** + * @param metricName the name of the metric + * + * @return true, if an OpenTelemetry instrument was created for the metric + */ + private boolean isInstrumentRegistered(String metricName) { + return instruments.containsKey(metricName); + } + + /** + * Builds baggage with all custom attributes. + * + * @param customAttributes Map containing the custom attributes. + * + * @return {@link Baggage} which contains the custom and extra attributes + */ + public Baggage getBaggage(Map customAttributes) { + BaggageBuilder builder = getBaggage().toBuilder(); + + for (Map.Entry customAttribute : customAttributes.entrySet()) { + builder.put(customAttribute.getKey(), customAttribute.getValue()); + } + + return builder.build(); + } + + /** + * Builds baggage with extra attributes in case the view needs them. + */ + public Baggage getBaggage() { + BaggageBuilder builder = Baggage.current().toBuilder(); + Set registeredExtraAttributes = attributesRegistry.getRegisteredExtraAttributes(); + + for (String registeredExtraAttribute : registeredExtraAttributes) { + builder.put(registeredExtraAttribute, configuration + .getAttributes() + .getExtra() + .get(registeredExtraAttribute)); + } + return builder.build(); + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/SelfMonitoringMetricManager.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/SelfMonitoringMetricManager.java new file mode 100644 index 0000000..6b1d784 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/SelfMonitoringMetricManager.java @@ -0,0 +1,74 @@ +package rocks.inspectit.ocelot.eum.server.metrics; + +import io.opentelemetry.context.Scope; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.selfmonitoring.SelfMonitoringSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; + +import java.util.Collections; +import java.util.Map; + +/** + * Central component, which is responsible for recording self monitoring metrics. + */ +@Component +@Slf4j +public class SelfMonitoringMetricManager { + + @Autowired + private EumServerConfiguration configuration; + + @Autowired + private InstrumentManager instrumentManager; + + /** + * Initialize self-monitoring metrics + */ + public void initMetrics() { + SelfMonitoringSettings selfMonitoringSettings = configuration.getSelfMonitoring(); + for (Map.Entry metricEntry : selfMonitoringSettings.getMetricsWithPrefixedViews().entrySet()) { + String metricName = metricEntry.getKey(); + MetricDefinitionSettings metricDefinitionSettings = metricEntry.getValue(); + + String fullMetricName = selfMonitoringSettings.getMetricPrefix() + metricName; + log.info("Registering self-monitoring metric: {}", metricName); + + instrumentManager.createInstrument(fullMetricName, metricDefinitionSettings); + } + } + + /** + * Records a self-monitoring metric with the common attributes. + * Only records a metric if self monitoring is enabled. + * + * @param metricName the name of the metric, excluding the metrics prefix + * @param value the value to record + * @param customAttributes the custom attributes for the metric + */ + public void record(String metricName, Number value, Map customAttributes) { + SelfMonitoringSettings selfMonitoringSettings = configuration.getSelfMonitoring(); + if (selfMonitoringSettings.isEnabled() && selfMonitoringSettings.getMetrics().containsKey(metricName)) { + MetricDefinitionSettings metricDefinition = selfMonitoringSettings.getMetricsWithPrefixedViews().get(metricName); + + String fullMetricName = selfMonitoringSettings.getMetricPrefix() + metricName; + + try (Scope scope = instrumentManager.getBaggage(customAttributes).makeCurrent()) { + instrumentManager.recordMetric(fullMetricName, metricDefinition, value); + } + } + } + + /** + * Records a self-monitoring measurement with the common attributes. + * Only records a measurement if self monitoring is enabled. + * + * @param measureName the name of the measure, excluding the metrics prefix + * @param value the actual value + */ + public void record(String measureName, Number value) { + record(measureName, value, Collections.emptyMap()); + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/TimeWindowViewManager.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/TimeWindowViewManager.java new file mode 100644 index 0000000..ae30b13 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/TimeWindowViewManager.java @@ -0,0 +1,124 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow; + +import io.opentelemetry.api.baggage.Baggage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.metrics.AttributesRegistry; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.QuantilesView; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.SmoothedAverageView; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.TimeWindowView; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.worker.TimeWindowRecorder; + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +/** + * Allows the creation of time-window views on metrics. + * Note that these views DO NOT coexist with OpenTelemetry {@link io.opentelemetry.sdk.metrics.View}s. + * For this reason observations must be reported via {@link TimeWindowRecorder#recordMetric(String, double, Baggage)} + * instead of using OpenTelemetry instruments.
+ * Note: The EUM-server cannot update metric definitions during runtime. + */ +@Slf4j +@Component +public class TimeWindowViewManager { + + @Autowired + private EumServerConfiguration configuration; + + @Autowired + private AttributesRegistry attributesRegistry; + + /** + * Maps the name of measures to registered time-window views + */ + private final Map> measuresToViewsMap = new ConcurrentHashMap<>(); + + /** + * @return the collection of registered time-window views for all metrics + */ + public Collection getAllViews() { + return measuresToViewsMap.values().stream() + .flatMap(Collection::stream) + .toList(); + } + + /** + * @param metricName the metric name + * + * @return the collection of registered time-window vies for the provided metric + */ + public Collection getViews(String metricName) { + return measuresToViewsMap.get(metricName); + } + + /** + * @param metricName the name of the metric + * + * @return true, if any time-window view exists for the metric + */ + public boolean areAnyViewsRegistered(String metricName) { + return measuresToViewsMap.containsKey(metricName); + } + + /** + * Register the new custom time-window view in {@link #measuresToViewsMap} for the provided metric. + * + * @param metricName the metric name + * @param viewName the view name + * @param unit the metric unit + * @param settings the (already validated) view settings + */ + public synchronized void registerView(String metricName, String viewName, String unit, ViewDefinitionSettings settings) { + log.debug("Registering time-window view: {}", metricName); + + List views = measuresToViewsMap.computeIfAbsent(metricName, (name) -> new CopyOnWriteArrayList<>()); + + TimeWindowView view = switch (settings.getAggregation()) { + case SMOOTHED_AVERAGE -> registerSmoothedAverageView(viewName, unit, settings); + case QUANTILES -> registerQuantilesView(viewName, unit, settings); + default -> throw new IllegalArgumentException("Unknow time-window aggregation:" + settings.getAggregation()); + }; + + views.add(view); + } + + private TimeWindowView registerSmoothedAverageView(String viewName, String unit, ViewDefinitionSettings settings) { + String description = settings.getDescription(); + Set attributes = getAttributeKeysForView(settings); + Duration timeWindow = settings.getTimeWindow(); + int bufferLimit = settings.getMaxBufferedPoints(); + double dropUpper = settings.getDropUpper(); + double dropLower = settings.getDropLower(); + + return new SmoothedAverageView(viewName, description, unit, attributes, timeWindow, bufferLimit, dropUpper, dropLower); + } + + private TimeWindowView registerQuantilesView(String viewName, String unit, ViewDefinitionSettings settings) { + String description = settings.getDescription(); + Set attributes = getAttributeKeysForView(settings); + Duration timeWindow = settings.getTimeWindow(); + int bufferLimit = settings.getMaxBufferedPoints(); + Set quantiles = settings.getQuantiles(); + boolean includeMin = quantiles.contains(0.0); + boolean includeMax = quantiles.contains(1.0); + Set quantilesFiltered = quantiles.stream() + .filter(p -> p > 0 && p < 1) + .collect(Collectors.toSet()); + + return new QuantilesView(viewName, description, unit, attributes, timeWindow, bufferLimit, quantilesFiltered, includeMin, includeMax); + } + + /** + * @return the attributes which are exposed for the given view + */ + private Set getAttributeKeysForView(ViewDefinitionSettings settings) { + return attributesRegistry.getAttributeKeysForView(settings); + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/QuantilesView.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/QuantilesView.java new file mode 100644 index 0000000..a9355f7 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/QuantilesView.java @@ -0,0 +1,154 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.views; + +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.api.common.Attributes; +import lombok.Getter; +import org.apache.commons.math3.stat.descriptive.rank.Percentile; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.time.Duration; +import java.time.Instant; +import java.util.*; + +/** + * For the data within this window, percentiles and min / max values can be computed. + */ +public class QuantilesView extends TimeWindowView { + + /** + * The attribute to use for the quantile or "min","max" respectively. + */ + private static final String QUANTILE_ATTRIBUTE_KEY = "quantile"; + + /** + * The attribute value to use for {@link #QUANTILE_ATTRIBUTE_KEY} for the "minimum" series. + */ + private static final String MIN_METRIC_SUFFIX = "_min"; + + /** + * The attribute value to use for {@link #QUANTILE_ATTRIBUTE_KEY} for the "maximum" series. + */ + private static final String MAX_METRIC_SUFFIX = "_max"; + + /** + * The formatter used to print quantiles to attributes. + */ + private static final DecimalFormat QUANTILE_TAG_FORMATTER = new DecimalFormat("#.#####", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + + /** + * The descriptor of the metric for this view, if quantile. + */ + private MetricInfo quantileMetricInfo; + + /** + * If not null, the minimum value will be exposed as this gauge. + */ + private MetricInfo minMetricInfo; + + /** + * If not null, the maximum value will be exposed as this gauge. + */ + private MetricInfo maxMetricInfo; + + /** + * The quantiles to compute in the range (0,1) + */ + @Getter + private final Set quantiles; + + /** + * @param viewName the prefix to use for the names of all exposed metrics + * @param description the description of this view + * @param unit the unit of the measure + * @param attributes the attribute keys to use for this view + * @param timeWindow the time range to use for computing minimum / maximum and quantile values + * @param bufferLimit the maximum number of measurements to be buffered by this view + * @param quantiles the set of quantiles in the range (0,1) which shall be provided as metrics + * @param includeMax true, if the maximum value should be exposed as metric + * @param includeMin true, if the minimum value should be exposed as metric + */ + public QuantilesView(String viewName, String description, String unit, Set attributes, Duration timeWindow, int bufferLimit, + Set quantiles, boolean includeMax, boolean includeMin) { + super(viewName, description, unit, attributes, timeWindow, bufferLimit); + validateConfiguration(includeMin, includeMax, quantiles); + + this.quantiles = new HashSet<>(quantiles); + + if (!quantiles.isEmpty()) { + this.quantileMetricInfo = new MetricInfo(viewName, description, unit); + } + if (includeMin) { + this.minMetricInfo = new MetricInfo(viewName + MIN_METRIC_SUFFIX, description, unit); + } + if (includeMax) { + this.maxMetricInfo = new MetricInfo(viewName + MAX_METRIC_SUFFIX, description, unit); + } + } + + private void validateConfiguration(boolean includeMin, boolean includeMax, Set quantiles) { + quantiles.stream().filter(q -> q <= 0.0 || q >= 1.0).forEach(q -> { + throw new IllegalArgumentException("Quantiles must be in range (0,1)"); + }); + if (quantiles.isEmpty() && !includeMin && !includeMax) { + throw new IllegalArgumentException("You must specify at least one quantile or enable minimum or maximum computation!"); + } + } + + boolean isMinEnabled() { + return minMetricInfo != null; + } + + boolean isMaxEnabled() { + return maxMetricInfo != null; + } + + @VisibleForTesting + static String getQuantileAttribute(double quantile) { + return QUANTILE_TAG_FORMATTER.format(quantile); + } + + @Override + protected List getMetrics() { + List metrics = new ArrayList<>(); + if (isMinEnabled()) { + metrics.add(minMetricInfo); + } + if (isMaxEnabled()) { + metrics.add(maxMetricInfo); + } + if (!quantiles.isEmpty()) { + metrics.add(quantileMetricInfo); + } + return metrics; + } + + @Override + protected void computeSeries(ResultSeriesCollector resultSeries, Instant time, Attributes attributes, double[] data) { + if (isMinEnabled() || isMaxEnabled()) { + double minValue = Double.MAX_VALUE; + double maxValue = -Double.MAX_VALUE; + for (double value : data) { + minValue = Math.min(minValue, value); + maxValue = Math.max(maxValue, value); + } + if (isMinEnabled()) { + resultSeries.add(minMetricInfo, time, attributes, minValue); + } + if (isMaxEnabled()) { + resultSeries.add(maxMetricInfo, time, attributes, maxValue); + } + } + if (!quantiles.isEmpty()) { + Percentile percentileComputer = new Percentile(); + percentileComputer.setData(data); + for (double quantile : quantiles) { + double percentileValue = percentileComputer.evaluate(quantile * 100); + Attributes attributesWithQuantile = attributes.toBuilder() + .put(QUANTILE_ATTRIBUTE_KEY, getQuantileAttribute(quantile)) + .build(); + resultSeries.add(quantileMetricInfo, time, attributesWithQuantile, percentileValue); + } + } + } +} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/SmoothedAverageView.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/SmoothedAverageView.java similarity index 51% rename from src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/SmoothedAverageView.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/SmoothedAverageView.java index 67d05a8..e31ab9f 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/SmoothedAverageView.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/SmoothedAverageView.java @@ -1,15 +1,13 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.views; -import io.opencensus.common.Timestamp; -import io.opencensus.metrics.LabelKey; -import io.opencensus.metrics.export.MetricDescriptor; +import io.opentelemetry.api.common.Attributes; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import java.time.Duration; +import java.time.Instant; import java.util.*; -import static java.lang.Math.round; - /** * For the data within this window, smoothed averages can be computed. */ @@ -17,37 +15,35 @@ public class SmoothedAverageView extends TimeWindowView { /** - * The descriptor of the metric for this view, if smoothed average. + * The metric information for this view, if smoothed average */ - private MetricDescriptor metricDescriptor; + private final MetricInfo metricInfo; @Getter - private double dropUpper; + private final double dropUpper; @Getter - private double dropLower; + private final double dropLower; /** - * Constructor. - * - * @param dropUpper value in percentage in the range (0,1) which indicates how many metrics in the upper range shall be dropped - * @param dropLower value in percentage in the range (0,1) which indicates how many metrics in the lower range shall be dropped - * @param tags the tags to use for this view - * @param timeWindowMillis the time range in milliseconds to use for computing minimum / maximum and percentile values * @param viewName the prefix to use for the names of all exposed metrics - * @param unit the unit of the measure * @param description the description of this view - * @param bufferLimit the maximum number of measurements to be buffered by this view + * @param unit the unit of the metric + * @param attributes the attribute keys to use for this view + * @param timeWindow the time rang to use for computing smoothed average values + * @param bufferLimit the maximum number of data-points to be buffered by this view + * @param dropUpper value in percentage in the range (0,1) which indicates how many metrics in the upper range shall be dropped + * @param dropLower value in percentage in the range (0,1) which indicates how many metrics in the lower range shall be dropped */ - SmoothedAverageView(double dropUpper, double dropLower, Set tags, long timeWindowMillis, String viewName, String unit, String description, int bufferLimit) { - super(tags, timeWindowMillis, viewName, unit, description, bufferLimit); + public SmoothedAverageView(String viewName, String description, String unit, Set attributes, + Duration timeWindow, int bufferLimit, double dropUpper, double dropLower) { + super(viewName, description, unit, attributes, timeWindow, bufferLimit); validateConfiguration(dropUpper, dropLower); this.dropUpper = dropUpper; this.dropLower = dropLower; - List smoothedAverageLabelKeys = getLabelKeysInOrder(); - metricDescriptor = MetricDescriptor.create(viewName, description, unit, MetricDescriptor.Type.GAUGE_DOUBLE, smoothedAverageLabelKeys); + this.metricInfo = new MetricInfo(viewName, description, unit); } private void validateConfiguration(double dropUpper, double dropLower) { @@ -60,19 +56,12 @@ private void validateConfiguration(double dropUpper, double dropLower) { } @Override - Set getSeriesNames() { - Set result = new HashSet<>(); - result.add(metricDescriptor.getName()); - return result; + protected List getMetrics() { + return Collections.singletonList(metricInfo); } @Override - protected List getMetrics() { - return Collections.singletonList(metricDescriptor); - } - - @Override - protected void computeSeries(List tagValues, double[] data, Timestamp time, ResultSeriesCollector resultSeries) { + protected void computeSeries(ResultSeriesCollector resultSeries, Instant time, Attributes attributes, double[] data) { int queueLength = data.length; int skipAtBottom = Math.min((int) Math.ceil(dropLower * queueLength), queueLength - 1); @@ -85,7 +74,6 @@ protected void computeSeries(List tagValues, double[] data, Timestamp ti .limit(limit) .average() .orElse(0.0); - resultSeries.add(metricDescriptor, smoothedAverage, time, tagValues); + resultSeries.add(metricInfo, time, attributes, smoothedAverage); } - } diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/TimeWindowView.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/TimeWindowView.java new file mode 100644 index 0000000..129b37f --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/TimeWindowView.java @@ -0,0 +1,350 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.views; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.metrics.data.DoublePointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.MetricDataType; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableDoublePointData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableGaugeData; +import io.opentelemetry.sdk.metrics.internal.data.ImmutableMetricData; +import io.opentelemetry.sdk.resources.Resource; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; +import rocks.inspectit.ocelot.eum.server.opentelemetry.OpenTelemetryInfo; + +import java.time.Duration; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Holds the data for a given measurement split by a provided set of attributes over a given time window. + */ +@Slf4j +public abstract class TimeWindowView { + + private static final Duration CLEANUP_INTERVAL = Duration.ofSeconds(1); + + /** + * Stores the buffered data of the sliding time window for each + * time series (unique combination of attribute values). + */ + private final ConcurrentHashMap, WindowedDoubleQueue> seriesValues = new ConcurrentHashMap<>(); + + /** + * The current number of points stored in this view, limited by {@link #bufferLimit}. + */ + private final AtomicInteger numberOfPoints = new AtomicInteger(0); + + /** + * The timestamp when the last full cleanup happened. + */ + private final AtomicLong lastCleanupTimeMs = new AtomicLong(0); + + /** + * Defines the attributes which are used for the view. Only these attributes can be recorded for the view! + * E.g. if the attribute "http_path" is used, quantiles will be computed for each http_path individually. + *

+ * The attribute values are stored in a fixed order in the keys of {@link #seriesValues} for each series. + * The values here define at which position within these arrays the corresponding attribute value is found. + * E.g. if tagIndex["http_path"] = 2, this means that the values for http_path will be at index 2 in the keys of {@link #seriesValues}. + */ + private final Map attributeIndices = new LinkedHashMap<>(); + + /** + * The name of the view, used as prefix for all individual metrics. + */ + @Getter + private final String viewName; + + /** + * The description of this view. + */ + @Getter + private final String description; + + /** + * The unit of the metric. + */ + @Getter + private final String unit; + + /** + * Defines the size of the sliding window. + */ + @Getter + private final Duration timeWindow; + + /** + * The maximum amount of measurement points to buffer. + * If this limit is reached, new metrics will be rejected until there is space again. + */ + @Getter + private final int bufferLimit; + + private boolean overflowWarningPrinted = false; + + /** + * @param viewName the prefix to use for the names of all exposed metrics + * @param description the description of this view + * @param unit the unit of the measure + * @param attributes the attribute keys to use for this view + * @param timeWindow the time range to use for computing minimum / maximum and quantiles values + * @param bufferLimit the maximum number of measurements to be buffered by this view + */ + TimeWindowView(String viewName, String description, String unit, Set attributes, Duration timeWindow, int bufferLimit) { + validateConfiguration(timeWindow, viewName, unit, description, bufferLimit); + assignAttributeIndices(attributes); + this.timeWindow = timeWindow; + this.viewName = viewName; + this.unit = unit; + this.description = description; + this.bufferLimit = bufferLimit; + } + + private void validateConfiguration(Duration timeWindow, String baseViewName, String unit, String description, int bufferLimit) { + if (StringUtils.isBlank(baseViewName)) { + throw new IllegalArgumentException("View name must not be blank!"); + } + if (StringUtils.isBlank(description)) { + throw new IllegalArgumentException("Description must not be blank!"); + } + if (StringUtils.isBlank(unit)) { + throw new IllegalArgumentException("Unit must not be blank!"); + } + if (timeWindow.toMillis() <= 0) { + throw new IllegalArgumentException("Time window must be positive!"); + } + if (bufferLimit < 1) { + throw new IllegalArgumentException("The buffer limit must be greater than or equal to 1!"); + } + } + + private void assignAttributeIndices(Set attributes) { + int idx = 0; + for (String attribute : attributes) { + attributeIndices.put(attribute, idx); + idx++; + } + } + + /** + * Adds the provided value to the sliding window of data. + * + * @param value the value of the metric + * @param time the timestamp when this value was observed + * @param baggage the baggage with of the observed value + * + * @return true, if the point could be added, false otherwise + */ + public boolean insertValue(double value, Instant time, Baggage baggage) { + removeStalePointsIfTimeThresholdExceeded(time); + List attributeValues = getAttributeValuesInOrder(baggage); + WindowedDoubleQueue queue = seriesValues.computeIfAbsent(attributeValues, (v) -> new WindowedDoubleQueue(timeWindow)); + synchronized (queue) { + long timeMillis = time.toEpochMilli(); + int removed = queue.removeStaleValues(timeMillis); + int currentSize = numberOfPoints.addAndGet(-removed); + if (currentSize < bufferLimit) { + numberOfPoints.incrementAndGet(); + queue.insert(value, timeMillis); + } else { + if (!overflowWarningPrinted) { + overflowWarningPrinted = true; + log.warn("Dropping points for Quantiles-View '{}' because the buffer limit has been reached!" + " Quantiles/Min/Max will be meaningless." + " This warning will not be shown for future drops!", viewName); + } + return false; + } + } + return true; + } + + /** + * Removes all data which has fallen out of the time window based on the given timestamp. + * + * @param time the current time + */ + private void removeStalePoints(Instant time) { + long timeMillis = time.toEpochMilli(); + lastCleanupTimeMs.set(timeMillis); + for (WindowedDoubleQueue queue : seriesValues.values()) { + synchronized (queue) { + int removed = queue.removeStaleValues(timeMillis); + numberOfPoints.getAndAdd(-removed); + } + } + } + + /** + * Removes all data which has fallen out of the time window based on the given timestamp. + * Only performs the cleanup if the last cleanup has been done more than {@link #CLEANUP_INTERVAL} ago + * and the buffer is running on its capacity limit. + * + * @param time the current time + */ + private void removeStalePointsIfTimeThresholdExceeded(Instant time) { + long timeMillis = time.toEpochMilli(); + long lastCleanupTime = lastCleanupTimeMs.get(); + boolean timeThresholdExceeded = timeMillis - lastCleanupTime > CLEANUP_INTERVAL.toMillis(); + if (timeThresholdExceeded && numberOfPoints.get() >= bufferLimit) { + removeStalePoints(time); + } + } + + /** + * Creates a list of ordered attribute values. The positions are derived from {@link #attributeIndices}. + * + * @return the attribute values used for this view + */ + private List getAttributeValuesInOrder(Baggage baggage) { + String[] orderedValues = new String[attributeIndices.size()]; + + baggage.forEach((key, value) -> { + if (attributeIndices.containsKey(key)) { // Only care about baggage which is configured for the view + int idx = attributeIndices.get(key); + orderedValues[idx] = value.getValue(); + } + }); + + return Arrays.asList(orderedValues); + } + + protected abstract Collection getMetrics(); + + /** + * Computes the defined quantiles and min / max metrics. + * + * @param time the current timestamp + * + * @return the metrics containing the quantiles and min / max + */ + public Collection computeMetrics(Instant time, Resource resource) { + removeStalePoints(time); + ResultSeriesCollector resultSeries = ResultSeriesCollector.create(getMetrics()); + for (Map.Entry, WindowedDoubleQueue> series : seriesValues.entrySet()) { + List attributeValues = series.getKey(); + Attributes attributes = buildAttributes(attributeValues); + WindowedDoubleQueue queue = series.getValue(); + double[] data = null; + synchronized (queue) { + int size = queue.size(); + if (size > 0) { + data = queue.copy(); + } + } + if (data != null) { + computeSeries(resultSeries, time, attributes, data); + } + } + + List resultMetrics = new ArrayList<>(); + for (Map.Entry> entry : resultSeries.seriesMap.entrySet()) { + MetricData metricData = createMetricData(resource, entry.getKey(), entry.getValue()); + resultMetrics.add(metricData); + } + return resultMetrics; + } + + /** + * Builds attributes from keys in {@link #attributeIndices} and the provided values. + * We expect the values to be properly ordered. + * + * @param attributeValues the ordered attribute values + * + * @return the attribute keys and values + */ + private Attributes buildAttributes(List attributeValues) { + Iterator keyIterator = attributeIndices.keySet().iterator(); + Iterator valueIterator = attributeValues.iterator(); + AttributesBuilder builder = Attributes.builder(); + + while (keyIterator.hasNext() && valueIterator.hasNext()) { + builder.put(keyIterator.next(), valueIterator.next()); + } + + return builder.build(); + } + + /** + * Creates the metric data object. At the moment all time-window views only support {@link MetricDataType#DOUBLE_GAUGE}. + * + * @param resource the resource information + * @param metricInfo the metric information + * @param pointData the data points + * + * @return the created metric data + */ + private MetricData createMetricData(Resource resource, MetricInfo metricInfo, Collection pointData) { + return ImmutableMetricData.createDoubleGauge( + resource, + OpenTelemetryInfo.INSTRUMENTATION_SCOPE_INFO, + metricInfo.name, + metricInfo.description, + metricInfo.unit, + ImmutableGaugeData.create(pointData) + ); + } + + /** + * Computes the data into one series. + * + * @param resultSeries the resulting time series + * @param time the timestamp + * @param attributes the attributes for the time series + * @param data the series data + */ + protected abstract void computeSeries(ResultSeriesCollector resultSeries, Instant time, Attributes attributes, double[] data); + + @AllArgsConstructor + protected static class MetricInfo { + private final String name; + + private final String description; + + private final String unit; + } + + protected static class ResultSeriesCollector { + + private final Map> seriesMap = new HashMap<>(); + + public static ResultSeriesCollector create(Collection metrics) { + ResultSeriesCollector resultSeries = new ResultSeriesCollector(); + if (!CollectionUtils.isEmpty(metrics)) { + metrics.forEach(metric -> resultSeries.seriesMap.put(metric, new ArrayList<>())); + } + return resultSeries; + } + + /** + * Creates and adds a data point to the series + * + * @param metric the metric information + * @param time the time of recording the data point + * @param attributes the data attributes + * @param value the data value + */ + void add(MetricInfo metric, Instant time, Attributes attributes, double value) { + Collection series = seriesMap.get(metric); + DoublePointData pointData = createPointData(time, attributes, value); + series.add(pointData); + } + + private DoublePointData createPointData(Instant time, Attributes attributes, double value) { + long timestamp = time.toEpochMilli() * 1_000_000 + time.getNano(); + return ImmutableDoublePointData.create( + timestamp, + timestamp, + attributes, + value + ); + } + } +} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/WindowedDoubleQueue.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/WindowedDoubleQueue.java similarity index 95% rename from src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/WindowedDoubleQueue.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/WindowedDoubleQueue.java index 951a842..997615a 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/metrics/percentiles/WindowedDoubleQueue.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/WindowedDoubleQueue.java @@ -1,10 +1,10 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.views; import com.google.common.annotations.VisibleForTesting; +import java.time.Duration; + /** - * COPIED FROM THE OCELOT CORE PROJECT! - *

* A circular, array based FIFO-queue for remembering measurement values in a sliding window over time. *

* A queue covers a fixed time range (e.g. 15 seconds). @@ -51,7 +51,7 @@ public class WindowedDoubleQueue { /** * The size of the time window covered by this queue. */ - private long timeRange; + private final long timeRange; /** * Creates a new queue, covering the given amount of time. @@ -60,10 +60,10 @@ public class WindowedDoubleQueue { * The unit must be the same as for timestamps given to {@link #insert(double, long)} * and {@link #removeStaleValues(long)} */ - public WindowedDoubleQueue(long timeRange) { + public WindowedDoubleQueue(Duration timeRange) { values = new double[MIN_CAPACITY]; timeStamps = new long[MIN_CAPACITY]; - this.timeRange = timeRange; + this.timeRange = timeRange.toMillis(); } /** @@ -73,7 +73,7 @@ public WindowedDoubleQueue(long timeRange) { * The queue expects that all inserts happen ordered in time! * You should never insert data which is older than the latest element in the queue. *

- * This method has an amortized O(1) runtime, with a worst case of O(n). + * This method has an amortized O(1) runtime with the worst case of O(n). *

* In addition, this method is guaranteed to not alter the queue in case it throws an exception. * @@ -207,5 +207,4 @@ static int roundUpToPowerOfTwo(int value) { } return highestOneBit * 2; } - } diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowMetricProducer.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowMetricProducer.java new file mode 100644 index 0000000..b687b8c --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowMetricProducer.java @@ -0,0 +1,61 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.worker; + +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricProducer; +import io.opentelemetry.sdk.resources.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.TimeWindowViewManager; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.TimeWindowView; +import rocks.inspectit.ocelot.eum.server.opentelemetry.OpenTelemetryController; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; + +/** + * A metric producer which caches time-window metrics for a specified amount of time. + * The metric producer will be registered in OpenTelemetry via {@link OpenTelemetryController}.
+ * Computation of percentiles can be expensive. + * For this reason we cache computed metrics for 1 second before recomputing them. + * Otherwise, e.g. spamming F5 on the prometheus endpoint could lead to an increased CPU usage. + */ +@Component +public class TimeWindowMetricProducer implements MetricProducer { + + @Autowired + private TimeWindowViewManager viewManager; + + /** + * The duration for which cached metrics are kept. + */ + private final Duration cacheDuration = Duration.ofSeconds(1); + + /** + * The timestamp when the metrics were computed the last time. + */ + private Instant cacheTimestamp = Instant.now(); + + private Collection cachedMetrics; + + @Override + public Collection produce(Resource resource) { + Instant now = Instant.now(); + + if (cachedMetrics == null || (now.toEpochMilli() - cacheTimestamp.toEpochMilli()) > cacheDuration.toMillis()) { + cachedMetrics = computeMetrics(resource); + cacheTimestamp = now; + } + return cachedMetrics; + } + + @VisibleForTesting + Collection computeMetrics(Resource resource) { + Instant now = Instant.now(); + Collection views = viewManager.getAllViews(); + return views.stream() + .flatMap(view -> view.computeMetrics(now, resource).stream()) + .toList(); + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowRecorder.java b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowRecorder.java new file mode 100644 index 0000000..f5b74ba --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowRecorder.java @@ -0,0 +1,120 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.worker; + +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.api.baggage.Baggage; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.TimeWindowViewManager; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.TimeWindowView; + +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Records time-window metrics asynchronously. + * Recording observation takes amortized O(1) time. + * However, the worst-case time of a recording is O(n), which is why we decouple the recording from the application threads. + * This worker maintains a fixed-size queue of observations which are then added via {@link #record}. + */ +@Slf4j +@Component +public class TimeWindowRecorder { + + private volatile boolean overflowLogged = false; + + @VisibleForTesting + final ArrayBlockingQueue recordsQueue = new ArrayBlockingQueue<>(8096); + + /** + * The interval for processing the {@link #recordsQueue} + */ + private final Duration recordingInterval = Duration.ofMillis(50); + + private Future recordingTask; + + @Autowired + private TimeWindowViewManager viewManager; + + @Autowired + private ScheduledExecutorService executorService; + + @PostConstruct + void postConstruct() { + Thread worker = new Thread(this::record); + worker.setDaemon(true); + worker.setName("time-window-recorder"); + + recordingTask = executorService.scheduleWithFixedDelay(worker, recordingInterval.toMillis(), recordingInterval.toMillis(), TimeUnit.MILLISECONDS); + } + + /** + * Records an observation for a given metric, if it was registered via {@link TimeWindowViewManager}. + * + * @param metricName the name of the metric, e.g. http/responsetime + * @param value the observation to record + * @param baggage the baggage to use + */ + public synchronized void recordMetric(String metricName, double value, Baggage baggage) { + if (viewManager.areAnyViewsRegistered(metricName)) { + boolean success = recordsQueue.offer(new MetricRecord(metricName, value, Instant.now(), baggage)); + if (!success && !overflowLogged) { + overflowLogged = true; + log.warn("Metric for time-window views has been dropped because queue is full. This message will not be shown for further drops"); + } + } + } + + /** + * Asynchronous recording via {@link #recordingTask}. + */ + @VisibleForTesting + void record() { + try { + MetricRecord record = recordsQueue.take(); + doRecord(record.metricName, record.value, record.time, record.baggage); + } catch (InterruptedException e) { + log.error("TimeWindowRecorder interrupted"); + Thread.currentThread().interrupt(); + } catch (Exception e) { + log.error("Error processing record: ", e); + } + } + + /** + * Records the specified metric observation. + * + * @param metricName the name of the metric + * @param value the observed value + * @param time the timestamp of the observation + * @param baggage the baggage to use + */ + private void doRecord(String metricName, double value, Instant time, Baggage baggage) { + Collection views = viewManager.getViews(metricName); + if (views != null) { + views.forEach(view -> view.insertValue(value, time, baggage)); + } + } + + @PreDestroy + void destroy() { + recordingTask.cancel(true); + } + + /** + * Queued metric record. + * + * @param metricName the metric name + * @param value the recorded value + * @param time the time of recording + * @param baggage the baggage to use + */ + private record MetricRecord(String metricName, double value, Instant time, Baggage baggage) {} +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/OpenTelemetryController.java b/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/OpenTelemetryController.java new file mode 100644 index 0000000..aea9c54 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/OpenTelemetryController.java @@ -0,0 +1,109 @@ +package rocks.inspectit.ocelot.eum.server.opentelemetry; + +import io.opentelemetry.api.metrics.*; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.metrics.*; +import io.opentelemetry.sdk.metrics.export.MetricProducer; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.resources.Resource; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.opentelemetry.metrics.ViewManager; +import rocks.inspectit.ocelot.eum.server.opentelemetry.resource.ResourceManager; + +import java.util.Collection; + +@Slf4j +@Component +public class OpenTelemetryController { + + + @Autowired + private EumServerConfiguration configuration; + + @Autowired + private ResourceManager resourceManager; + + @Autowired + private ViewManager viewManager; + + @Autowired(required = false) + private Collection metricReaders; + + @Autowired(required = false) + private Collection metricProducers; + + /** + * The configured OpenTelemetry SDK + */ + private OpenTelemetrySdk openTelemetry; + + @PostConstruct + synchronized void configureOpenTelemetry() { + if(openTelemetry != null) { + log.warn("OpenTelemetry already configured!"); + return; + } + + SdkMeterProvider meterProvider = configureMeterProvider(); + + // We don't need any tracerProvider, since we do not record traces via API here + openTelemetry = OpenTelemetrySdk.builder() + .setMeterProvider(meterProvider) + .build(); + // Using buildAndRegisterGlobal() is also possible, but makes cleaning in tests more cumbersome... + } + + /** + * @return the Meter API to create metrics + */ + public Meter getMeter() { + Meter meter = openTelemetry.getMeter(OpenTelemetryInfo.INSTRUMENTATION_SCOPE_NAME); + return meter; // For debugging + } + + /** + * Configure the meter provider with registered metric readers, producers and views. + * + * @return the configured meter provider + */ + private SdkMeterProvider configureMeterProvider() { + Resource resource = resourceManager.getResource(); + SdkMeterProviderBuilder builder = SdkMeterProvider.builder().setResource(resource); + + if(!CollectionUtils.isEmpty(metricReaders)) { + for (MetricReader metricReader : metricReaders) { + log.debug("Registering OpenTelemetry MetricReader: {}", metricReader); + builder.registerMetricReader(metricReader); + } + } + else log.info("OpenTelemetry has not registered any MetricReader! " + + "Thus no metrics can be recorded. Enable at least one metrics exporter to record metrics"); + + if(!CollectionUtils.isEmpty(metricProducers)) { + for (MetricProducer metricProducer : metricProducers) { + log.debug("Registering OpenTelemetry MetricProducer: {}", metricProducer); + builder.registerMetricProducer(metricProducer); + } + } + + viewManager.registerViews(builder); + + return builder.build(); + } + + @PreDestroy + public void shutdown() { + if (null != openTelemetry) { + log.info("Flushing pending OpenTelemetry data"); + openTelemetry.close(); + log.info("Flushing OpenTelemetry data completed"); + } + openTelemetry = null; + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/OpenTelemetryInfo.java b/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/OpenTelemetryInfo.java new file mode 100644 index 0000000..9916571 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/OpenTelemetryInfo.java @@ -0,0 +1,13 @@ +package rocks.inspectit.ocelot.eum.server.opentelemetry; + +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; + +/** + * Constant information for OpenTelemetry + */ +public class OpenTelemetryInfo { + + public static final String INSTRUMENTATION_SCOPE_NAME = "rocks.inspectit.ocelot"; + + public static final InstrumentationScopeInfo INSTRUMENTATION_SCOPE_INFO = InstrumentationScopeInfo.create(INSTRUMENTATION_SCOPE_NAME); +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/metrics/ViewManager.java b/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/metrics/ViewManager.java new file mode 100644 index 0000000..39e7361 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/metrics/ViewManager.java @@ -0,0 +1,207 @@ +package rocks.inspectit.ocelot.eum.server.opentelemetry.metrics; + +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.sdk.metrics.*; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.AggregationType; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.metrics.InstrumentManager; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.TimeWindowViewManager; + +import java.util.*; + +/** + * Stores all active OpenTelemetry metric views. + *
+ * Note that if we do not register any view for an instrument (see {@link InstrumentManager}), + * OpenTelemetry would use a default view automatically to record data. + * However, this default view would not filter any attributes and thus would use every attribute, + * which was passed during recording. To prevent this, we should register at least one view for every metric by ourselves. + */ +@Component +public class ViewManager { + + @Autowired + private EumServerConfiguration configuration; + + @Autowired + private TimeWindowViewManager viewManager; + + /** + * Registers all configured views in the provided {@link SdkMeterProviderBuilder}. + * + * @param builder the sdk builder to register views with + */ + public void registerViews(SdkMeterProviderBuilder builder) { + val metricViews = getAllActiveViewsForMetrics(); + + for (val metricEntry : metricViews.entrySet()) { + String metricName = metricEntry.getKey(); + Map viewDefinitionSettings = metricEntry.getValue(); + + for (Map.Entry viewEntry: viewDefinitionSettings.entrySet()) { + String viewName = viewEntry.getKey(); + ViewDefinitionSettings settings = viewEntry.getValue(); + + if (settings.getAggregation().isTimeWindowAggregation()) { + registerTimeWindowView(metricName, viewName, settings); + } else { + View view = createView(viewName, settings); + InstrumentSelector selector = createSelector(metricName); + + builder.registerView(selector, view); + } + } + } + } + + /** + * Registers the view as custom time-window view, which is handled by {@link TimeWindowViewManager} instead of + * OpenTelemetry {@link MeterProvider}. + * + * @param metricName the metric name + * @param viewName the view name + * @param settings the view settings + */ + private void registerTimeWindowView(String metricName, String viewName, ViewDefinitionSettings settings) { + String unit = getUnit(metricName); + + viewManager.registerView(metricName, viewName, unit, settings); + } + + /** + * @return the map of all metric names and their particular active views + */ + private Map> getAllActiveViewsForMetrics() { + Map> metricViews = new HashMap<>(); + + // check self-monitoring definitions + configuration.getSelfMonitoring().getMetrics() + .entrySet().stream() + .filter(e -> e.getValue().isEnabled()) + .forEach((e) -> { + String metricName = e.getKey(); + MetricDefinitionSettings metricDefinition = e.getValue().getCopyWithDefaultsPopulated(metricName); + + Map activeViews = getActiveViews(metricDefinition); + metricViews.put(metricName, activeViews); + }); + + // check beacon metric definitions + configuration.getDefinitions().entrySet().stream() + .filter(e -> e.getValue().isEnabled()) + .forEach(e -> { + String metricName = e.getKey(); + MetricDefinitionSettings metricDefinition = e.getValue().getCopyWithDefaultsPopulated(metricName); + + Map activeViews = getActiveViews(metricDefinition); + metricViews.put(metricName, activeViews); + }); + + return metricViews; + } + + /** + * @return the map of all active views for the provided metric definition + */ + private Map getActiveViews(MetricDefinitionSettings metricDefinition) { + Map activeViews = new HashMap<>(); + metricDefinition.getViews().entrySet().stream() + .filter(e -> e.getValue().isEnabled()) + .forEach(e -> activeViews.put(e.getKey(), e.getValue())); + return activeViews; + } + + /** + * Creates a new view from the provided settings. + * + * @param viewName the view name + * @param settings the view settings + * + * @return the created view + */ + private View createView(String viewName, ViewDefinitionSettings settings) { + Aggregation aggregation = convertAggregation(settings); + ViewBuilder builder = View.builder() + .setName(viewName) + .setDescription(settings.getDescription()) + .setAggregation(aggregation) + .setCardinalityLimit(settings.getCardinalityLimit()); + boolean withCommonAttributes = settings.isWithCommonAttributes(); + + if (!CollectionUtils.isEmpty(settings.getAttributes())) { + builder.setAttributeFilter((attribute) -> filterAttribute(settings, attribute)); + } + else if (withCommonAttributes){ + builder.setAttributeFilter(this::isDefinedAsGlobal); + } + else { + builder.setAttributeFilter((attr) -> false); // reject all attributes + } + + return builder.build(); + } + + /** + * Checks, if the view includes the provided attribute key or if the key is defined globally + * + * @param settings the view settings + * @param attribute the current attribute key + * + * @return true, if this attribute key should be used for the provided view + */ + private boolean filterAttribute(ViewDefinitionSettings settings, String attribute) { + return settings.getAttributes().getOrDefault(attribute, false) || + isDefinedAsGlobal(attribute); + } + + /** + * @param attribute the current attribute key + * + * @return true, if the attribute key is defined globally + */ + private boolean isDefinedAsGlobal(String attribute) { + return configuration.getAttributes().getDefineAsGlobal().contains(attribute); + } + + /** + * Creates a selector so the view can be applied to their particular metric. + * At them moment, we select the metric solely by their name. + * + * @param metricName the metric name + * + * @return the instrument selector + */ + private InstrumentSelector createSelector(String metricName) { + return InstrumentSelector.builder() + .setName(metricName) + .build(); + } + + /** + * Converts the {@link AggregationType} to a proper OpenTelemetry {@link Aggregation}. + * + * @param settings the view settings + * + * @return the converted {@link Aggregation} + */ + private Aggregation convertAggregation(ViewDefinitionSettings settings) { + AggregationType type = settings.getAggregation(); + return switch (type) { + case SUM -> Aggregation.sum(); + case LAST_VALUE -> Aggregation.lastValue(); + case HISTOGRAM -> Aggregation.explicitBucketHistogram(settings.getBucketBoundaries()); + case EXPONENTIAL_HISTOGRAM -> Aggregation.base2ExponentialBucketHistogram(settings.getMaxBuckets(), settings.getMaxScale()); + default -> throw new IllegalArgumentException("Unexpected OpenTelemetry aggregation:" + type); + }; + } + + private String getUnit(String metricName) { + return configuration.getDefinitions().get(metricName).getUnit(); + } +} diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/resource/ResourceManager.java b/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/resource/ResourceManager.java new file mode 100644 index 0000000..69b0ed9 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/opentelemetry/resource/ResourceManager.java @@ -0,0 +1,32 @@ +package rocks.inspectit.ocelot.eum.server.opentelemetry.resource; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.ServiceAttributes; +import io.opentelemetry.semconv.TelemetryAttributes; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import rocks.inspectit.ocelot.eum.server.AppStartupRunner; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.utils.VersionUtil; + +@Component +public class ResourceManager { + + @Autowired + private EumServerConfiguration configuration; + + /** + * @return the OpenTelemetry resources + */ + public Resource getResource() { + return Resource.create(Attributes.of( + ServiceAttributes.SERVICE_NAME, configuration.getExporters().getServiceName(), + TelemetryAttributes.TELEMETRY_SDK_LANGUAGE, "java", + TelemetryAttributes.TELEMETRY_SDK_NAME, "opentelemetry", + TelemetryAttributes.TELEMETRY_SDK_VERSION, VersionUtil.getOpenTelemetryVersion(), + AttributeKey.stringKey("inspectit.eum-server.version"), VersionUtil.getServerVersion() + )); + } +} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/rest/BeaconController.java b/src/main/java/rocks/inspectit/ocelot/eum/server/rest/BeaconController.java similarity index 84% rename from src/main/java/rocks/inspectit/oce/eum/server/rest/BeaconController.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/rest/BeaconController.java index 26fe0a8..d90b9bc 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/rest/BeaconController.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/rest/BeaconController.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.rest; +package rocks.inspectit.ocelot.eum.server.rest; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -6,11 +6,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.*; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.beacon.processor.CompositeBeaconProcessor; -import rocks.inspectit.oce.eum.server.exporters.beacon.BeaconHttpExporter; -import rocks.inspectit.oce.eum.server.metrics.BeaconMetricManager; -import rocks.inspectit.oce.eum.server.metrics.SelfMonitoringMetricManager; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.processor.CompositeBeaconProcessor; +import rocks.inspectit.ocelot.eum.server.exporters.beacon.BeaconHttpExporter; +import rocks.inspectit.ocelot.eum.server.metrics.BeaconMetricManager; +import rocks.inspectit.ocelot.eum.server.metrics.SelfMonitoringMetricManager; import java.util.Collections; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/rest/TraceController.java b/src/main/java/rocks/inspectit/ocelot/eum/server/rest/TraceController.java similarity index 94% rename from src/main/java/rocks/inspectit/oce/eum/server/rest/TraceController.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/rest/TraceController.java index 6ec8978..234552a 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/rest/TraceController.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/rest/TraceController.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.rest; +package rocks.inspectit.ocelot.eum.server.rest; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; @@ -16,8 +16,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; -import rocks.inspectit.oce.eum.server.metrics.SelfMonitoringMetricManager; -import rocks.inspectit.oce.eum.server.tracing.opentelemtry.OpenTelemetryProtoConverter; +import rocks.inspectit.ocelot.eum.server.metrics.SelfMonitoringMetricManager; +import rocks.inspectit.ocelot.eum.server.tracing.opentelemetry.OpenTelemetryProtoConverter; import jakarta.validation.constraints.NotBlank; import java.util.Collection; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/rest/WebConfiguration.java b/src/main/java/rocks/inspectit/ocelot/eum/server/rest/WebConfiguration.java similarity index 90% rename from src/main/java/rocks/inspectit/oce/eum/server/rest/WebConfiguration.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/rest/WebConfiguration.java index b0ad1f9..627008e 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/rest/WebConfiguration.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/rest/WebConfiguration.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.rest; +package rocks.inspectit.ocelot.eum.server.rest; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/security/ApiTokenAuthentication.java b/src/main/java/rocks/inspectit/ocelot/eum/server/security/ApiTokenAuthentication.java similarity index 97% rename from src/main/java/rocks/inspectit/oce/eum/server/security/ApiTokenAuthentication.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/security/ApiTokenAuthentication.java index 25be129..9a84c43 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/security/ApiTokenAuthentication.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/security/ApiTokenAuthentication.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.security; +package rocks.inspectit.ocelot.eum.server.security; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/security/ApiTokenFilter.java b/src/main/java/rocks/inspectit/ocelot/eum/server/security/ApiTokenFilter.java similarity index 97% rename from src/main/java/rocks/inspectit/oce/eum/server/security/ApiTokenFilter.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/security/ApiTokenFilter.java index f7e197e..ff15d91 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/security/ApiTokenFilter.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/security/ApiTokenFilter.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.security; +package rocks.inspectit.ocelot.eum.server.security; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.AuthenticationManager; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/security/SecurityConfig.java b/src/main/java/rocks/inspectit/ocelot/eum/server/security/SecurityConfig.java similarity index 93% rename from src/main/java/rocks/inspectit/oce/eum/server/security/SecurityConfig.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/security/SecurityConfig.java index 91ef62b..122cc7a 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/security/SecurityConfig.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/security/SecurityConfig.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.security; +package rocks.inspectit.ocelot.eum.server.security; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -13,8 +13,8 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.security.SecuritySettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.security.SecuritySettings; import java.util.Collections; import java.util.List; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProvider.java b/src/main/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProvider.java similarity index 96% rename from src/main/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProvider.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProvider.java index 8385dab..b81cc90 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProvider.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProvider.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.security.authprovider; +package rocks.inspectit.ocelot.eum.server.security.authprovider; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -17,9 +17,9 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.security.ApiTokenAuthentication; -import rocks.inspectit.oce.eum.server.utils.DirectoryPoller; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.security.ApiTokenAuthentication; +import rocks.inspectit.ocelot.eum.server.utils.DirectoryPoller; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/tracing/opentelemtry/OpenTelemetryProtoConverter.java b/src/main/java/rocks/inspectit/ocelot/eum/server/tracing/opentelemetry/OpenTelemetryProtoConverter.java similarity index 95% rename from src/main/java/rocks/inspectit/oce/eum/server/tracing/opentelemtry/OpenTelemetryProtoConverter.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/tracing/opentelemetry/OpenTelemetryProtoConverter.java index 922ffe5..ec48872 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/tracing/opentelemtry/OpenTelemetryProtoConverter.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/tracing/opentelemetry/OpenTelemetryProtoConverter.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.tracing.opentelemtry; +package rocks.inspectit.ocelot.eum.server.tracing.opentelemetry; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; @@ -14,8 +14,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.utils.RequestUtils; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.utils.RequestUtils; import jakarta.servlet.http.HttpServletRequest; import java.util.*; diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/utils/AttributeUtil.java b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/AttributeUtil.java new file mode 100644 index 0000000..8e2d2a1 --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/AttributeUtil.java @@ -0,0 +1,22 @@ +package rocks.inspectit.ocelot.eum.server.utils; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; + +public class AttributeUtil { + + /** + * Converts the provided baggage data to attributes. + * + * @param baggage the baggage + * + * @return the baggage data as attributes + */ + public static Attributes toAttributes(Baggage baggage) { + AttributesBuilder builder = Attributes.builder(); + baggage.asMap() + .forEach((key, entry) -> builder.put(key, entry.getValue())); + return builder.build(); + } +} diff --git a/src/main/java/rocks/inspectit/oce/eum/server/utils/DirectoryPoller.java b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/DirectoryPoller.java similarity index 99% rename from src/main/java/rocks/inspectit/oce/eum/server/utils/DirectoryPoller.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/utils/DirectoryPoller.java index e40eaea..cf976fa 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/utils/DirectoryPoller.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/DirectoryPoller.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.utils; +package rocks.inspectit.ocelot.eum.server.utils; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/utils/GeolocationResolver.java b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/GeolocationResolver.java similarity index 97% rename from src/main/java/rocks/inspectit/oce/eum/server/utils/GeolocationResolver.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/utils/GeolocationResolver.java index 5f5f962..61ea93f 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/utils/GeolocationResolver.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/GeolocationResolver.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.utils; +package rocks.inspectit.ocelot.eum.server.utils; import com.maxmind.geoip2.DatabaseReader; import com.maxmind.geoip2.exception.GeoIp2Exception; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/utils/IPUtils.java b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/IPUtils.java similarity index 97% rename from src/main/java/rocks/inspectit/oce/eum/server/utils/IPUtils.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/utils/IPUtils.java index 9286cc5..eea0ca7 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/utils/IPUtils.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/IPUtils.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.utils; +package rocks.inspectit.ocelot.eum.server.utils; import org.apache.commons.net.util.SubnetUtils; import org.springframework.security.web.util.matcher.IpAddressMatcher; diff --git a/src/main/java/rocks/inspectit/oce/eum/server/utils/RequestUtils.java b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/RequestUtils.java similarity index 94% rename from src/main/java/rocks/inspectit/oce/eum/server/utils/RequestUtils.java rename to src/main/java/rocks/inspectit/ocelot/eum/server/utils/RequestUtils.java index 561a7bb..d21ccdc 100644 --- a/src/main/java/rocks/inspectit/oce/eum/server/utils/RequestUtils.java +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/RequestUtils.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.utils; +package rocks.inspectit.ocelot.eum.server.utils; import lombok.AccessLevel; import lombok.NoArgsConstructor; diff --git a/src/main/java/rocks/inspectit/ocelot/eum/server/utils/VersionUtil.java b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/VersionUtil.java new file mode 100644 index 0000000..6403e8a --- /dev/null +++ b/src/main/java/rocks/inspectit/ocelot/eum/server/utils/VersionUtil.java @@ -0,0 +1,80 @@ +package rocks.inspectit.ocelot.eum.server.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; + +@Slf4j +public class VersionUtil { + + /** + * Version string of unknown versions + */ + private static final String UNKNOWN = "UNKNOWN"; + + /** + * The file used to load the server's version + */ + private static final String SERVER_VERSION_INFORMATION_FILE = "/eum-version.info"; + + /** + * The server's version + */ + private static String serverVersion; + + /** + * The date the server was built + */ + private static String serverBuildDate; + + /** + * The Boomerang.js version shipped with the server + */ + private static String bommerangjsVersion; + + /** + * The OpenTelemetry API version + */ + private static String openTelemetryVersion; + + public static String getServerVersion() { + if(serverVersion == null) readVersionInformation(); + return serverVersion; + } + + public static String getServerBuildDate() { + if(serverBuildDate == null) readVersionInformation(); + return serverBuildDate; + } + + public static String getBoomerangVersion() { + if(bommerangjsVersion == null) readVersionInformation(); + return bommerangjsVersion; + } + + public static String getOpenTelemetryVersion() { + if(openTelemetryVersion == null) readVersionInformation(); + return openTelemetryVersion; + } + + /** + * Loads the agent's version information from the {@link #SERVER_VERSION_INFORMATION_FILE} file. + */ + private static void readVersionInformation() { + try (InputStream inputStream = VersionUtil.class.getResourceAsStream(SERVER_VERSION_INFORMATION_FILE)) { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + serverVersion = reader.readLine(); + serverBuildDate = reader.readLine(); + bommerangjsVersion = reader.readLine(); + openTelemetryVersion = reader.readLine(); + } catch (Exception e) { + log.warn("Could not read server version information file"); + serverVersion = UNKNOWN; + serverBuildDate = UNKNOWN; + bommerangjsVersion = UNKNOWN; + openTelemetryVersion = UNKNOWN; + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d8b5bd9..f5685f7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,56 +1,84 @@ +# EUM-SERVER PROPERTIES inspectit-eum-server: definitions: + + resource_time: + enabled: true + description: Response end time of the resource loading + instrument-type: HISTOGRAM + value-type: DOUBLE + value-expression: "{restiming}" + unit: ms + views: + resource_time: + aggregation: HISTOGRAM + attributes: + initiatorType: true + cached: true + crossOrigin: true + page_ready_time: - measure-type: LONG + instrument-type: HISTOGRAM + value-type: LONG value-expression: "{t_page}" unit: ms views: - '[page_ready_time/SUM]': { aggregation: SUM } - '[page_ready_time/COUNT]': { aggregation: COUNT } + page_ready_time: { aggregation: HISTOGRAM } load_time: - measure-type: LONG + instrument-type: HISTOGRAM + value-type: LONG value-expression: "{t_done}" beacon-requirements: - field: rt.quit requirement: NOT_EXISTS unit: ms views: - '[load_time/SUM]': { aggregation: SUM } - '[load_time/COUNT]': { aggregation: COUNT } + load_time: { aggregation: HISTOGRAM } calc_load_time: - measure-type: LONG + instrument-type: HISTOGRAM + value-type: LONG value-expression: "{rt.end} - {rt.tstart}" beacon-requirements: - field: rt.quit requirement: NOT_EXISTS unit: ms views: - '[calc_load_time/SUM]': { aggregation: SUM } - '[calc_load_time/COUNT]': { aggregation: COUNT } + calc_load_time: { aggregation: HISTOGRAM } start_timestamp: - measure-type: LONG + instrument-type: GAUGE + value-type: LONG value-expression: "{rt.tstart}" unit: ms + views: + start_timestamp: + aggregation: LAST_VALUE + attributes: { APPLICATION: true } navigation_start_timestamp: - measure-type: LONG + instrument-type: GAUGE + value-type: LONG value-expression: "{rt.nstart}" unit: ms + views: + navigation_start_timestamp: + aggregation: LAST_VALUE + attributes: { APPLICATION: true } end_timestamp: - measure-type: LONG + instrument-type: GAUGE + value-type: LONG value-expression: "{rt.end}" unit: ms views: end_timestamp: aggregation: LAST_VALUE - tags: { APPLICATION: true } + attributes: { APPLICATION: true } - tags: + attributes: extra: APPLICATION: my-application beacon: @@ -58,8 +86,10 @@ inspectit-eum-server: input: COUNTRY_CODE URL: input: u + null-as-empty: true OS: input: ua.plt + null-as-empty: true U_NO_QUERY: input: u replacements: @@ -115,9 +145,13 @@ inspectit-eum-server: # metrics exporter settings exporters: + + # service name for all exported data + service-name: browser-js + metrics: prometheus: - # Determines whether the prometheus exporter is enabled. + # determines whether the prometheus exporter is enabled. enabled: DISABLED # The host of the prometheus HTTP endpoint. @@ -126,50 +160,9 @@ inspectit-eum-server: # The port of the prometheus HTTP endpoint. port: 8888 - # service name for all exported metrics. - service-name: "browser-js" - - influx: - # Determines whether the influx exporter is enabled. - enabled: IF_CONFIGURED - - # the export interval of the metrics. - export-interval: 15s - - # the url under which the InfluxDB can be accessed, e.g. http://localhost:8086 - # If this property is not set, the influx-exporter will not be started. - # endpoint: "http://localhost:8086" - endpoint: null - - # The database to write to. - # If this property is not set, the influx-exporter will not be started. - database: "inspectit" - - # The username to be used to connect to the influxDB. - # user: - - # The password to be used to connect to the influxDB. - # password: - - # The retention policy to write to. - # If this property is not set, the influx-exporter will not be started. - retention-policy: "autogen" - - # If true, the specified database will be created with the autogen retention policy. - create-database: true - - # If disabled, the raw values of each counter will be written to the InfluxDB on each export. - # When enabled, only the change of the counter in comparison to the previous export will be written. - # This difference will only be written if the counter has changed (=the difference is non-zero). - # This can greatly reduce the total data written to influx and makes writing queries easier. - counters-as-differences: true - - # The size of the buffer for failed batches. - # E.g. if the exportInterval is 15s and the buffer-size is 4, the export will keep up to one minute of data in memory. - buffer-size: 40 - - # settings for the OtlpGrpcMetricExporter used in OtlpGrpcMetricExporterService + # settings for the OTLP metrics exporter otlp: + # Determines whether the otlp metrics exporter is enabled. enabled: IF_CONFIGURED # the export interval of the metrics export-interval: 15s @@ -185,10 +178,12 @@ inspectit-eum-server: compression: NONE # timeout, i.e., maximum time the OTLP exporter will wait for each batch export timeout: 10s + tracing: - # Specifies whether client IP addresses which are added to spans should be masked. + # specifies whether client IP addresses which are added to spans should be masked. mask-span-ip-addresses: true + # settings for the OTLP trace exporter otlp: # If OTLP exporter for the OT received spans is enabled. enabled: IF_CONFIGURED @@ -203,30 +198,28 @@ inspectit-eum-server: # timeout, i.e., maximum time the OTLP exporter will wait for each batch export timeout: 10s - # service name for all exported spans. - service-name: browser-js - beacons: + http: - # Whether beacons should be exported via HTTP. + # whether beacons should be exported via HTTP enabled: DISABLED - # The endpoint to which the beacons are to be sent. + # the endpoint to which the beacons are to be sent endpoint-url: http://localhost:8080 - # The max. amount of threads exporting beacons (min. 1). + # the max. amount of threads exporting beacons (min. 1) worker-threads: 2 - # The maximum number of beacons to be exported using a single HTTP request (min. 1). + # the maximum number of beacons to be exported using a single HTTP request (min. 1) max-batch-size: 100 - # The flush interval to export beacons in case the 'max-batch-size' has not been reached (min. 1 second). + # the flush interval to export beacons in case the 'max-batch-size' has not been reached (min. 1 second) flush-interval: 5s - # When specified, the request will be using this username for Basic authentication. + # when specified, the request will be using this username for Basic authentication # username: - # The password used for Basic authentication. + # the password used for Basic authentication # password: # settings for the EUM server's self-monitoring @@ -235,96 +228,76 @@ inspectit-eum-server: # whether self-monitoring is enabled enabled: true - # the prefix used for the self-monitoring metrics - metricPrefix: "inspectit-eum/self/" + # the prefix used for the self-monitoring metrics and views + metricPrefix: "inspectit_eum_self_" # definitions of the self-monitoring metrics metrics: # counts the amount of received EUM beacons beacons_received: - measure-type: LONG + instrument-type: COUNTER + value-type: LONG unit: amount views: - '[inspectit-eum/self/beacons_received/count]': - aggregation: COUNT - tags: + beacons_received: + aggregation: SUM + attributes: is_error: true beacons_export: - measure-type: LONG + instrument-type: HISTOGRAM + value-type: LONG unit: amount views: - # the amount of beacon exports - '[inspectit-eum/self/beacons_export/count]': - aggregation: COUNT - tags: - exporter: true - is_error: true - # the export duration - '[inspectit-eum/self/beacons_export/duration/sum]': - aggregation: SUM - tags: + beacons_export: + aggregation: HISTOGRAM + attributes: exporter: true is_error: true beacons_export_batch: - measure-type: LONG + instrument-type: COUNTER + value-type: LONG unit: amount views: # the amount of elements in a single export execution - '[inspectit-eum/self/beacons_export/batch/sum]': + beacons_export_batch: aggregation: SUM - tags: + attributes: exporter: true is_error: true beacons_processor: - measure-type: LONG + instrument-type: HISTOGRAM + value-type: LONG unit: amount views: - # the amount of beacon processors - '[inspectit-eum/self/beacons_processor/count]': - aggregation: COUNT - tags: - beacon_processor: true - is_error: true - # the export duration - '[inspectit-eum/self/beacons_processor/duration/sum]': - aggregation: SUM - tags: + beacons_processor: + aggregation: HISTOGRAM + attributes: beacon_processor: true is_error: true traces_received: - measure-type: LONG + instrument-type: HISTOGRAM + value-type: LONG unit: amount views: - # the number of received traces - '[inspectit-eum/self/traces_received/count]': - aggregation: COUNT - tags: - is_error: true - # the export duration - '[inspectit-eum/self/traces_received/duration/sum]': - aggregation: SUM - tags: + traces_received: + aggregation: HISTOGRAM + attributes: is_error: true traces_span_size: - measure-type: LONG + instrument-type: COUNTER + value-type: LONG unit: amount views: - # the amount of elements in a single export execution - '[inspectit-eum/self/traces_span_size/sum]': + traces_span_size: aggregation: SUM - tags: + attributes: is_error: true - # settings for exposing resource timing metrics - resource-timing: - enabled: true - - security: # Enable/Disable Security enabled: false @@ -336,21 +309,22 @@ inspectit-eum-server: - "/boomerang/**" auth-provider: simple: - # Enable/Disable Provider + # enable/disable provider enabled: false - # Flag indicates if the directory should be watched for changes and tokens reloaded + # flag indicates if the directory should be watched for changes and tokens reloaded watch: true - # How often directory should be watched for changes + # how often directory should be watched for changes frequency: 60s - # The directory where token files are stored. Empty by default to force users to provide one + # the directory where token files are stored. Empty by default to force users to provide one token-directory: "" - # The name of the initial token file. If a name is provided file will be created with an initial token + # the name of the initial token file. If a name is provided file will be created with an initial token default-file-name: "default-token-file.yaml" -# SERVER properties -# Avoid exposing internals to outside via error page +# SERVER PROPERTIES server: + port: 8080 + # avoid exposing internals to outside via error page error: include-stacktrace: never include-binding-errors: never @@ -360,15 +334,15 @@ server: # ACTUATOR PROPERTIES management: - # Whether to enable or disable all endpoints by default. + # whether to enable or disable all endpoints by default endpoints.enabled-by-default: false endpoint: - # Whether to enable the health endpoint. + # whether to enable the health endpoint. health.enabled: true spring: autoconfigure: - # Disable ErrorMvcAutoConfiguration to avoid nasty exceptions if whitelabel error page is disabled + # disable ErrorMvcAutoConfiguration to avoid nasty exceptions if whitelabel error page is disabled # https://github.com/spring-projects/spring-boot/issues/2001 exclude: org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration diff --git a/src/test/java/io/opentelemetry/sdk/trace/OcelotSpanUtilsTest.java b/src/test/java/io/opentelemetry/sdk/trace/OcelotSpanUtilsTest.java index f1c0be1..75ca0ce 100644 --- a/src/test/java/io/opentelemetry/sdk/trace/OcelotSpanUtilsTest.java +++ b/src/test/java/io/opentelemetry/sdk/trace/OcelotSpanUtilsTest.java @@ -18,13 +18,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class OcelotSpanUtilsTest { +class OcelotSpanUtilsTest { @Nested class CreateSpanContext { @Test - public void validContext() { + void validContext() { SpanContext result = OcelotSpanUtils.createSpanContext("03c2a546267d1e90d70269bdc02babef", "c29e6dd2a1e1e7ae"); assertThat(result.isValid()).isTrue(); @@ -33,7 +33,7 @@ public void validContext() { } @Test - public void emptySpanId() { + void emptySpanId() { SpanContext result = OcelotSpanUtils.createSpanContext("03c2a546267d1e90d70269bdc02babef", ""); assertThat(result.isValid()).isFalse(); @@ -44,7 +44,7 @@ public void emptySpanId() { class ToStatusCode { @Test - public void toStatusCode() { + void toStatusCode() { assertThat(OcelotSpanUtils.toStatusCode(Status.StatusCode.STATUS_CODE_OK)).isEqualTo(StatusCode.OK); assertThat(OcelotSpanUtils.toStatusCode(Status.StatusCode.STATUS_CODE_ERROR)).isEqualTo(StatusCode.ERROR); assertThat(OcelotSpanUtils.toStatusCode(Status.StatusCode.STATUS_CODE_UNSET)).isEqualTo(StatusCode.UNSET); @@ -56,14 +56,14 @@ public void toStatusCode() { class ToAttributes { @Test - public void verifyEmptyArray() { + void verifyEmptyArray() { Attributes attributes = OcelotSpanUtils.toAttributes(Collections.emptyList()); assertTrue(attributes.isEmpty()); } @Test - public void verifyNullKeyValue() { + void verifyNullKeyValue() { List keyValues = Collections.singletonList(null); Attributes attributes = OcelotSpanUtils.toAttributes(keyValues); @@ -72,7 +72,7 @@ public void verifyNullKeyValue() { } @Test - public void verifyEmptyKeyValue() { + void verifyEmptyKeyValue() { KeyValue kv = KeyValue.newBuilder().build(); List keyValues = Collections.singletonList(kv); @@ -82,7 +82,7 @@ public void verifyEmptyKeyValue() { } @Test - public void verifyNoValue() { + void verifyNoValue() { KeyValue kv = KeyValue.newBuilder() .setKey("service.name") .build(); @@ -94,7 +94,7 @@ public void verifyNoValue() { } @Test - public void verifyNoKey() { + void verifyNoKey() { KeyValue kv = KeyValue.newBuilder() .setValue(AnyValue.newBuilder().setStringValue("frontend").build()) .build(); @@ -106,7 +106,7 @@ public void verifyNoKey() { } @Test - public void verifyValidAttributes() { + void verifyValidAttributes() { KeyValue kvString = KeyValue.newBuilder() .setKey("service.name") .setValue(AnyValue.newBuilder().setStringValue("frontend").build()) @@ -139,7 +139,7 @@ public void verifyValidAttributes() { } @Test - public void verifyArrayValue() { + void verifyArrayValue() { KeyValue kvArray = KeyValue.newBuilder() .setKey("browser.brands") .setValue(AnyValue.newBuilder().setArrayValue( diff --git a/src/test/java/rocks/inspectit/oce/eum/server/metrics/BeaconMetricManagerTest.java b/src/test/java/rocks/inspectit/oce/eum/server/metrics/BeaconMetricManagerTest.java deleted file mode 100644 index 6c39857..0000000 --- a/src/test/java/rocks/inspectit/oce/eum/server/metrics/BeaconMetricManagerTest.java +++ /dev/null @@ -1,158 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics; - -import com.google.common.collect.ImmutableMap; -import io.opencensus.stats.StatsRecorder; -import io.opencensus.stats.ViewManager; -import io.opencensus.tags.Tags; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; -import org.mockito.junit.jupiter.MockitoExtension; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.beacon.recorder.BeaconRecorder; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.BeaconMetricDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.MetricDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.ViewDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.tags.BeaconTagSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.events.RegisteredTagsEvent; - -import java.util.*; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -/** - * Tests {@link BeaconMetricManager} - */ -@ExtendWith(MockitoExtension.class) -public class BeaconMetricManagerTest { - - @InjectMocks - BeaconMetricManager beaconMetricManager; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - EumServerConfiguration configuration; - - @Mock - MeasuresAndViewsManager measuresAndViewsManager; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - StatsRecorder statsRecorder; - - @Mock - ViewManager viewManager; - - @Spy - List beaconRecorders = new ArrayList<>(Arrays.asList(mock(BeaconRecorder.class))); - - private final Set registeredTags = new HashSet<>(Arrays.asList("first", "second", "third")); - - @Nested - class ProcessUsedTags { - - @Test - void processOneUsedTag() { - Map beaconSettings = Collections.singletonMap("first", new BeaconTagSettings()); - when(configuration.getTags().getBeacon()).thenReturn(beaconSettings); - - beaconMetricManager.processUsedTags(new RegisteredTagsEvent(this, registeredTags)); - - assertThat(beaconMetricManager.registeredBeaconTags).containsExactly("first"); - } - - @Test - void processMultipleUsedTags() { - Map beaconSettings = ImmutableMap.of("first", new BeaconTagSettings(), "third", new BeaconTagSettings()); - when(configuration.getTags().getBeacon()).thenReturn(beaconSettings); - - beaconMetricManager.processUsedTags(new RegisteredTagsEvent(this, registeredTags)); - - assertThat(beaconMetricManager.registeredBeaconTags).containsExactlyInAnyOrder("first", "third"); - } - - @Test - void processNoTags() { - when(configuration.getTags().getBeacon()).thenReturn(Collections.emptyMap()); - - beaconMetricManager.processUsedTags(new RegisteredTagsEvent(this, registeredTags)); - - assertThat(beaconMetricManager.registeredBeaconTags).isEmpty(); - } - } - - @Nested - class ProcessBeacon { - - private Map definitionMap; - - @BeforeEach - void setupConfiguration() { - ViewDefinitionSettings view = ViewDefinitionSettings.builder() - .bucketBoundaries(Arrays.asList(0d, 1d)) - .aggregation(ViewDefinitionSettings.Aggregation.HISTOGRAM) - .tag("TAG_1", true) - .tag("TAG_2", true) - .build(); - Map views = new HashMap<>(); - views.put("Dummy metric name/HISTOGRAM", view); - - BeaconMetricDefinitionSettings dummyMetricDefinition = BeaconMetricDefinitionSettings.beaconMetricBuilder() - .valueExpression("{dummy_beacon_field}") - .description("Dummy description") - .type(MetricDefinitionSettings.MeasureType.DOUBLE) - .unit("ms") - .enabled(true) - .views(views) - .build(); - - definitionMap = new HashMap<>(); - definitionMap.put("Dummy metric name", dummyMetricDefinition); - } - - @BeforeEach - public void setupMocks() { - when(measuresAndViewsManager.getTagContext()).thenReturn(Tags.getTagger().emptyBuilder()); - } - - @Test - void verifyNoViewIsGeneratedWithEmptyBeacon() { - when(configuration.getDefinitions()).thenReturn(definitionMap); - HashMap beaconMap = new HashMap<>(); - - beaconMetricManager.processBeacon(Beacon.of(beaconMap)); - - verifyNoMoreInteractions(viewManager, statsRecorder); - } - - @Test - void verifyNoViewIsGeneratedWithFullBeacon() { - when(configuration.getDefinitions()).thenReturn(definitionMap); - HashMap beaconMap = new HashMap<>(); - beaconMap.put("fake_beacon_field", "12d"); - - beaconMetricManager.processBeacon(Beacon.of(beaconMap)); - - verifyNoMoreInteractions(viewManager, statsRecorder); - } - - @Test - void beaconRecordersProcessed() { - when(configuration.getDefinitions()).thenReturn(definitionMap); - HashMap beaconMap = new HashMap<>(); - beaconMap.put("fake_beacon_field", "12d"); - Beacon beacon = Beacon.of(beaconMap); - - beaconMetricManager.processBeacon(beacon); - - assertThat(beaconRecorders).allSatisfy(beaconRecorder -> { - verify(beaconRecorder).record(beacon); - verifyNoMoreInteractions(beaconRecorder); - }); - verifyNoMoreInteractions(viewManager, statsRecorder); - } - - } -} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/metrics/MeasuresAndViewsManagerTest.java b/src/test/java/rocks/inspectit/oce/eum/server/metrics/MeasuresAndViewsManagerTest.java deleted file mode 100644 index ba6620e..0000000 --- a/src/test/java/rocks/inspectit/oce/eum/server/metrics/MeasuresAndViewsManagerTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Sets; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.*; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.context.ApplicationEventPublisher; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.events.RegisteredTagsEvent; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -import java.util.*; - -@ExtendWith(MockitoExtension.class) -public class MeasuresAndViewsManagerTest { - - @InjectMocks - private MeasuresAndViewsManager manager = new MeasuresAndViewsManager(); - - @Mock - private ApplicationEventPublisher applicationEventPublisher; - - @Captor - private ArgumentCaptor eventArgumentCaptor; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private EumServerConfiguration configuration; - - @Nested - class ProcessRegisteredTags { - - @Test - void registerNoTag() { - when(configuration.getTags().getExtra()).thenReturn(Collections.emptyMap()); - - manager.processRegisteredTags(Collections.emptySet()); - - verify(applicationEventPublisher).publishEvent(eventArgumentCaptor.capture()); - assertThat(eventArgumentCaptor.getValue().getRegisteredTags()).isEmpty(); - assertThat(manager.registeredExtraTags).isEmpty(); - } - - @Test - void registerSingleTag() { - when(configuration.getTags().getExtra()).thenReturn(Collections.singletonMap("first", "value")); - - manager.processRegisteredTags(Collections.singleton("first")); - - verify(applicationEventPublisher).publishEvent(eventArgumentCaptor.capture()); - assertThat(eventArgumentCaptor.getValue().getRegisteredTags()).containsExactly("first"); - assertThat(manager.registeredExtraTags).containsExactly("first"); - } - - @Test - void registerMultipleTags() { - Map tagMap = ImmutableMap.of("first", "value", "second", "value"); - when(configuration.getTags().getExtra()).thenReturn(tagMap); - - manager.processRegisteredTags(Sets.newHashSet("first", "second")); - - verify(applicationEventPublisher).publishEvent(eventArgumentCaptor.capture()); - assertThat(eventArgumentCaptor.getValue().getRegisteredTags()).containsExactlyInAnyOrder("first", "second"); - assertThat(manager.registeredExtraTags).containsExactlyInAnyOrder("first", "second"); - } - - @Test - void registerTagsMultipleTimes() { - Map tagMap = ImmutableMap.of("first", "value", "second", "value"); - when(configuration.getTags().getExtra()).thenReturn(tagMap); - - // first execution - manager.processRegisteredTags(Collections.singleton("first")); - - assertThat(manager.registeredExtraTags).containsExactly("first"); - - // second execution - manager.processRegisteredTags(Collections.singleton("second")); - - assertThat(manager.registeredExtraTags).containsExactlyInAnyOrder("first", "second"); - verify(applicationEventPublisher, times(2)).publishEvent(eventArgumentCaptor.capture()); - RegisteredTagsEvent eventOne = eventArgumentCaptor.getAllValues().get(0); - RegisteredTagsEvent eventTwo = eventArgumentCaptor.getAllValues().get(1); - assertThat(eventOne.getRegisteredTags()).containsExactlyInAnyOrder("first"); - assertThat(eventTwo.getRegisteredTags()).containsExactlyInAnyOrder("first", "second"); - } - } - -} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/PercentileViewTest.java b/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/PercentileViewTest.java deleted file mode 100644 index 9c7599b..0000000 --- a/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/PercentileViewTest.java +++ /dev/null @@ -1,287 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; - -import com.google.common.collect.ImmutableSet; -import io.opencensus.common.Timestamp; -import io.opencensus.metrics.LabelKey; -import io.opencensus.metrics.LabelValue; -import io.opencensus.metrics.export.Metric; -import io.opencensus.metrics.export.MetricDescriptor; -import io.opencensus.metrics.export.Value; -import io.opencensus.tags.*; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class PercentileViewTest { - - @Nested - class Constructor { - - @Test - void noPercentilesAndMinMaxSpecified() { - assertThatThrownBy(() -> new PercentileView(false, false, Collections.emptySet(), Collections.emptySet(), 1000, "name", "unit", "description", 1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void invalidPercentile() { - assertThatThrownBy(() -> new PercentileView(true, true, new HashSet<>(Arrays.asList(1.0)), Collections.emptySet(), 1000, "name", "unit", "description", 1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void blankName() { - assertThatThrownBy(() -> new PercentileView(true, true, Collections.emptySet(), Collections.emptySet(), 1000, " ", "unit", "description", 1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void blankUnit() { - assertThatThrownBy(() -> new PercentileView(true, true, Collections.emptySet(), Collections.emptySet(), 1000, "name", " ", "description", 1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void blankDescription() { - assertThatThrownBy(() -> new PercentileView(true, true, Collections.emptySet(), Collections.emptySet(), 1000, "name", "unit", " ", 1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void invalidTimeWindow() { - assertThatThrownBy(() -> new PercentileView(true, true, Collections.emptySet(), Collections.emptySet(), 0, "name", "unit", "description", 1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void invalidBufferSize() { - assertThatThrownBy(() -> new PercentileView(true, true, Collections.emptySet(), Collections.emptySet(), 1000, "name", "unit", "description", 0)) - .isInstanceOf(IllegalArgumentException.class); - } - - } - - @Nested - class GetPercentileTag { - - @Test - void unnecessaryZeroesOmitted() { - String tag = PercentileView.getPercentileTag(0.5); - assertThat(tag).isEqualTo("0.5"); - } - - @Test - void tooLongValueRoundedDown() { - String tag = PercentileView.getPercentileTag(1.0 / 3); - assertThat(tag).isEqualTo("0.33333"); - } - - @Test - void tooLongValueRoundedUp() { - String tag = PercentileView.getPercentileTag(1.0 / 3 * 2); - assertThat(tag).isEqualTo("0.66667"); - } - } - - @Nested - class GetSeriesNames { - - @Test - void checkPercentileSeries() { - TimeWindowView view = new PercentileView(false, false, ImmutableSet.of(0.5), Collections.emptySet(), 10, "name", "unit", "description", 1); - - assertThat(view.getSeriesNames()).containsExactly("name"); - } - - @Test - void checkMinSeries() { - TimeWindowView view = new PercentileView(true, false, Collections.emptySet(), Collections.emptySet(), 10, "name", "unit", "description", 1); - - assertThat(view.getSeriesNames()).containsExactly("name_min"); - } - - @Test - void checkMaxSeries() { - TimeWindowView view = new PercentileView(false, true, Collections.emptySet(), Collections.emptySet(), 10, "name", "unit", "description", 1); - - assertThat(view.getSeriesNames()).containsExactly("name_max"); - } - - @Test - void checkAllPercentileSeries() { - TimeWindowView view = new PercentileView(true, true, ImmutableSet.of(0.5), Collections.emptySet(), 10, "name", "unit", "description", 1); - - assertThat(view.getSeriesNames()).containsExactlyInAnyOrder("name", "name_min", "name_max"); - } - - } - - @Nested - class ComputeMetrics { - - private TagContext createTagContext(String... keyValuePairs) { - TagContextBuilder builder = Tags.getTagger().emptyBuilder(); - for (int i = 0; i < keyValuePairs.length; i += 2) { - builder.putLocal(TagKey.create(keyValuePairs[i]), TagValue.create(keyValuePairs[i + 1])); - } - return builder.build(); - } - - @Test - void checkQuantileMetricDescriptor() { - TimeWindowView view = new PercentileView(false, false, ImmutableSet.of(0.5), ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 1); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection result = view.computeMetrics(queryTime); - assertThat(result).hasSize(1); - MetricDescriptor descriptor = result.iterator().next().getMetricDescriptor(); - - assertThat(descriptor.getName()).isEqualTo("name"); - assertThat(descriptor.getDescription()).isEqualTo("description"); - assertThat(descriptor.getLabelKeys()).containsExactly(LabelKey.create("my_tag", ""), LabelKey.create("quantile", "")); - assertThat(descriptor.getUnit()).isEqualTo("unit"); - assertThat(descriptor.getType()).isEqualTo(MetricDescriptor.Type.GAUGE_DOUBLE); - } - - @Test - void checkMinMetricDescriptor() { - TimeWindowView view = new PercentileView(true, false, Collections.emptySet(), ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 1); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection result = view.computeMetrics(queryTime); - assertThat(result).hasSize(1); - MetricDescriptor descriptor = result.iterator().next().getMetricDescriptor(); - - assertThat(descriptor.getName()).isEqualTo("name_min"); - assertThat(descriptor.getDescription()).isEqualTo("description"); - assertThat(descriptor.getLabelKeys()).containsExactly(LabelKey.create("my_tag", "")); - assertThat(descriptor.getUnit()).isEqualTo("unit"); - assertThat(descriptor.getType()).isEqualTo(MetricDescriptor.Type.GAUGE_DOUBLE); - } - - @Test - void checkMaxMetricDescriptor() { - TimeWindowView view = new PercentileView(false, true, Collections.emptySet(), ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 1); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection result = view.computeMetrics(queryTime); - assertThat(result).hasSize(1); - MetricDescriptor descriptor = result.iterator().next().getMetricDescriptor(); - - assertThat(descriptor.getName()).isEqualTo("name_max"); - assertThat(descriptor.getDescription()).isEqualTo("description"); - assertThat(descriptor.getLabelKeys()).containsExactly(LabelKey.create("my_tag", "")); - assertThat(descriptor.getUnit()).isEqualTo("unit"); - assertThat(descriptor.getType()).isEqualTo(MetricDescriptor.Type.GAUGE_DOUBLE); - } - - @Test - void checkMinimumMetric() { - TimeWindowView view = new PercentileView(true, false, Collections.emptySet(), ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 4); - - view.insertValue(42, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(3), createTagContext("my_tag", "bar")); - view.insertValue(100, Timestamp.fromMillis(4), createTagContext("my_tag", "bar")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(2).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(42)); - }); - }).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("bar")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(100)); - }); - }); - } - - @Test - void checkMaximumMetric() { - TimeWindowView view = new PercentileView(false, true, Collections.emptySet(), ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 4); - - view.insertValue(42, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(3), createTagContext("my_tag", "bar")); - view.insertValue(100, Timestamp.fromMillis(4), createTagContext("my_tag", "bar")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(2).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(99)); - }); - }).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("bar")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(101)); - }); - }); - } - - @Test - void checkPercentileMetrics() { - TimeWindowView view = new PercentileView(false, false, ImmutableSet.of(0.5, 0.9), ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 18); - - for (int i = 1; i < 10; i++) { - view.insertValue(10 + i, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(100 + i, Timestamp.fromMillis(3), createTagContext("my_tag", "bar")); - } - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(4).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo"), LabelValue.create("0.9")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(19)); - }); - }).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("bar"), LabelValue.create("0.9")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(109)); - }); - }).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo"), LabelValue.create("0.5")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(15)); - }); - }).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("bar"), LabelValue.create("0.5")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(105)); - }); - - }); - - } - - } -} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/SmoothedAverageViewTest.java b/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/SmoothedAverageViewTest.java deleted file mode 100644 index f273c33..0000000 --- a/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/SmoothedAverageViewTest.java +++ /dev/null @@ -1,336 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; - -import com.google.common.collect.ImmutableSet; -import io.opencensus.common.Timestamp; -import io.opencensus.metrics.LabelKey; -import io.opencensus.metrics.LabelValue; -import io.opencensus.metrics.export.Metric; -import io.opencensus.metrics.export.MetricDescriptor; -import io.opencensus.metrics.export.Value; -import io.opencensus.tags.*; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.Collection; -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class SmoothedAverageViewTest { - - @Nested - class Constructor { - - - @Test - void invalidDropUpper() { - assertThatThrownBy(() -> new SmoothedAverageView(-1.0, 0.0, Collections.emptySet(), 1000, "name", "unit", "description", 1)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - void invalidDropLower() { - assertThatThrownBy(() -> new SmoothedAverageView(0.05, 1.01, Collections.emptySet(), 1000, "name", "unit", "description", 1)) - .isInstanceOf(IllegalArgumentException.class); - } - - } - - @Nested - class GetSeriesNames { - - @Test - void checkSmoothedAverageSeries() { - TimeWindowView view = new SmoothedAverageView(0.0, 0.05, Collections.emptySet(), 10, "name", "unit", "description", 1); - - assertThat(view.getSeriesNames()).containsExactly("name"); - } - - } - - @Nested - class ComputeMetrics { - - private TagContext createTagContext(String... keyValuePairs) { - TagContextBuilder builder = Tags.getTagger().emptyBuilder(); - for (int i = 0; i < keyValuePairs.length; i += 2) { - builder.putLocal(TagKey.create(keyValuePairs[i]), TagValue.create(keyValuePairs[i + 1])); - } - return builder.build(); - } - - @Test - void checkSmoothedAverageMetricDescriptor() { - TimeWindowView view = new SmoothedAverageView(0.05, 0.05, ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 1); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection result = view.computeMetrics(queryTime); - assertThat(result).hasSize(1); - MetricDescriptor descriptor = result.iterator().next().getMetricDescriptor(); - - assertThat(descriptor.getName()).isEqualTo("name"); - assertThat(descriptor.getDescription()).isEqualTo("description"); - assertThat(descriptor.getLabelKeys()).containsExactly(LabelKey.create("my_tag", "")); - assertThat(descriptor.getUnit()).isEqualTo("unit"); - assertThat(descriptor.getType()).isEqualTo(MetricDescriptor.Type.GAUGE_DOUBLE); - } - - @Test - void checkSmoothedAverageMetric() { - TimeWindowView view = new SmoothedAverageView(0.05, 0.05, ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 12); - - view.insertValue(42, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - - view.insertValue(101, Timestamp.fromMillis(3), createTagContext("my_tag", "bar")); - view.insertValue(150, Timestamp.fromMillis(4), createTagContext("my_tag", "bar")); - view.insertValue(171, Timestamp.fromMillis(4), createTagContext("my_tag", "bar")); - view.insertValue(250, Timestamp.fromMillis(5), createTagContext("my_tag", "bar")); - view.insertValue(99, Timestamp.fromMillis(5), createTagContext("my_tag", "bar")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "bar")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(2).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(71.75)); - }); - }).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("bar")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(130.75)); - }); - }); - } - - @Test - void checkSmoothedAverageMetricGreatIndex() { - TimeWindowView view = new SmoothedAverageView(0.2, 0.2, ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 24); - - view.insertValue(42, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(42, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(4), createTagContext("my_tag", "foo")); - view.insertValue(171, Timestamp.fromMillis(4), createTagContext("my_tag", "foo")); - view.insertValue(250, Timestamp.fromMillis(5), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(5), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(171, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(250, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(8), createTagContext("my_tag", "foo")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(1).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(97.14285714285714)); - }); - }); - } - - @Test - void checkSmoothedAverageMetricDropOnlyLower() { - TimeWindowView view = new SmoothedAverageView(0.0, 0.2, ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 24); - - view.insertValue(42, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(42, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(4), createTagContext("my_tag", "foo")); - view.insertValue(171, Timestamp.fromMillis(4), createTagContext("my_tag", "foo")); - view.insertValue(250, Timestamp.fromMillis(5), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(5), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(171, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(250, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(8), createTagContext("my_tag", "foo")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(1).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(123.78947368421052)); - }); - }); - } - - @Test - void checkSmoothedAverageMetricDropOnlyUpper() { - TimeWindowView view = new SmoothedAverageView(0.11, 0.0, ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 24); - - view.insertValue(42, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(42, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(4), createTagContext("my_tag", "foo")); - view.insertValue(171, Timestamp.fromMillis(4), createTagContext("my_tag", "foo")); - view.insertValue(250, Timestamp.fromMillis(5), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(5), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(171, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(250, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(8), createTagContext("my_tag", "foo")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(1).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(92.04761904761905)); - }); - }); - } - - @Test - void checkSmoothedAverageMetricDropGreaterUpperIndex() { - TimeWindowView view = new SmoothedAverageView(0.9, 0.2, ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 24); - - view.insertValue(42, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(42, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(50, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(68, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(70, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(4), createTagContext("my_tag", "foo")); - view.insertValue(171, Timestamp.fromMillis(4), createTagContext("my_tag", "foo")); - view.insertValue(250, Timestamp.fromMillis(5), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(5), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(6), createTagContext("my_tag", "foo")); - view.insertValue(171, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(250, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(99, Timestamp.fromMillis(7), createTagContext("my_tag", "foo")); - view.insertValue(101, Timestamp.fromMillis(8), createTagContext("my_tag", "foo")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(1).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(68.0)); - }); - }); - } - - @Test - void checkSmoothedAverageMetricDropNothing() { - TimeWindowView view = new SmoothedAverageView(0.0, 0.0, ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 4); - - view.insertValue(80, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(87, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - view.insertValue(100, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - view.insertValue(150, Timestamp.fromMillis(3), createTagContext("my_tag", "foo")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(1).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(104.25)); - }); - }); - } - - @Test - void checkSmoothedAverageMetricDropSmallValues() { - TimeWindowView view = new SmoothedAverageView(0.1, 0.0, ImmutableSet.of("my_tag"), 10, "name", "unit", "description", 4); - - view.insertValue(116, Timestamp.fromMillis(1), createTagContext("my_tag", "foo")); - view.insertValue(125, Timestamp.fromMillis(2), createTagContext("my_tag", "foo")); - - Timestamp queryTime = Timestamp.fromMillis(10); - Collection results = view.computeMetrics(queryTime); - assertThat(results).hasSize(1); - - Metric result = results.iterator().next(); - - assertThat(result.getTimeSeriesList()).hasSize(1).anySatisfy(series -> { - assertThat(series.getLabelValues()).containsExactly(LabelValue.create("foo")); - assertThat(series.getPoints()).hasSize(1).anySatisfy(pt -> { - assertThat(pt.getTimestamp()).isEqualTo(queryTime); - assertThat(pt.getValue()).isEqualTo(Value.doubleValue(116)); - }); - }); - } - - } -} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowViewManagerTest.java b/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowViewManagerTest.java deleted file mode 100644 index 299269a..0000000 --- a/src/test/java/rocks/inspectit/oce/eum/server/metrics/percentiles/TimeWindowViewManagerTest.java +++ /dev/null @@ -1,571 +0,0 @@ -package rocks.inspectit.oce.eum.server.metrics.percentiles; - -import io.opencensus.common.Scope; -import io.opencensus.metrics.LabelKey; -import io.opencensus.metrics.LabelValue; -import io.opencensus.metrics.export.Metric; -import io.opencensus.metrics.export.MetricDescriptor; -import io.opencensus.metrics.export.TimeSeries; -import io.opencensus.metrics.export.Value; -import io.opencensus.tags.TagKey; -import io.opencensus.tags.TagValue; -import io.opencensus.tags.Tags; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.util.*; -import java.util.function.Supplier; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.lenient; - -@ExtendWith(MockitoExtension.class) -public class TimeWindowViewManagerTest { - - private TimeWindowViewManager viewManager; - - private Supplier clock; - - @BeforeEach - @SuppressWarnings("unchecked") - void init() { - clock = Mockito.mock(Supplier.class); - lenient().doReturn(0L).when(clock).get(); - viewManager = new TimeWindowViewManager(clock); - viewManager.init(); - } - - @AfterEach - void destroy() { - viewManager.destroy(); - } - - private void awaitMetricsProcessing() { - await().until(() -> - viewManager.worker.recordsQueue.isEmpty() - && viewManager.worker.worker.getState() == Thread.State.WAITING); - } - - private void assertTotalSeriesCount(Collection metrics, long expectedSeriesCount) { - long count = metrics - .stream() - .flatMap(metric -> metric.getTimeSeriesList().stream()) - .count(); - assertThat(count).isEqualTo(expectedSeriesCount); - } - - private void assertContainsMetric(Collection metrics, String name, double value, String... tagKeyValuePairs) { - assertThat(metrics) - .anySatisfy(m -> assertThat(m.getMetricDescriptor().getName()).isEqualTo(name)); - Metric metric = metrics.stream() - .filter(m -> m.getMetricDescriptor().getName().equals(name)) - .findFirst() - .get(); - - List keys = metric.getMetricDescriptor().getLabelKeys(); - assertThat(keys).hasSize(tagKeyValuePairs.length / 2); - List values = new ArrayList<>(); - keys.forEach(label -> values.add(null)); - - for (int i = 0; i < tagKeyValuePairs.length; i += 2) { - LabelKey tagKey = LabelKey.create(tagKeyValuePairs[i], ""); - LabelValue tagValue = LabelValue.create(tagKeyValuePairs[i + 1]); - assertThat(keys).contains(tagKey); - values.set(keys.indexOf(tagKey), tagValue); - } - - assertThat(metric.getTimeSeriesList()) - .anySatisfy(ts -> assertThat(ts.getLabelValues()).isEqualTo(values)); - TimeSeries ts = metric.getTimeSeriesList().stream() - .filter(series -> series.getLabelValues().equals(values)) - .findFirst().get(); - - assertThat(ts.getPoints()).hasSize(1); - assertThat(ts.getPoints().get(0).getValue()).isEqualTo(Value.doubleValue(value)); - - } - - @Nested - class GetMeasureForSeries { - - @Test - void noViewsRegistered() { - assertThat(viewManager.getMeasureNameForSeries("test")).isNull(); - } - - @Test - void singleViewRegisteredPercentiles() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Arrays.asList(0.5, 0.95), 15000, Collections.emptyList(), 1); - - assertThat(viewManager.getMeasureNameForSeries("my/view")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_smoothed_average")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/view_min")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_max")).isNull(); - } - - @Test - void singleViewRegisteredSmoothedAverage() { - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/view_smoothed_average", "ms", "foo", - 0.05, 0.05, 15000, Collections.emptyList(), 1); - - assertThat(viewManager.getMeasureNameForSeries("my/view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/view_smoothed_average")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_max")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/view_min")).isNull(); - } - - @Test - void multipleViewsRegistered() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Arrays.asList(0.5, 0.95), 15000, Collections.emptyList(), 1); - - assertThat(viewManager.getMeasureNameForSeries("my/view")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_min")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_max")).isNull(); - - viewManager.createOrUpdatePercentileView("my/other_measure", "my/other_view", "ms", "foo", - false, true, Collections.emptySet(), 15000, Collections.emptyList(), 1); - - assertThat(viewManager.getMeasureNameForSeries("my/view")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_min")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_max")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_min")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_max")).isEqualTo("my/other_measure"); - - viewManager.createOrUpdateSmoothedAverageView("my/further_measure", "my/further_view_smoothed_average", "ms", "foo", - 0.05, 0.1, 15000, Collections.emptyList(), 1); - - assertThat(viewManager.getMeasureNameForSeries("my/view")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_smoothed_average")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/view_min")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_max")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_smoothed_average")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_min")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_max")).isEqualTo("my/other_measure"); - assertThat(viewManager.getMeasureNameForSeries("my/further_view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_smoothed_average")).isEqualTo("my/further_measure"); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_min")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_max")).isNull(); - } - - @Test - void viewRemoved() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Arrays.asList(0.5, 0.95), 15000, Collections.emptyList(), 1); - viewManager.createOrUpdatePercentileView("my/other_measure", "my/other_view", "ms", "foo", - false, true, Collections.emptySet(), 15000, Collections.emptyList(), 1); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.07, 0.5, 15000, Collections.emptyList(), 1); - - assertThat(viewManager.getMeasureNameForSeries("my/view")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_smoothed_average")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/view_min")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/view_max")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_smoothed_average")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_min")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_max")).isEqualTo("my/other_measure"); - assertThat(viewManager.getMeasureNameForSeries("my/further_view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_smoothed_average")).isEqualTo("my/measure"); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_min")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_max")).isNull(); - - viewManager.removeView("my/measure", "my/view"); - viewManager.removeView("my/measure", "my/further_view_smoothed_average"); - - assertThat(viewManager.getMeasureNameForSeries("my/view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/view_min")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/view_max")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_min")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/other_view_max")).isEqualTo("my/other_measure"); - assertThat(viewManager.getMeasureNameForSeries("my/further_view")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_smoothed_average")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_min")).isNull(); - assertThat(viewManager.getMeasureNameForSeries("my/further_view_max")).isNull(); - } - } - - @Nested - class ComputeMetrics { - - @Test - void testNoData() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, true, Arrays.asList(0.5, 0.95), 15000, Collections.emptyList(), 1); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.07, 0.5, 15000, Collections.emptyList(), 1); - - Collection result = viewManager.computeMetrics(); - - assertThat(result).hasSize(4); - assertTotalSeriesCount(result, 0); - } - - - @Test - void testWithData() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, true, Arrays.asList(0.5, 0.95), 15000, Collections.emptyList(), 100); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.2, 0.2, 15000, Collections.emptyList(), 100); - - for (int i = 1; i < 100; i++) { - doReturn((long) i).when(clock).get(); - viewManager.recordMeasurement("my/measure", i); - } - awaitMetricsProcessing(); - - doReturn(10000L).when(clock).get(); - Collection result = viewManager.computeMetrics(); - - assertThat(result).hasSize(4); - assertTotalSeriesCount(result, 5); - assertContainsMetric(result, "my/view_min", 1); - assertContainsMetric(result, "my/view_max", 99); - assertContainsMetric(result, "my/further_view_smoothed_average", 50.0); - assertContainsMetric(result, "my/view", 50, "quantile", "0.5"); - assertContainsMetric(result, "my/view", 95, "quantile", "0.95"); - } - - @Test - void testMultiSeriesData() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, true, Arrays.asList(0.5, 0.95), 15000, Arrays.asList("tag1", "tag2"), 198); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.2, 0.2, 15000, Arrays.asList("tag1", "tag2"), 198); - - for (int i = 1; i < 100; i++) { - doReturn((long) i).when(clock).get(); - viewManager.recordMeasurement("my/measure", i); - try (Scope s = Tags.getTagger().emptyBuilder() - .putLocal(TagKey.create("tag1"), TagValue.create("foo")) - .putLocal(TagKey.create("tag2"), TagValue.create("bar")).buildScoped()) { - viewManager.recordMeasurement("my/measure", 1000 + i); - } - } - awaitMetricsProcessing(); - - doReturn(10000L).when(clock).get(); - Collection result = viewManager.computeMetrics(); - - assertThat(result).hasSize(4); - assertTotalSeriesCount(result, 10); - assertContainsMetric(result, "my/view_min", 1, "tag1", "", "tag2", ""); - assertContainsMetric(result, "my/view_max", 99, "tag1", "", "tag2", ""); - assertContainsMetric(result, "my/further_view_smoothed_average", 50.0, "tag1", "", "tag2", ""); - assertContainsMetric(result, "my/view", 50, "tag1", "", "tag2", "", "quantile", "0.5"); - assertContainsMetric(result, "my/view", 95, "tag1", "", "tag2", "", "quantile", "0.95"); - assertContainsMetric(result, "my/view_min", 1001, "tag1", "foo", "tag2", "bar"); - assertContainsMetric(result, "my/view_max", 1099, "tag1", "foo", "tag2", "bar"); - assertContainsMetric(result, "my/further_view_smoothed_average", 1050.0, "tag1", "foo", "tag2", "bar"); - assertContainsMetric(result, "my/view", 1050, "tag1", "foo", "tag2", "bar", "quantile", "0.5"); - assertContainsMetric(result, "my/view", 1095, "tag1", "foo", "tag2", "bar", "quantile", "0.95"); - } - - @Test - void testMultipleViewsForSameMeasure() { - viewManager.createOrUpdatePercentileView("my/measure", "viewA", "ms", "foo", - true, false, Collections.emptyList(), 1, Collections.emptyList(), 1); - viewManager.createOrUpdatePercentileView("my/measure", "viewB", "ms", "foo", - true, false, Collections.emptyList(), 1, Arrays.asList("tag1"), 1); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "viewC_smoothed_average", "ms", "foo", - 0.15, 0.45, 1, Arrays.asList("tag2"), 1); - - viewManager.recordMeasurement("my/measure", 1); - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - - assertThat(result).hasSize(3); - assertTotalSeriesCount(result, 3); - assertContainsMetric(result, "viewA_min", 1); - assertContainsMetric(result, "viewB_min", 1, "tag1", ""); - assertContainsMetric(result, "viewC_smoothed_average", 1, "tag2", ""); - } - - @Test - void testWithStaleData() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, true, Arrays.asList(0.5, 0.95), 15000, Collections.emptyList(), 99); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.05, 0.05, 15000, Collections.emptyList(), 99); - - for (int i = 1; i < 100; i++) { - doReturn((long) i).when(clock).get(); - viewManager.recordMeasurement("my/measure", i); - } - awaitMetricsProcessing(); - doReturn(20000L).when(clock).get(); - - Collection result = viewManager.computeMetrics(); - - assertThat(result).hasSize(4); - assertTotalSeriesCount(result, 0); - } - - @Test - void testDroppingBecauseBufferIsFull() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Arrays.asList("tag"), 10); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.05, 0.05, 1, Arrays.asList("tag"), 10); - - doReturn(0L).when(clock).get(); - try (Scope s = Tags.getTagger().emptyBuilder() - .putLocal(TagKey.create("tag"), TagValue.create("foo")) - .buildScoped()) { - for (int i = 0; i < 20; i++) { - viewManager.recordMeasurement("my/measure", 20 - i); - } - } - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(2); - assertTotalSeriesCount(result, 2); - assertContainsMetric(result, "my/view_min", 11.0, "tag", "foo"); - assertContainsMetric(result, "my/further_view_smoothed_average", 15.5, "tag", "foo"); - } - - @Test - void testDroppingPreventedThroughCleanupTask() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Arrays.asList("tag"), 10); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.05, 0.05, 1, Arrays.asList("tag"), 10); - - doReturn(0L).when(clock).get(); - try (Scope s = Tags.getTagger().emptyBuilder() - .putLocal(TagKey.create("tag"), TagValue.create("foo")) - .buildScoped()) { - for (int i = 0; i < 10; i++) { - viewManager.recordMeasurement("my/measure", i); - } - } - awaitMetricsProcessing(); - doReturn(10000L).when(clock).get(); - try (Scope s = Tags.getTagger().emptyBuilder() - .putLocal(TagKey.create("tag"), TagValue.create("bar")) - .buildScoped()) { - viewManager.recordMeasurement("my/measure", 1000); - } - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - - assertThat(result).hasSize(2); - assertTotalSeriesCount(result, 2); - assertContainsMetric(result, "my/view_min", 1000, "tag", "bar"); - assertContainsMetric(result, "my/further_view_smoothed_average", 1000, "tag", "bar"); - } - } - - @Nested - class CreateOrUpdateView { - - @Test - void updateMetricDescription() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Collections.emptyList(), 100); - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "s", "bar", - true, false, Collections.emptyList(), 1, Collections.emptyList(), 100); - - viewManager.recordMeasurement("my/measure", 42); - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(1); - MetricDescriptor md = result.iterator().next().getMetricDescriptor(); - - assertThat(md.getName()).isEqualTo("my/view_min"); - assertThat(md.getUnit()).isEqualTo("s"); - assertThat(md.getDescription()).isEqualTo("bar"); - } - - @Test - void updateMinMaxPercentiles() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Collections.emptyList(), 100); - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "s", "bar", - false, true, Arrays.asList(0.5), 1, Collections.emptyList(), 100); - - viewManager.recordMeasurement("my/measure", 42); - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(2); - assertContainsMetric(result, "my/view_max", 42); - assertContainsMetric(result, "my/view", 42, "quantile", "0.5"); - } - - @Test - void updateSmoothedAverage() { - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/view_smoothed_average", "ms", "foo", - 0.2, 0.2, 1, Collections.emptyList(), 100); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/view_smoothed_average", "s", "bar", - 0.0, 0.05, 1, Arrays.asList("tag1", "tag2"), 100); - - try (Scope s = Tags.getTagger().emptyBuilder() - .putLocal(TagKey.create("tag1"), TagValue.create("foo")) - .putLocal(TagKey.create("tag2"), TagValue.create("bar")).buildScoped()) { - viewManager.recordMeasurement("my/measure", 42); - viewManager.recordMeasurement("my/measure", 43); - viewManager.recordMeasurement("my/measure", 44); - viewManager.recordMeasurement("my/measure", 45); - } - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(1); - assertContainsMetric(result, "my/view_smoothed_average", 44, "tag1", "foo", "tag2", "bar"); - } - - @Test - void updateTimeWindow() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Collections.emptyList(), 100); - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 100, Collections.emptyList(), 100); - - doReturn(0L).when(clock).get(); - viewManager.recordMeasurement("my/measure", 42); - awaitMetricsProcessing(); - - doReturn(99L).when(clock).get(); - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(1); - assertContainsMetric(result, "my/view_min", 42); - } - - @Test - void updateBufferSize() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 100, Collections.emptyList(), 100); - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 100, Collections.emptyList(), 1); - - doReturn(0L).when(clock).get(); - viewManager.recordMeasurement("my/measure", 100); - viewManager.recordMeasurement("my/measure", 10); - awaitMetricsProcessing(); - - doReturn(99L).when(clock).get(); - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(1); - assertContainsMetric(result, "my/view_min", 100); //because the second point has been dropped - } - - @Test - void updateTags() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Collections.emptyList(), 100); - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Arrays.asList("tag1", "tag2"), 100); - - try (Scope s = Tags.getTagger().emptyBuilder() - .putLocal(TagKey.create("tag1"), TagValue.create("foo")) - .putLocal(TagKey.create("tag2"), TagValue.create("bar")).buildScoped()) { - viewManager.recordMeasurement("my/measure", 42); - } - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(1); - assertContainsMetric(result, "my/view_min", 42, "tag1", "foo", "tag2", "bar"); - } - - @Test - void updateWithValueRecorded() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Collections.emptyList(), 100); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.2, 0.2, 1, Collections.emptyList(), 100); - - viewManager.recordMeasurement("my/measure", 42); - awaitMetricsProcessing(); - - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Arrays.asList("tag1", "tag2"), 100); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "s", "bar", - 0.0, 0.05, 1, Arrays.asList("tag1", "tag2"), 100); - - try (Scope s = Tags.getTagger().emptyBuilder() - .putLocal(TagKey.create("tag1"), TagValue.create("foo")) - .putLocal(TagKey.create("tag2"), TagValue.create("bar")).buildScoped()) { - viewManager.recordMeasurement("my/measure", 42); - } - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(2); - assertContainsMetric(result, "my/view_min", 42, "tag1", "foo", "tag2", "bar"); - assertContainsMetric(result, "my/further_view_smoothed_average", 42, "tag1", "foo", "tag2", "bar"); - } - - @Test - void updatePercentileToSmoothedAverage() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 1, Collections.emptyList(), 100); - - viewManager.recordMeasurement("my/measure", 42); - awaitMetricsProcessing(); - - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/view", "s", "bar", - 0.0, 0.05, 1, Arrays.asList("tag1", "tag2"), 100); - - try (Scope s = Tags.getTagger().emptyBuilder() - .putLocal(TagKey.create("tag1"), TagValue.create("foo")) - .putLocal(TagKey.create("tag2"), TagValue.create("bar")).buildScoped()) { - viewManager.recordMeasurement("my/measure", 42); - } - awaitMetricsProcessing(); - - Collection result = viewManager.computeMetrics(); - assertThat(result).hasSize(1); - assertContainsMetric(result, "my/view", 42, "tag1", "foo", "tag2", "bar"); - } - } - - @Nested - class RemoveView { - - @Test - void checkViewRemoved() { - viewManager.createOrUpdatePercentileView("my/measure", "my/view", "ms", "foo", - true, false, Collections.emptyList(), 100, Collections.emptyList(), 100); - viewManager.createOrUpdateSmoothedAverageView("my/measure", "my/further_view_smoothed_average", "ms", "foo", - 0.2, 0.2, 1, Collections.emptyList(), 100); - - viewManager.recordMeasurement("my/measure", 42); - awaitMetricsProcessing(); - - boolean removed = viewManager.removeView("my/measure", "my/view"); - boolean removed_smoothed_average = viewManager.removeView("my/measure", "my/further_view_smoothed_average"); - - Collection result = viewManager.computeMetrics(); - assertThat(viewManager.areAnyViewsRegisteredForMeasure("my/measure")).isFalse(); - assertThat(removed).isTrue(); - assertThat(removed_smoothed_average).isTrue(); - assertThat(result).isEmpty(); - - } - - @Test - void removeNonExistingView() { - boolean removed = viewManager.removeView("my/measure", "my/view"); - assertThat(removed).isFalse(); - } - - } -} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/utils/TagUtilsTest.java b/src/test/java/rocks/inspectit/oce/eum/server/utils/TagUtilsTest.java deleted file mode 100644 index c914660..0000000 --- a/src/test/java/rocks/inspectit/oce/eum/server/utils/TagUtilsTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package rocks.inspectit.oce.eum.server.utils; - -import io.opencensus.tags.TagValue; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TagUtilsTest { - - @Test - public void createTagValue() { - assertThat(TagUtils.createTagValue("my-tag-key", "my-tag-value")).isEqualTo(TagValue.create("my-tag-value")); - } - - @Test - public void createTagValue_tooLong() { - assertThat(TagUtils.createTagValue("my-tag-key", "this-value-is-over-255-characters-long ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------")) - .isEqualTo(TagValue.create("")); - } - - @Test - public void createTagValue_nonPrintableCharacter() { - assertThat(TagUtils.createTagValue("my-tag-key", "non-printable-character-\u007f")).isEqualTo(TagValue.create("")); - } - -} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/arithmetic/ArithmeticExpressionTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/arithmetic/ArithmeticExpressionTest.java similarity index 79% rename from src/test/java/rocks/inspectit/oce/eum/server/arithmetic/ArithmeticExpressionTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/arithmetic/ArithmeticExpressionTest.java index dfa5529..2a5574d 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/arithmetic/ArithmeticExpressionTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/arithmetic/ArithmeticExpressionTest.java @@ -1,18 +1,18 @@ -package rocks.inspectit.oce.eum.server.arithmetic; +package rocks.inspectit.ocelot.eum.server.arithmetic; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; class ArithmeticExpressionTest { @Nested - public class Eval { + class Eval { @Test - public void evaluateMinus() { + void evaluateMinus() { ArithmeticExpression expression = new ArithmeticExpression("1565601241723 - 1565601241693"); double result = expression.eval(); @@ -21,7 +21,7 @@ public void evaluateMinus() { } @Test - public void evuluatePlus() { + void evaluatePlus() { ArithmeticExpression expression = new ArithmeticExpression("10 + 20"); double result = expression.eval(); @@ -30,7 +30,7 @@ public void evuluatePlus() { } @Test - public void evaluateParentheses() { + void evaluateParentheses() { ArithmeticExpression expression = new ArithmeticExpression("(1+1)*5"); double result = expression.eval(); @@ -39,7 +39,7 @@ public void evaluateParentheses() { } @Test - public void invalidExpression() { + void invalidExpression() { ArithmeticExpression expression = new ArithmeticExpression("(1+*5"); assertThatExceptionOfType(RuntimeException.class) @@ -47,4 +47,4 @@ public void invalidExpression() { .withMessage("Could not solve expression '(1+*5'. Unexpected character at position 3: *"); } } -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/arithmetic/RawExpressionTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/arithmetic/RawExpressionTest.java similarity index 86% rename from src/test/java/rocks/inspectit/oce/eum/server/arithmetic/RawExpressionTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/arithmetic/RawExpressionTest.java index 7749500..f5d89f7 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/arithmetic/RawExpressionTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/arithmetic/RawExpressionTest.java @@ -1,8 +1,8 @@ -package rocks.inspectit.oce.eum.server.arithmetic; +package rocks.inspectit.ocelot.eum.server.arithmetic; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import java.util.Collections; import java.util.HashMap; @@ -13,10 +13,10 @@ class RawExpressionTest { @Nested - public class Constructor { + class Constructor { @Test - public void withoutFields() { + void withoutFields() { RawExpression expression = new RawExpression("2"); assertThat(expression.getFields()).isEmpty(); @@ -24,7 +24,7 @@ public void withoutFields() { } @Test - public void withReference() { + void withReference() { RawExpression expression = new RawExpression("{field}"); assertThat(expression.getFields()).containsExactly("field"); @@ -32,7 +32,7 @@ public void withReference() { } @Test - public void withReferences() { + void withReferences() { RawExpression expression = new RawExpression("{field} - {field.second}"); assertThat(expression.getFields()).containsExactly("field", "field.second"); @@ -40,7 +40,7 @@ public void withReferences() { } @Test - public void withSameReferences() { + void withSameReferences() { RawExpression expression = new RawExpression("{field} - {field}"); assertThat(expression.getFields()).containsExactly("field"); @@ -49,10 +49,10 @@ public void withSameReferences() { } @Nested - public class IsSolvable { + class IsSolvable { @Test - public void notSolvable() { + void notSolvable() { Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); RawExpression expression = new RawExpression("{field} - {field.second}"); @@ -62,7 +62,7 @@ public void notSolvable() { } @Test - public void isSolvable() { + void isSolvable() { Map map = new HashMap<>(); map.put("field", "5"); map.put("field.second", "10"); @@ -76,10 +76,10 @@ public void isSolvable() { } @Nested - public class Solve { + class Solve { @Test - public void calculation() { + void calculation() { Map map = new HashMap<>(); map.put("field", "5"); map.put("field.second", "10"); @@ -92,7 +92,7 @@ public void calculation() { } @Test - public void directReference() { + void directReference() { Map map = new HashMap<>(); map.put("field", "5"); map.put("field.second", "10"); @@ -105,7 +105,7 @@ public void directReference() { } @Test - public void missingField() { + void missingField() { Map map = new HashMap<>(); map.put("field", "5"); Beacon beacon = Beacon.of(map); @@ -117,7 +117,7 @@ public void missingField() { } @Test - public void invalidExpression() { + void invalidExpression() { Map map = new HashMap<>(); map.put("field", "5"); Beacon beacon = Beacon.of(map); @@ -128,4 +128,4 @@ public void invalidExpression() { assertThat(result).isNull(); } } -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/BeaconTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/BeaconTest.java similarity index 79% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/BeaconTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/BeaconTest.java index 05897ed..3be929c 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/BeaconTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/BeaconTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.beacon; +package rocks.inspectit.ocelot.eum.server.beacon; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -12,12 +12,12 @@ class BeaconTest { @Nested - public class Contains { + class Contains { - private Beacon beacon; + Beacon beacon; @BeforeEach - private void before() { + void before() { HashMap map = new HashMap<>(); map.put("first", "1"); map.put("second", "2"); @@ -25,14 +25,14 @@ private void before() { } @Test - public void containsField() { + void containsField() { boolean result = beacon.contains("first"); assertThat(result).isTrue(); } @Test - public void merge() { + void merge() { HashMap map = new HashMap<>(); map.put("first", "3"); map.put("third", "4"); @@ -43,38 +43,38 @@ public void merge() { } @Test - public void containsFields() { + void containsFields() { boolean result = beacon.contains("first", "second"); assertThat(result).isTrue(); } @Test - public void doesNotContainField() { + void doesNotContainField() { boolean result = beacon.contains("third"); assertThat(result).isFalse(); } @Test - public void doesNotContainFields() { + void doesNotContainFields() { boolean result = beacon.contains("first", "third"); assertThat(result).isFalse(); } @Test - public void containsFieldsAsList() { + void containsFieldsAsList() { boolean result = beacon.contains(Arrays.asList("first", "second")); assertThat(result).isTrue(); } @Test - public void doesNotContainFieldsAsList() { + void doesNotContainFieldsAsList() { boolean result = beacon.contains(Arrays.asList("first", "third")); assertThat(result).isFalse(); } } -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/BmrBeaconProcessorTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BmrBeaconProcessorTest.java similarity index 86% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/BmrBeaconProcessorTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BmrBeaconProcessorTest.java index bd54960..9952c04 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/BmrBeaconProcessorTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/BmrBeaconProcessorTest.java @@ -1,11 +1,11 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import java.util.Arrays; import java.util.Collections; @@ -19,16 +19,16 @@ class BmrBeaconProcessorTest { private BmrBeaconProcessor processor; @Nested - public class Process { + class Process { - private final String[] keyNames = Arrays.stream(BmrBeaconProcessor.VALUE_NAMES) + final String[] keyNames = Arrays.stream(BmrBeaconProcessor.VALUE_NAMES) .map(valueName -> BmrBeaconProcessor.ATTRIBUTE_KEY + "." + valueName) .toArray(String[]::new); - private final int maxExpectedSize = keyNames.length + 1; + final int maxExpectedSize = keyNames.length + 1; @Test - public void noRtBmrAttribute() { + void noRtBmrAttribute() { Beacon beacon = Beacon.of(Collections.singletonMap("key", "")); Beacon result = processor.process(beacon); @@ -37,7 +37,7 @@ public void noRtBmrAttribute() { } @Test - public void rtBmrWithoutValues() { + void rtBmrWithoutValues() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "")); Beacon result = processor.process(beacon); @@ -46,7 +46,7 @@ public void rtBmrWithoutValues() { } @Test - public void rtBmrWithOneValue() { + void rtBmrWithOneValue() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "123")); Beacon result = processor.process(beacon); @@ -57,7 +57,7 @@ public void rtBmrWithOneValue() { } @Test - public void rtBmrWithInvalidValue() { + void rtBmrWithInvalidValue() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "foo")); Beacon result = processor.process(beacon); @@ -68,7 +68,7 @@ public void rtBmrWithInvalidValue() { } @Test - public void rtBmrWithZeroValue() { + void rtBmrWithZeroValue() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "0")); Beacon result = processor.process(beacon); @@ -79,7 +79,7 @@ public void rtBmrWithZeroValue() { } @Test - public void rtBmrWithZeroSum() { + void rtBmrWithZeroSum() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "0,0")); Beacon result = processor.process(beacon); @@ -90,7 +90,7 @@ public void rtBmrWithZeroSum() { } @Test - public void rtBmrWithMultipleValues() { + void rtBmrWithMultipleValues() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "123,321")); Beacon result = processor.process(beacon); @@ -101,7 +101,7 @@ public void rtBmrWithMultipleValues() { } @Test - public void rtBmrMultipleValuesWithOneEmptyValue() { + void rtBmrMultipleValuesWithOneEmptyValue() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "123,,321")); Beacon result = processor.process(beacon); @@ -112,7 +112,7 @@ public void rtBmrMultipleValuesWithOneEmptyValue() { } @Test - public void rtBmrMultipleValuesWithOneInvalidValue() { + void rtBmrMultipleValuesWithOneInvalidValue() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "123,bar,321")); Beacon result = processor.process(beacon); @@ -123,7 +123,7 @@ public void rtBmrMultipleValuesWithOneInvalidValue() { } @Test - public void rtBmrMultipleValuesWithTwoInvalidValues() { + void rtBmrMultipleValuesWithTwoInvalidValues() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "123,bar,321,foo")); Beacon result = processor.process(beacon); @@ -134,7 +134,7 @@ public void rtBmrMultipleValuesWithTwoInvalidValues() { } @Test - public void rtBmrAllValues() { + void rtBmrAllValues() { Beacon beacon = Beacon.of(Collections.singletonMap("rt.bmr", "123,120,,,,1,,,7,,,")); Beacon result = processor.process(beacon); @@ -144,4 +144,4 @@ public void rtBmrAllValues() { .contains(entry("rt.bmr", "123,120,,,,1,,,7,,,"), entry("rt.bmr.startTime", "123"), entry("rt.bmr.responseEnd", "120"), entry("rt.bmr.secureConnectionStart", "1"), entry("rt.bmr.domainLookupStart", "7")); } } -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/ClientHeaderBeaconProcessorTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/ClientHeaderBeaconProcessorTest.java similarity index 81% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/ClientHeaderBeaconProcessorTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/ClientHeaderBeaconProcessorTest.java index 3cfbee4..90436ee 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/ClientHeaderBeaconProcessorTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/ClientHeaderBeaconProcessorTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -7,7 +7,7 @@ import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockHttpServletRequest; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import java.util.Collections; @@ -18,14 +18,14 @@ class ClientHeaderBeaconProcessorTest { @InjectMocks - private ClientHeaderBeaconProcessor processor; + ClientHeaderBeaconProcessor processor; - private final String HEADER_PREFIX = "client.header."; + final String HEADER_PREFIX = "client.header."; @Nested - public class Process { + class Process { - private final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletRequest request = new MockHttpServletRequest(); @BeforeEach void getCurrentRequest() { @@ -35,7 +35,7 @@ void getCurrentRequest() { } @Test - public void singleClientHeader() { + void singleClientHeader() { request.addHeader("Accept-Encoding", "gzip, deflate"); Beacon beacon = Beacon.of(Collections.singletonMap("key", "value")); @@ -46,7 +46,7 @@ public void singleClientHeader() { } @Test - public void differentClientHeader() { + void differentClientHeader() { request.addHeader("Accept-Encoding", "gzip,deflate"); request.addHeader("Connection", "keep-alive"); @@ -58,7 +58,7 @@ public void differentClientHeader() { } @Test - public void multipleClientHeader() { + void multipleClientHeader() { request.addHeader("Accept-Encoding", "gzip"); request.addHeader("Accept-Encoding", "deflate"); @@ -68,6 +68,5 @@ public void multipleClientHeader() { assertThat(result.toMap()).hasSize(2) .contains(entry("key", "value"), entry(HEADER_PREFIX + "Accept-Encoding", "gzip,deflate")); } - } -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CompositeBeaconProcessorTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CompositeBeaconProcessorTest.java similarity index 72% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CompositeBeaconProcessorTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CompositeBeaconProcessorTest.java index 6e450ee..9955611 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CompositeBeaconProcessorTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CompositeBeaconProcessorTest.java @@ -1,9 +1,7 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import org.assertj.core.api.Assertions; -import org.assertj.core.data.MapEntry; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -11,14 +9,11 @@ import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.metrics.SelfMonitoringMetricManager; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.metrics.SelfMonitoringMetricManager; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -30,17 +25,17 @@ class CompositeBeaconProcessorTest { @InjectMocks - private CompositeBeaconProcessor processor; + CompositeBeaconProcessor processor; @Mock - private SelfMonitoringMetricManager selfMonitoring; + SelfMonitoringMetricManager selfMonitoring; @Spy - private List processorList = ImmutableList.of(beacon -> beacon.merge(ImmutableMap.of("key2", "value2")), beacon -> beacon + List processorList = ImmutableList.of(beacon -> beacon.merge(ImmutableMap.of("key2", "value2")), beacon -> beacon .merge(ImmutableMap.of("key1", "value2"))); @Test - public void test() { + void test() { Beacon processedBeacon = processor.process(Beacon.of(ImmutableMap.of("key1", "value1"))); // Ensure value got properly overwritten @@ -57,5 +52,4 @@ public void test() { .toArray(Map.Entry[]::new); assertThat(tagCaptor.getAllValues()).flatExtracting(Map::entrySet).contains(processorTagEntries); } - -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CountryCodeBeaconProcessorTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CountryCodeBeaconProcessorTest.java similarity index 59% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CountryCodeBeaconProcessorTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CountryCodeBeaconProcessorTest.java index 58a6a45..05dc757 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CountryCodeBeaconProcessorTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CountryCodeBeaconProcessorTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.BeforeEach; @@ -12,11 +12,11 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.tags.TagsSettings; -import rocks.inspectit.oce.eum.server.utils.GeolocationResolver; -import rocks.inspectit.oce.eum.server.utils.IPUtils; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.AttributeSettings; +import rocks.inspectit.ocelot.eum.server.utils.GeolocationResolver; +import rocks.inspectit.ocelot.eum.server.utils.IPUtils; import java.util.Arrays; @@ -33,24 +33,24 @@ class CountryCodeBeaconProcessorTest { CountryCodeBeaconProcessor preProcessor; @Mock - private GeolocationResolver geolocationResolver; + GeolocationResolver geolocationResolver; @Spy - private IPUtils ipUtils = new IPUtils(); + IPUtils ipUtils = new IPUtils(); @Mock - private EumServerConfiguration configuration; + EumServerConfiguration configuration; - public static final String DEFAULT_IP_ADDRESS = "10.10.10.10"; + static final String DEFAULT_IP_ADDRESS = "10.10.10.10"; - private TagsSettings tagsSettings = new TagsSettings(); + AttributeSettings attributeSettings = new AttributeSettings(); - private Beacon beacon; + Beacon beacon; - private MockHttpServletRequest request; + MockHttpServletRequest request; @BeforeEach - private void initializeBeacon() { + void initializeBeacon() { beacon = Beacon.of(ImmutableMap.of("dummyMetric", "dummyValue")); request = new MockHttpServletRequest(); request.setRemoteAddr(DEFAULT_IP_ADDRESS); @@ -59,21 +59,21 @@ private void initializeBeacon() { } @Nested - public class Process { + class Process { @Test - public void addEmptyCountryCode() { + void addEmptyCountryCode() { when(geolocationResolver.getCountryCode(DEFAULT_IP_ADDRESS)).thenReturn(""); - when(configuration.getTags()).thenReturn(tagsSettings); + when(configuration.getAttributes()).thenReturn(attributeSettings); Beacon b = preProcessor.process(beacon); assertThat(b.get(CountryCodeBeaconProcessor.TAG_COUNTRY_CODE)).isEqualTo(""); } @Test - public void addGeoIPDBCountryCode() { + void addGeoIPDBCountryCode() { when(geolocationResolver.getCountryCode(DEFAULT_IP_ADDRESS)).thenReturn("DE"); - when(configuration.getTags()).thenReturn(tagsSettings); + when(configuration.getAttributes()).thenReturn(attributeSettings); Beacon b = preProcessor.process(beacon); assertThat(b.contains(CountryCodeBeaconProcessor.TAG_COUNTRY_CODE)).isTrue(); @@ -81,59 +81,59 @@ public void addGeoIPDBCountryCode() { } @Test - public void addCustomLabelIPMatches() { - tagsSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.10.10.10"})); - when(configuration.getTags()).thenReturn(tagsSettings); + void addCustomLabelIPMatches() { + attributeSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.10.10.10"})); + when(configuration.getAttributes()).thenReturn(attributeSettings); Beacon b = preProcessor.process(beacon); assertThat(b.get(CountryCodeBeaconProcessor.TAG_COUNTRY_CODE)).isEqualTo("CUSTOM_TAG_1"); } @Test - public void addGeoIPDBCountryCodeSecondPrio1() { - tagsSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.10.10.11"})); + void addGeoIPDBCountryCodeSecondPrio1() { + attributeSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.10.10.11"})); when(geolocationResolver.getCountryCode(DEFAULT_IP_ADDRESS)).thenReturn("DE"); - when(configuration.getTags()).thenReturn(tagsSettings); + when(configuration.getAttributes()).thenReturn(attributeSettings); Beacon b = preProcessor.process(beacon); assertThat(b.get(CountryCodeBeaconProcessor.TAG_COUNTRY_CODE)).isEqualTo("DE"); } @Test - public void addGeoIPDBCountryCodeSecondPrio2() { - tagsSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.11.0.0/16"})); + void addGeoIPDBCountryCodeSecondPrio2() { + attributeSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.11.0.0/16"})); when(geolocationResolver.getCountryCode(DEFAULT_IP_ADDRESS)).thenReturn("DE"); - when(configuration.getTags()).thenReturn(tagsSettings); + when(configuration.getAttributes()).thenReturn(attributeSettings); Beacon b = preProcessor.process(beacon); assertThat(b.get(CountryCodeBeaconProcessor.TAG_COUNTRY_CODE)).isEqualTo("DE"); } @Test - public void addCustomLabelCIDRMatches1() { - tagsSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.10.0.0/16"})); - when(configuration.getTags()).thenReturn(tagsSettings); + void addCustomLabelCIDRMatches1() { + attributeSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.10.0.0/16"})); + when(configuration.getAttributes()).thenReturn(attributeSettings); Beacon b = preProcessor.process(beacon); assertThat(b.get(CountryCodeBeaconProcessor.TAG_COUNTRY_CODE)).isEqualTo("CUSTOM_TAG_1"); } @Test - public void addCustomLabelCIDRMatches2() { - tagsSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.11.0.0/16"})); - tagsSettings.getCustomIPMapping().put("CUSTOM_TAG_2", Arrays.asList(new String[]{"10.10.0.0/16"})); - when(configuration.getTags()).thenReturn(tagsSettings); + void addCustomLabelCIDRMatches2() { + attributeSettings.getCustomIPMapping().put("CUSTOM_TAG_1", Arrays.asList(new String[]{"10.11.0.0/16"})); + attributeSettings.getCustomIPMapping().put("CUSTOM_TAG_2", Arrays.asList(new String[]{"10.10.0.0/16"})); + when(configuration.getAttributes()).thenReturn(attributeSettings); Beacon b = preProcessor.process(beacon); assertThat(b.get(CountryCodeBeaconProcessor.TAG_COUNTRY_CODE)).isEqualTo("CUSTOM_TAG_2"); } @Test - public void respectForwardedHeader() { + void respectForwardedHeader() { when(geolocationResolver.getCountryCode("10.0.0.0")).thenReturn("DE"); - when(configuration.getTags()).thenReturn(tagsSettings); + when(configuration.getAttributes()).thenReturn(attributeSettings); request.addHeader("X-Forwarded-For", "10.0.0.0"); Beacon result = preProcessor.process(beacon); @@ -143,9 +143,9 @@ public void respectForwardedHeader() { } @Test - public void respectForwardedHeader_multiValue() { + void respectForwardedHeader_multiValue() { when(geolocationResolver.getCountryCode("10.0.0.0")).thenReturn("DE"); - when(configuration.getTags()).thenReturn(tagsSettings); + when(configuration.getAttributes()).thenReturn(attributeSettings); request.addHeader("X-Forwarded-For", new String[]{"10.0.0.0", "10.0.0.1"}); Beacon result = preProcessor.process(beacon); diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessorTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessorTest.java similarity index 80% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessorTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessorTest.java index 6b5152d..0fea89a 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessorTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/CsvKeyValueExpanderBeaconProcessorTest.java @@ -1,11 +1,11 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import java.util.Collections; @@ -16,13 +16,13 @@ class CsvKeyValueExpanderBeaconProcessorTest { @InjectMocks - private CsvKeyValueExpanderBeaconProcessor processor; + CsvKeyValueExpanderBeaconProcessor processor; @Nested - public class Process { + class Process { @Test - public void noTOtherAttribute() { + void noTOtherAttribute() { Beacon beacon = Beacon.of(Collections.singletonMap("key", "value")); Beacon result = processor.process(beacon); @@ -31,7 +31,7 @@ public void noTOtherAttribute() { } @Test - public void tOtherWithoutPatternAttribute() { + void tOtherWithoutPatternAttribute() { Beacon beacon = Beacon.of(Collections.singletonMap("t_other", "value")); Beacon result = processor.process(beacon); @@ -40,7 +40,7 @@ public void tOtherWithoutPatternAttribute() { } @Test - public void tOtherWithPatternAttribute() { + void tOtherWithPatternAttribute() { Beacon beacon = Beacon.of(Collections.singletonMap("t_other", "value|123")); Beacon result = processor.process(beacon); @@ -51,7 +51,7 @@ public void tOtherWithPatternAttribute() { } @Test - public void tOtherWithMultiplePatternAttributes() { + void tOtherWithMultiplePatternAttributes() { Beacon beacon = Beacon.of(Collections.singletonMap("t_other", "value|123,another_value|321")); Beacon result = processor.process(beacon); @@ -62,4 +62,4 @@ public void tOtherWithMultiplePatternAttributes() { entry("t_other.another_value", "321")); } } -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessorIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessorIntTest.java similarity index 91% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessorIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessorIntTest.java index a7430b7..b5c96e2 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessorIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessorIntTest.java @@ -1,11 +1,11 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; -import rocks.inspectit.oce.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; import static org.assertj.core.api.Assertions.assertThat; @@ -14,7 +14,7 @@ class RegexReplacementBeaconProcessorIntTest { @Autowired - private RegexReplacementBeaconProcessor processor; + RegexReplacementBeaconProcessor processor; @Test void testRemoveQueryParameters() { @@ -49,6 +49,4 @@ void testRemoveQueryParametersFromUnwiseUrl() { assertThat(b.get("PGU_HOST")).isNull(); assertThat(b.get("PGU_PORT")).isNull(); } - - -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessorTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessorTest.java similarity index 72% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessorTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessorTest.java index 2aed578..637ed4a 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/processor/RegexReplacementBeaconProcessorTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/processor/RegexReplacementBeaconProcessorTest.java @@ -1,13 +1,13 @@ -package rocks.inspectit.oce.eum.server.beacon.processor; +package rocks.inspectit.ocelot.eum.server.beacon.processor; import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.tags.BeaconTagSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.tags.TagsSettings; -import rocks.inspectit.oce.eum.server.configuration.model.tags.PatternAndReplacement; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.BeaconAttributeSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.AttributeSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.PatternAndReplacement; import java.util.Arrays; import java.util.LinkedHashMap; @@ -15,39 +15,39 @@ import static org.assertj.core.api.Assertions.*; -public class RegexReplacementBeaconProcessorTest { +class RegexReplacementBeaconProcessorTest { @Nested class Process { @Test void errorOnCyclicDependency() { - BeaconTagSettings first = BeaconTagSettings.builder().input("second").build(); - BeaconTagSettings second = BeaconTagSettings.builder().input("third").build(); - BeaconTagSettings third = BeaconTagSettings.builder().input("first").build(); + BeaconAttributeSettings first = BeaconAttributeSettings.builder().input("second").build(); + BeaconAttributeSettings second = BeaconAttributeSettings.builder().input("third").build(); + BeaconAttributeSettings third = BeaconAttributeSettings.builder().input("first").build(); - Map regexSettings = new LinkedHashMap<>(); + Map regexSettings = new LinkedHashMap<>(); regexSettings.put("first", first); regexSettings.put("second", second); regexSettings.put("third", third); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(regexSettings); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(regexSettings); assertThatThrownBy(() -> new RegexReplacementBeaconProcessor(conf)).hasMessageStartingWith("Cyclic"); } @Test void dependenciesRespected() { - BeaconTagSettings first = BeaconTagSettings.builder() + BeaconAttributeSettings first = BeaconAttributeSettings.builder() .input("in") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Hello World") .replacement("Bye World") .build())) .build(); - BeaconTagSettings second = BeaconTagSettings.builder() + BeaconAttributeSettings second = BeaconAttributeSettings.builder() .input("first") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Bye World") @@ -55,13 +55,13 @@ void dependenciesRespected() { .build())) .build(); - Map invalidOrder = new LinkedHashMap<>(); + Map invalidOrder = new LinkedHashMap<>(); invalidOrder.put("second", second); invalidOrder.put("first", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(invalidOrder); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(invalidOrder); RegexReplacementBeaconProcessor proc = new RegexReplacementBeaconProcessor(conf); @@ -74,14 +74,14 @@ void dependenciesRespected() { @Test void copyWithoutRegexSupported() { - BeaconTagSettings first = BeaconTagSettings.builder().input("in").build(); + BeaconAttributeSettings first = BeaconAttributeSettings.builder().input("in").build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("first", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor proc = new RegexReplacementBeaconProcessor(conf); @@ -92,7 +92,7 @@ void copyWithoutRegexSupported() { @Test void inplaceChangeSupported() { - BeaconTagSettings first = BeaconTagSettings.builder() + BeaconAttributeSettings first = BeaconAttributeSettings.builder() .input("value") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Hello World") @@ -100,12 +100,12 @@ void inplaceChangeSupported() { .build())) .build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("value", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor proc = new RegexReplacementBeaconProcessor(conf); @@ -116,14 +116,14 @@ void inplaceChangeSupported() { @Test void handleBrokenReplacementStrings() { - BeaconTagSettings first = BeaconTagSettings.builder() + BeaconAttributeSettings first = BeaconAttributeSettings.builder() .input("value") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Hello World") .replacement("one") .build())) .build(); - BeaconTagSettings second = BeaconTagSettings.builder() + BeaconAttributeSettings second = BeaconAttributeSettings.builder() .input("value") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Hello World") @@ -131,7 +131,7 @@ void handleBrokenReplacementStrings() { .keepNoMatch(false) .build())) .build(); - BeaconTagSettings third = BeaconTagSettings.builder() + BeaconAttributeSettings third = BeaconAttributeSettings.builder() .input("value") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Hello World") @@ -139,14 +139,14 @@ void handleBrokenReplacementStrings() { .build())) .build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("first", first); beaconTags.put("second", second); beaconTags.put("third", third); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor processor = new RegexReplacementBeaconProcessor(conf); @@ -161,7 +161,7 @@ void handleBrokenReplacementStrings() { @Test void nullAttribute() { - BeaconTagSettings first = BeaconTagSettings.builder() + BeaconAttributeSettings first = BeaconAttributeSettings.builder() .input("not-existing") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Hello World") @@ -169,12 +169,12 @@ void nullAttribute() { .build())) .build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("first", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor processor = new RegexReplacementBeaconProcessor(conf); @@ -187,7 +187,7 @@ void nullAttribute() { @Test void nullAttributeAsEmpty() { - BeaconTagSettings first = BeaconTagSettings.builder() + BeaconAttributeSettings first = BeaconAttributeSettings.builder() .input("not-existing") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("^$") @@ -196,12 +196,12 @@ void nullAttributeAsEmpty() { .nullAsEmpty(true) .build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("first", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor processor = new RegexReplacementBeaconProcessor(conf); @@ -215,7 +215,7 @@ void nullAttributeAsEmpty() { @Test void multipleReplacementsWithDeprecation() { - BeaconTagSettings first = BeaconTagSettings.builder().input("source") + BeaconAttributeSettings first = BeaconAttributeSettings.builder().input("source") .regex("Hello") .replacement("Bye") .replacements(Arrays.asList( @@ -224,12 +224,12 @@ void multipleReplacementsWithDeprecation() { )) .build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("first", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor processor = new RegexReplacementBeaconProcessor(conf); @@ -243,7 +243,7 @@ void multipleReplacementsWithDeprecation() { @Test void brokenAdditionalReplacements() { - BeaconTagSettings first = BeaconTagSettings.builder().input("source") + BeaconAttributeSettings first = BeaconAttributeSettings.builder().input("source") .regex("Hello") .replacement("Bye") .replacements(Arrays.asList( @@ -253,12 +253,12 @@ void brokenAdditionalReplacements() { )) .build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("first", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor processor = new RegexReplacementBeaconProcessor(conf); @@ -272,7 +272,7 @@ void brokenAdditionalReplacements() { @Test void keepNoMatchByDefault() { - BeaconTagSettings first = BeaconTagSettings.builder() + BeaconAttributeSettings first = BeaconAttributeSettings.builder() .input("value") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Hello World") @@ -284,12 +284,12 @@ void keepNoMatchByDefault() { .build())) .build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("output", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor processor = new RegexReplacementBeaconProcessor(conf); @@ -303,7 +303,7 @@ void keepNoMatchByDefault() { @Test void discardNoMatch() { - BeaconTagSettings first = BeaconTagSettings.builder() + BeaconAttributeSettings first = BeaconAttributeSettings.builder() .input("value") .replacements(Arrays.asList(PatternAndReplacement.builder() .pattern("Hello World") @@ -316,12 +316,12 @@ void discardNoMatch() { .build())) .build(); - Map beaconTags = new LinkedHashMap<>(); + Map beaconTags = new LinkedHashMap<>(); beaconTags.put("output", first); EumServerConfiguration conf = new EumServerConfiguration(); - conf.setTags(new TagsSettings()); - conf.getTags().setBeacon(beaconTags); + conf.setAttributes(new AttributeSettings()); + conf.getAttributes().setBeacon(beaconTags); RegexReplacementBeaconProcessor processor = new RegexReplacementBeaconProcessor(conf); diff --git a/src/test/java/rocks/inspectit/oce/eum/server/beacon/recorder/ResourceTimingBeaconRecorderTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/ResourceTimingBeaconRecorderTest.java similarity index 70% rename from src/test/java/rocks/inspectit/oce/eum/server/beacon/recorder/ResourceTimingBeaconRecorderTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/ResourceTimingBeaconRecorderTest.java index e328b59..63fef30 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/beacon/recorder/ResourceTimingBeaconRecorderTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/beacon/recorder/ResourceTimingBeaconRecorderTest.java @@ -1,7 +1,7 @@ -package rocks.inspectit.oce.eum.server.beacon.recorder; +package rocks.inspectit.ocelot.eum.server.beacon.recorder; import com.fasterxml.jackson.databind.ObjectMapper; -import io.opencensus.tags.Tags; +import io.opentelemetry.api.baggage.Baggage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -10,9 +10,9 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.metrics.MeasuresAndViewsManager; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.metrics.InstrumentManager; import java.util.Collections; import java.util.HashMap; @@ -29,7 +29,7 @@ class ResourceTimingBeaconRecorderTest { ResourceTimingBeaconRecorder recorder; @Mock - MeasuresAndViewsManager measuresAndViewsManager; + InstrumentManager instrumentManager; @Mock EumServerConfiguration configuration; @@ -37,39 +37,39 @@ class ResourceTimingBeaconRecorderTest { ObjectMapper objectMapper; @BeforeEach - public void init() { - lenient().when(measuresAndViewsManager.getTagContext(any())).thenReturn(Tags.getTagger().emptyBuilder()); + void init() { + lenient().when(instrumentManager.getBaggage(any())).thenReturn(Baggage.empty()); objectMapper = new ObjectMapper(); - recorder = new ResourceTimingBeaconRecorder(objectMapper, measuresAndViewsManager, configuration); + recorder = new ResourceTimingBeaconRecorder(objectMapper, instrumentManager, configuration); } @Nested class Record { @Captor - ArgumentCaptor> tagsCaptor; + ArgumentCaptor> baggageCaptor; @Test - public void noResourceTimingInfo() { + void noResourceTimingInfo() { Beacon beacon = Beacon.of(Collections.emptyMap()); recorder.record(beacon); - verifyNoMoreInteractions(measuresAndViewsManager); + verifyNoMoreInteractions(instrumentManager); } @Test - public void notAJson() { + void notAJson() { Beacon beacon = Beacon.of(Collections.singletonMap("restiming", "This is not a valid json")); recorder.record(beacon); - verifyNoMoreInteractions(measuresAndViewsManager); + verifyNoMoreInteractions(instrumentManager); } @Test - public void badCompression() { + void badCompression() { // intentionally change the initiator String json = "" + "{\n" + " \"https://www.google.de/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png\": \"!!\"\n" + "}"; Map fields = new HashMap<>(); @@ -79,11 +79,11 @@ public void badCompression() { recorder.record(beacon); - verifyNoMoreInteractions(measuresAndViewsManager); + verifyNoMoreInteractions(instrumentManager); } @Test - public void nestedResources() { + void nestedResources() { String json = "" + "{\n" + " \"http\": {\n" + " \"://myhost/\": {\n" + " \"|\": \"6,2\",\n" + " \"boomerang/plugins/\": {\n" + " \"r\": {\n" + " \"t.js\": \"32o,2u,2q,25*1d67,9s,wdu*20\",\n" + " \"estiming.js\": \"02p,2w,2p,24*1efk,9z,y2i*20\"\n" + " }\n" + " }\n" + " },\n" + " \"s://www.google.de/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png\": \"*02k,7k,1y,a2|180,3l\"\n" + " }\n" + "}"; Map fields = new HashMap<>(); fields.put("u", "http://myhost/somepage.html"); @@ -92,14 +92,14 @@ public void nestedResources() { recorder.record(beacon); - verify(measuresAndViewsManager, atLeastOnce()).getTagContext(tagsCaptor.capture()); - verify(measuresAndViewsManager).recordMeasure(eq("resource_time"), any(), eq(2)); - verify(measuresAndViewsManager).recordMeasure(eq("resource_time"), any(), eq(102)); - verify(measuresAndViewsManager).recordMeasure(eq("resource_time"), any(), eq(104)); - verify(measuresAndViewsManager).recordMeasure(eq("resource_time"), any(), eq(129)); - verifyNoMoreInteractions(measuresAndViewsManager); + verify(instrumentManager, atLeastOnce()).getBaggage(baggageCaptor.capture()); + verify(instrumentManager).recordMetric(eq("resource_time"), any(), eq(2)); + verify(instrumentManager).recordMetric(eq("resource_time"), any(), eq(102)); + verify(instrumentManager).recordMetric(eq("resource_time"), any(), eq(104)); + verify(instrumentManager).recordMetric(eq("resource_time"), any(), eq(129)); + verifyNoMoreInteractions(instrumentManager); - assertThat(tagsCaptor.getAllValues()).hasSize(4) + assertThat(baggageCaptor.getAllValues()).hasSize(4) // | .anySatisfy(map -> assertThat(map).hasSize(3) .containsEntry("initiatorType", "HTML") @@ -123,7 +123,7 @@ public void nestedResources() { } @Test - public void pipedData() { + void pipedData() { // intentionally change the initiator String json = "" + "{\n" + " \"https://www.google.de/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png\": \"*02k,7k,1y,a2|180,3l|280,4l\"\n" + "}"; Map fields = new HashMap<>(); @@ -133,12 +133,12 @@ public void pipedData() { recorder.record(beacon); - verify(measuresAndViewsManager, atLeastOnce()).getTagContext(tagsCaptor.capture()); - verify(measuresAndViewsManager).recordMeasure(eq("resource_time"), any(), eq(129)); - verify(measuresAndViewsManager).recordMeasure(eq("resource_time"), any(), eq(165)); - verifyNoMoreInteractions(measuresAndViewsManager); + verify(instrumentManager, atLeastOnce()).getBaggage(baggageCaptor.capture()); + verify(instrumentManager).recordMetric(eq("resource_time"), any(), eq(129)); + verify(instrumentManager).recordMetric(eq("resource_time"), any(), eq(165)); + verifyNoMoreInteractions(instrumentManager); - assertThat(tagsCaptor.getAllValues()).hasSize(2) + assertThat(baggageCaptor.getAllValues()).hasSize(2) // first .anySatisfy(map -> assertThat(map).hasSize(2) .containsEntry("initiatorType", "IMG") @@ -150,7 +150,7 @@ public void pipedData() { } @Test - public void onlyAdditionalData() { + void onlyAdditionalData() { // intentionally change the initiator String json = "" + "{\n" + " \"https://www.google.de/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png\": \"*02k,7k,1y,a2\"\n" + "}"; Map fields = new HashMap<>(); @@ -160,11 +160,11 @@ public void onlyAdditionalData() { recorder.record(beacon); - verifyNoMoreInteractions(measuresAndViewsManager); + verifyNoMoreInteractions(instrumentManager); } @Test - public void wrongInitiator() { + void wrongInitiator() { // intentionally change the initiator String json = "" + "{\n" + " \"https://www.google.de/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png\": \"w80,3l\"\n" + "}"; Map fields = new HashMap<>(); @@ -174,11 +174,11 @@ public void wrongInitiator() { recorder.record(beacon); - verify(measuresAndViewsManager, atLeastOnce()).getTagContext(tagsCaptor.capture()); - verify(measuresAndViewsManager).recordMeasure(eq("resource_time"), any(), eq(129)); - verifyNoMoreInteractions(measuresAndViewsManager); + verify(instrumentManager, atLeastOnce()).getBaggage(baggageCaptor.capture()); + verify(instrumentManager).recordMetric(eq("resource_time"), any(), eq(129)); + verifyNoMoreInteractions(instrumentManager); - assertThat(tagsCaptor.getAllValues()).hasSize(1) + assertThat(baggageCaptor.getAllValues()).hasSize(1) // first .anySatisfy(map -> assertThat(map).hasSize(2) .containsEntry("initiatorType", "OTHER") @@ -186,7 +186,7 @@ public void wrongInitiator() { } @Test - public void noResponseEnd() { + void noResponseEnd() { // intentionally change the initiator String json = "" + "{\n" + " \"https://www.google.de/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png\": \"180\"\n" + "}"; Map fields = new HashMap<>(); @@ -196,17 +196,15 @@ public void noResponseEnd() { recorder.record(beacon); - verify(measuresAndViewsManager, atLeastOnce()).getTagContext(tagsCaptor.capture()); - verify(measuresAndViewsManager).recordMeasure(eq("resource_time"), any(), eq(0)); - verifyNoMoreInteractions(measuresAndViewsManager); + verify(instrumentManager, atLeastOnce()).getBaggage(baggageCaptor.capture()); + verify(instrumentManager).recordMetric(eq("resource_time"), any(), eq(0)); + verifyNoMoreInteractions(instrumentManager); - assertThat(tagsCaptor.getAllValues()).hasSize(1) + assertThat(baggageCaptor.getAllValues()).hasSize(1) // first .anySatisfy(map -> assertThat(map).hasSize(2) .containsEntry("initiatorType", "IMG") .containsEntry("crossOrigin", "true")); } - } - } diff --git a/src/test/java/rocks/inspectit/oce/eum/server/configuration/model/TagsSettingsTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/configuration/model/AttributeSettingsTest.java similarity index 58% rename from src/test/java/rocks/inspectit/oce/eum/server/configuration/model/TagsSettingsTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/configuration/model/AttributeSettingsTest.java index 9237bd9..63b766b 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/configuration/model/TagsSettingsTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/configuration/model/AttributeSettingsTest.java @@ -1,105 +1,105 @@ -package rocks.inspectit.oce.eum.server.configuration.model; +package rocks.inspectit.ocelot.eum.server.configuration.model; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; -import rocks.inspectit.oce.eum.server.configuration.model.tags.BeaconTagSettings; -import rocks.inspectit.oce.eum.server.configuration.model.tags.TagsSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.BeaconAttributeSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.AttributeSettings; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) -class TagsSettingsTest { +class AttributeSettingsTest { @Nested - public class IsGlobalTagMissing { + class IsGlobalAttributeMissing { @Test - public void emptySettings() { - TagsSettings settings = new TagsSettings(); + void emptySettings() { + AttributeSettings settings = new AttributeSettings(); - boolean result = settings.isGlobalTagMissing(); + boolean result = settings.isGlobalAttributeMissing(); assertThat(result).isFalse(); } @Test - public void tagMissing() { - TagsSettings settings = new TagsSettings(); + void tagMissing() { + AttributeSettings settings = new AttributeSettings(); settings.getDefineAsGlobal().add("missing-tag"); - boolean result = settings.isGlobalTagMissing(); + boolean result = settings.isGlobalAttributeMissing(); assertThat(result).isTrue(); } @Test - public void hasBeaconTag() { - TagsSettings settings = new TagsSettings(); + void hasBeaconTag() { + AttributeSettings settings = new AttributeSettings(); settings.getDefineAsGlobal().add("beacon-tag"); - settings.getBeacon().put("beacon-tag", BeaconTagSettings.builder().input("beacon-field").build()); + settings.getBeacon().put("beacon-tag", BeaconAttributeSettings.builder().input("beacon-field").build()); - boolean result = settings.isGlobalTagMissing(); + boolean result = settings.isGlobalAttributeMissing(); assertThat(result).isFalse(); } @Test - public void hasExtraTag() { - TagsSettings settings = new TagsSettings(); - settings.getDefineAsGlobal().add("extra-tag"); - settings.getExtra().put("extra-tag", "constant-value"); + void hasExtraTag() { + AttributeSettings settings = new AttributeSettings(); + settings.getDefineAsGlobal().add("extra-attribute"); + settings.getExtra().put("extra-attribute", "constant-value"); - boolean result = settings.isGlobalTagMissing(); + boolean result = settings.isGlobalAttributeMissing(); assertThat(result).isFalse(); } } @Nested - public class IsCheckUniquenessOfTags { + class IsCheckUniquenessOfAttributes { @Test - public void emptySettings() { - TagsSettings settings = new TagsSettings(); + void emptySettings() { + AttributeSettings settings = new AttributeSettings(); - boolean result = settings.isCheckUniquenessOfTags(); + boolean result = settings.isCheckUniquenessOfAttributes(); assertThat(result).isTrue(); } @Test - public void noDuplicateTags() { - TagsSettings settings = new TagsSettings(); + void noDuplicateTags() { + AttributeSettings settings = new AttributeSettings(); settings.getExtra().put("tag_a", ""); - settings.getBeacon().put("tag_b", BeaconTagSettings.builder().build()); + settings.getBeacon().put("tag_b", BeaconAttributeSettings.builder().build()); - boolean result = settings.isCheckUniquenessOfTags(); + boolean result = settings.isCheckUniquenessOfAttributes(); assertThat(result).isTrue(); } @Test - public void hasDuplicateTags() { - TagsSettings settings = new TagsSettings(); + void hasDuplicateTags() { + AttributeSettings settings = new AttributeSettings(); settings.getExtra().put("tag_a", ""); - settings.getBeacon().put("tag_a", BeaconTagSettings.builder().build()); + settings.getBeacon().put("tag_a", BeaconAttributeSettings.builder().build()); - boolean result = settings.isCheckUniquenessOfTags(); + boolean result = settings.isCheckUniquenessOfAttributes(); assertThat(result).isFalse(); } } @Nested - public class IsCheckIPsRangesDoNotOverlap { + class IsCheckIPsRangesDoNotOverlap { @Test - public void emptyCustomMapping() { - TagsSettings settings = new TagsSettings(); + void emptyCustomMapping() { + AttributeSettings settings = new AttributeSettings(); boolean result = settings.isCheckIpRangesDoNotOverlap(); @@ -107,8 +107,8 @@ public void emptyCustomMapping() { } @Test - public void ipsAreEqual() { - TagsSettings settings = new TagsSettings(); + void ipsAreEqual() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"127.127.127.127"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"127.127.127.127"})); @@ -118,8 +118,8 @@ public void ipsAreEqual() { } @Test - public void ipsAreNotEqual() { - TagsSettings settings = new TagsSettings(); + void ipsAreNotEqual() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"127.127.127.127"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"127.127.127.128"})); @@ -129,8 +129,8 @@ public void ipsAreNotEqual() { } @Test - public void cidrsAreEqual() { - TagsSettings settings = new TagsSettings(); + void cidrsAreEqual() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"10.0.0.0/16"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"10.0.0.0/16"})); @@ -140,8 +140,8 @@ public void cidrsAreEqual() { } @Test - public void cidrsOverlap() { - TagsSettings settings = new TagsSettings(); + void cidrsOverlap() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"10.0.0.0/16"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"10.0.0.0/17"})); @@ -151,8 +151,8 @@ public void cidrsOverlap() { } @Test - public void cidrsDoNotOverlap() { - TagsSettings settings = new TagsSettings(); + void cidrsDoNotOverlap() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"10.0.0.0/16"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"10.1.0.0/16"})); @@ -162,8 +162,8 @@ public void cidrsDoNotOverlap() { } @Test - public void cidrContainsIp() { - TagsSettings settings = new TagsSettings(); + void cidrContainsIp() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"10.0.0.0/16"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"10.0.0.1"})); @@ -173,8 +173,8 @@ public void cidrContainsIp() { } @Test - public void cidrDoesNotContainIp() { - TagsSettings settings = new TagsSettings(); + void cidrDoesNotContainIp() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"10.0.0.0/16"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"11.0.0.1"})); @@ -184,8 +184,8 @@ public void cidrDoesNotContainIp() { } @Test - public void cidrsOfSameLabelAreOverlapping() { - TagsSettings settings = new TagsSettings(); + void cidrsOfSameLabelAreOverlapping() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"10.0.0.0/16", "10.0.0.0/17"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"11.0.0.1"})); @@ -195,8 +195,8 @@ public void cidrsOfSameLabelAreOverlapping() { } @Test - public void cidrAndIpOfSameLabelAreOverlapping() { - TagsSettings settings = new TagsSettings(); + void cidrAndIpOfSameLabelAreOverlapping() { + AttributeSettings settings = new AttributeSettings(); settings.getCustomIPMapping().put("GER", Arrays.asList(new String[]{"10.0.0.0/16", "10.0.0.1"})); settings.getCustomIPMapping().put("FR", Arrays.asList(new String[]{"11.0.0.1"})); diff --git a/src/test/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirementTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/configuration/model/BeaconRequirementTest.java similarity index 76% rename from src/test/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirementTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/configuration/model/BeaconRequirementTest.java index 46ae205..7a0b75f 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirementTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/configuration/model/BeaconRequirementTest.java @@ -1,27 +1,27 @@ -package rocks.inspectit.oce.eum.server.configuration.model; +package rocks.inspectit.ocelot.eum.server.configuration.model; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.BeaconRequirement; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon.InitiatorType; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.BeaconRequirement; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon.InitiatorType; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; class BeaconRequirementTest { @Nested - public class StaticValidate { + class StaticValidate { - private Beacon beacon; + Beacon beacon; @BeforeEach - public void before() { + void before() { HashMap map = new HashMap<>(); map.put("first", "1"); map.put("second", "2"); @@ -29,7 +29,7 @@ public void before() { } @Test - public void noFulfillment() { + void noFulfillment() { BeaconRequirement requirementA = new BeaconRequirement(); requirementA.setField("third"); requirementA.setRequirement(BeaconRequirement.RequirementType.NOT_EXISTS); @@ -37,13 +37,13 @@ public void noFulfillment() { requirementB.setField("second"); requirementB.setRequirement(BeaconRequirement.RequirementType.NOT_EXISTS); - boolean result = BeaconRequirement.validate(beacon, Arrays.asList(requirementA, requirementB)); + boolean result = BeaconRequirement.validate(beacon, List.of(requirementA, requirementB)); assertThat(result).isFalse(); } @Test - public void fulfillment() { + void fulfillment() { BeaconRequirement requirementA = new BeaconRequirement(); requirementA.setField("third"); requirementA.setRequirement(BeaconRequirement.RequirementType.NOT_EXISTS); @@ -51,20 +51,20 @@ public void fulfillment() { requirementB.setField("second"); requirementB.setRequirement(BeaconRequirement.RequirementType.EXISTS); - boolean result = BeaconRequirement.validate(beacon, Arrays.asList(requirementA, requirementB)); + boolean result = BeaconRequirement.validate(beacon, List.of(requirementA, requirementB)); assertThat(result).isTrue(); } @Test - public void nullList() { + void nullList() { boolean result = BeaconRequirement.validate(beacon, null); assertThat(result).isTrue(); } @Test - public void emptyList() { + void emptyList() { boolean result = BeaconRequirement.validate(beacon, Collections.emptyList()); assertThat(result).isTrue(); @@ -72,12 +72,12 @@ public void emptyList() { } @Nested - public class Validate { + class Validate { - private BeaconRequirement requirement = new BeaconRequirement(); + BeaconRequirement requirement = new BeaconRequirement(); @Test - public void noFulfillmentNotExists() { + void noFulfillmentNotExists() { Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); requirement.setField("field"); requirement.setRequirement(BeaconRequirement.RequirementType.NOT_EXISTS); @@ -88,7 +88,7 @@ public void noFulfillmentNotExists() { } @Test - public void fulfillmentNotExists() { + void fulfillmentNotExists() { Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); requirement.setField("another"); requirement.setRequirement(BeaconRequirement.RequirementType.NOT_EXISTS); @@ -99,7 +99,7 @@ public void fulfillmentNotExists() { } @Test - public void noFulfillmentExists() { + void noFulfillmentExists() { Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); requirement.setField("another"); requirement.setRequirement(BeaconRequirement.RequirementType.EXISTS); @@ -110,7 +110,7 @@ public void noFulfillmentExists() { } @Test - public void fulfillmentExists() { + void fulfillmentExists() { Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); requirement.setField("field"); requirement.setRequirement(BeaconRequirement.RequirementType.EXISTS); @@ -121,9 +121,9 @@ public void fulfillmentExists() { } @Test - public void wrongInitiator() { + void wrongInitiator() { Beacon beacon = Beacon.of(Collections.singletonMap("http.initiator", "spa")); - requirement.setInitiators(Arrays.asList(InitiatorType.SPA_HARD)); + requirement.setInitiators(List.of(InitiatorType.SPA_HARD)); requirement.setRequirement(BeaconRequirement.RequirementType.HAS_INITIATOR); boolean result = requirement.validate(beacon); @@ -132,9 +132,9 @@ public void wrongInitiator() { } @Test - public void correctInitiator() { + void correctInitiator() { Beacon beacon = Beacon.of(Collections.singletonMap("http.initiator", "xhr")); - requirement.setInitiators(Arrays.asList(InitiatorType.SPA_HARD, InitiatorType.XHR)); + requirement.setInitiators(List.of(InitiatorType.SPA_HARD, InitiatorType.XHR)); requirement.setRequirement(BeaconRequirement.RequirementType.HAS_INITIATOR); boolean result = requirement.validate(beacon); @@ -143,9 +143,9 @@ public void correctInitiator() { } @Test - public void nullInitiator() { + void nullInitiator() { Beacon beacon = Beacon.of(Collections.emptyMap()); - requirement.setInitiators(Arrays.asList(InitiatorType.SPA_HARD, InitiatorType.XHR)); + requirement.setInitiators(List.of(InitiatorType.SPA_HARD, InitiatorType.XHR)); requirement.setRequirement(BeaconRequirement.RequirementType.HAS_INITIATOR); boolean result = requirement.validate(beacon); @@ -154,7 +154,7 @@ public void nullInitiator() { } @Test - public void documentInitiator() { + void documentInitiator() { Beacon beacon = Beacon.of(Collections.emptyMap()); requirement.setInitiators(Collections.singletonList(InitiatorType.DOCUMENT)); requirement.setRequirement(BeaconRequirement.RequirementType.HAS_INITIATOR); diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/ExporterIntMockMvcTestBase.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/ExporterIntMockMvcTestBase.java similarity index 88% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/ExporterIntMockMvcTestBase.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/ExporterIntMockMvcTestBase.java index 742c8f3..7048303 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/ExporterIntMockMvcTestBase.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/ExporterIntMockMvcTestBase.java @@ -1,16 +1,17 @@ -package rocks.inspectit.oce.eum.server.exporters; +package rocks.inspectit.ocelot.eum.server.exporters; import com.google.common.io.CharStreams; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.message.BasicNameValuePair; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import rocks.inspectit.ocelot.eum.server.rest.TraceController; import java.io.InputStreamReader; import java.io.Reader; @@ -28,13 +29,13 @@ public class ExporterIntMockMvcTestBase { @Autowired protected MockMvc mockMvc; + // resource span created by OpenTelemetry.js SDK + // last version tested: 0.48.0 @Value("classpath:ot-trace-prod-v0.48.0.json") - private Resource prodSpans; + Resource prodSpans; public static final String SERVICE_NAME = "E2E-test"; - final static String DEFAULT_TRACE_ID = "497d4e959f574a77d0d3abf05523ec5c"; - static String URL_KEY = "u"; static String SUT_URL = "http://test.com/login"; @@ -53,9 +54,9 @@ public class ExporterIntMockMvcTestBase { protected static String BEACON_LOAD_TIME_KEY_NAME = "t_done"; // Metric key names are in OTEL format (i.e., using '/') - protected static String METRIC_PAGE_READY_TIME_KEY_NAME = "page_ready_time/SUM"; + protected static String METRIC_PAGE_READY_TIME_KEY_NAME = "page_ready_time"; - protected static String METRIC_LOAD_TIME_KEY_NAME = "load_time/SUM"; + protected static String METRIC_LOAD_TIME_KEY_NAME = "load_time"; protected static String METRIC_END_TIMESTAMP_KEY_NAME ="end_timestamp"; @@ -81,14 +82,7 @@ protected Map getBasicBeacon() { } /** - * Posts a {@code Span} to {@link rocks.inspectit.oce.eum.server.rest.TraceController#spans(String)} using {@link #postSpan(String)} using the {@link #DEFAULT_TRACE_ID} - */ - void postSpan() throws Exception { - postSpan(DEFAULT_TRACE_ID); - } - - /** - * Posts a {@code Span} to {@link rocks.inspectit.oce.eum.server.rest.TraceController#spans(String)} + * Posts a {@code Span} to {@link TraceController#spans(String)} * * @param traceId the trace id to be used */ @@ -99,7 +93,7 @@ protected void postSpan(String traceId) throws Exception { } /** - * Posts {@code Span}s to {@link rocks.inspectit.oce.eum.server.rest.TraceController#spans(String)}. + * Posts {@code Span}s to {@link TraceController#spans(String)}. * The span data will be read from a file. *
* The span data originates from production and should always pass valid diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/ExporterIntTestBaseWithOtelCollector.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/ExporterIntTestBaseWithOtelCollector.java similarity index 75% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/ExporterIntTestBaseWithOtelCollector.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/ExporterIntTestBaseWithOtelCollector.java index 0d285da..093c120 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/ExporterIntTestBaseWithOtelCollector.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/ExporterIntTestBaseWithOtelCollector.java @@ -1,11 +1,12 @@ -package rocks.inspectit.oce.eum.server.exporters; +package rocks.inspectit.ocelot.eum.server.exporters; import com.google.protobuf.InvalidProtocolBufferException; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.ServiceRequestContext; import com.linecorp.armeria.server.grpc.protocol.AbstractUnaryGrpcService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; -import io.opencensus.trace.TraceId; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.TraceId; import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; @@ -22,7 +23,6 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.ViewDefinitionSettings; import java.io.UncheckedIOException; import java.util.ArrayList; @@ -39,8 +39,10 @@ import static org.testcontainers.Testcontainers.exposeHostPorts; /** - * Base class for exporter integration tests. Verifies integration with the OpenTelemetry Collector. The Collector can be configured to accept the required data over gRPC or HTTP and exports the data over gRPC to a server running in process, allowing assertions to be made against the data. - * This class is based on the {@link io.opentelemetry.integrationtest.OtlpExporterIntegrationTest} + * Base class for exporter integration tests. Verifies integration with the OpenTelemetry Collector. + * The Collector can be configured to accept the required data over gRPC or HTTP and exports the data over gRPC + * to a server running in process, allowing assertions to be made against the data. + * This class is based on the {@link io.opentelemetry.integrationtest.OtlpExporterIntegrationTest}. */ @Testcontainers(disabledWithoutDocker = true) @SpringBootTest @@ -48,7 +50,7 @@ public class ExporterIntTestBaseWithOtelCollector extends ExporterIntMockMvcTest protected static final String OTLP_HTTP_METRICS_PATH = "/v1/metrics"; - static final String COLLECTOR_TAG = "0.70.0"; + static final String COLLECTOR_TAG = "0.100.0"; static final String COLLECTOR_IMAGE = "otel/opentelemetry-collector-contrib:" + COLLECTOR_TAG; @@ -62,13 +64,9 @@ public class ExporterIntTestBaseWithOtelCollector extends ExporterIntMockMvcTest static final Integer COLLECTOR_PROMETHEUS_RECEIVER_PORT = 8889; - static final Integer COLLECTOR_INFLUX_DB1_PORT = 8086; - static final int COLLECTOR_ZIPKIN_PORT = 9411; - static final int COLLECTOR_TRACE_QUERY_PORT = 16686; - - private static final Logger LOGGER = Logger.getLogger(ExporterIntTestBaseWithOtelCollector.class.getName()); + static final Logger LOGGER = Logger.getLogger(ExporterIntTestBaseWithOtelCollector.class.getName()); /** * The {@link OtlpGrpcServer} used as an exporter endpoint for the OpenTelemetry Collector @@ -80,11 +78,6 @@ public class ExporterIntTestBaseWithOtelCollector extends ExporterIntMockMvcTest */ static GenericContainer collector; - // TODO: try to re-use the collector container across test classes to speed up tests - static { - - } - @BeforeAll static void startCollector() { // start the gRPC server @@ -97,13 +90,13 @@ static void startCollector() { collector = new GenericContainer<>(DockerImageName.parse(COLLECTOR_IMAGE)).withEnv("LOGGING_EXPORTER_LOG_LEVEL", "INFO") .withEnv("OTLP_EXPORTER_ENDPOINT", "host.testcontainers.internal:" + grpcServer.httpPort()) - .withEnv("PROMETHEUS_SCRAPE_TARGET", String.format("host.testcontainers.internlal:%s", COLLECTOR_PROMETHEUS_PORT)) + .withEnv("PROMETHEUS_SCRAPE_TARGET", String.format("host.testcontainers.internal:%s", COLLECTOR_PROMETHEUS_PORT)) .withEnv("PROMETHEUS_INTEGRATION_TEST_SCRAPE_TARGET", String.format("host.testcontainers.internal:%s", COLLECTOR_PROMETHEUS_RECEIVER_PORT)) .withClasspathResourceMapping("otel-config.yaml", "/otel-config.yaml", BindMode.READ_ONLY) .withCommand("--config", "/otel-config.yaml") .withLogConsumer(outputFrame -> LOGGER.log(Level.INFO, outputFrame.getUtf8String().replace("\n", ""))) // expose all relevant ports - .withExposedPorts(COLLECTOR_OTLP_GRPC_PORT, COLLECTOR_OTLP_HTTP_PORT, COLLECTOR_HEALTH_CHECK_PORT, COLLECTOR_PROMETHEUS_PORT, COLLECTOR_INFLUX_DB1_PORT, COLLECTOR_ZIPKIN_PORT, COLLECTOR_PROMETHEUS_RECEIVER_PORT) + .withExposedPorts(COLLECTOR_OTLP_GRPC_PORT, COLLECTOR_OTLP_HTTP_PORT, COLLECTOR_HEALTH_CHECK_PORT, COLLECTOR_PROMETHEUS_PORT, COLLECTOR_ZIPKIN_PORT, COLLECTOR_PROMETHEUS_RECEIVER_PORT) .waitingFor(Wait.forHttp("/").forPort(COLLECTOR_HEALTH_CHECK_PORT)); //collector.withStartupTimeout(Duration.of(1, ChronoUnit.MINUTES)); @@ -120,6 +113,7 @@ static void stop() { @BeforeEach void reset() { + GlobalOpenTelemetry.resetForTest(); grpcServer.reset(); } @@ -165,63 +159,65 @@ protected void awaitSpansExported(String expectedTraceId) { assertThat(spansLis.anyMatch(s -> s.stream() .anyMatch(span -> TraceId.fromBytes(span.getTraceId().toByteArray()) - .toLowerBase16() .equals(expectedTraceId)))).isTrue(); - }); - - } - - /** - * Verifies that the metric has been exported to and received by the {@link #grpcServer} - * - * @param metricName - * @param value - */ - protected void awaitMetricsExported(String metricName, double value) { - awaitMetricsExported(metricName, value, ViewDefinitionSettings.Aggregation.SUM); } /** * Verifies that the metric has been exported to and received by the {@link #grpcServer} * * @param metricName - * @param value - * @param aggregation */ - protected void awaitMetricsExported(String metricName, double value, ViewDefinitionSettings.Aggregation aggregation) { + protected void awaitMetricsExported(String metricName) { await().atMost(30, TimeUnit.SECONDS) .pollInterval(500, TimeUnit.MILLISECONDS) - .untilAsserted(() -> assertThat(grpcServer.metricRequests.stream()).anyMatch(mReq -> mReq.getResourceMetricsList() - .stream() - .anyMatch(rm -> + .untilAsserted(() -> assertThat(grpcServer.metricRequests.stream()) + .anyMatch(mReq -> mReq.getResourceMetricsList() + .stream() + .anyMatch(rm -> // check for the given metrics rm.getScopeMetrics(0) .getMetricsList() .stream() - .filter(metric -> metric.getName().equalsIgnoreCase(metricName)) - .anyMatch(metric -> (aggregation == ViewDefinitionSettings.Aggregation.LAST_VALUE ? metric.getGauge() - .getDataPointsList() : metric.getSum() - .getDataPointsList()).stream().anyMatch(d -> d.getAsDouble() == value))))); + .anyMatch(metric -> metric.getName().equalsIgnoreCase(metricName)) + ) + ) + ); } /** - * Checks if a metric with the given value has been recorded or not + * Checks if a histogram with the given count and sum has been recorded * - * @param value the value - * @param expected whether the value is expected or not + * @param count the expected data point count + * @param sum the expected data point sum */ - protected void assertMetric(double value, boolean expected) { - assertMetric(value, expected, ViewDefinitionSettings.Aggregation.SUM); + protected void assertHistogram(String metricName, int count, double sum) { + assertThat(grpcServer.metricRequests.stream() + .anyMatch(mReq -> mReq.getResourceMetricsList() + .stream() + .anyMatch(rm -> rm.getScopeMetricsList() + .stream() + .anyMatch(iml -> iml.getMetricsList() + .stream() + .anyMatch(metric -> metric.getName().equalsIgnoreCase(metricName) + && metric.getHistogram().getDataPointsList() + .stream() + .anyMatch(dataPoint -> + dataPoint.getCount() == count && dataPoint.getSum() == sum + ) + ) + ) + ) + ) + ).isTrue(); } /** - * Checks if a metric with the given value has been recorded or not + * Checks if a gauge with the given count and sum has been recorded * - * @param value the value - * @param expected whether the value is expected or not + * @param value the expected value */ - protected void assertMetric(double value, boolean expected, ViewDefinitionSettings.Aggregation aggregation) { + protected void assertGauge(String metricName, int value) { assertThat(grpcServer.metricRequests.stream() .anyMatch(mReq -> mReq.getResourceMetricsList() .stream() @@ -229,11 +225,15 @@ protected void assertMetric(double value, boolean expected, ViewDefinitionSettin .stream() .anyMatch(iml -> iml.getMetricsList() .stream() - .anyMatch(metric -> (aggregation == ViewDefinitionSettings.Aggregation.LAST_VALUE ? - metric.getGauge().getDataPointsList() : metric.getSum().getDataPointsList()) - .stream() - .anyMatch(d -> expected ? d.getAsDouble() == value : d.getAsDouble() != value - )))))).isTrue(); + .anyMatch(metric -> metric.getName().equalsIgnoreCase(metricName) && + metric.getGauge().getDataPointsList() + .stream() + .anyMatch(dataPoint -> dataPoint.getAsInt() == value) + ) + ) + ) + ) + ).isTrue(); } /** @@ -259,7 +259,8 @@ protected void configure(ServerBuilder sb) { @Override protected CompletionStage handleMessage(ServiceRequestContext ctx, byte[] message) { try { - traceRequests.add(ExportTraceServiceRequest.parseFrom(message)); + ExportTraceServiceRequest request = ExportTraceServiceRequest.parseFrom(message); + traceRequests.add(request); } catch (InvalidProtocolBufferException e) { throw new UncheckedIOException(e); } @@ -270,7 +271,8 @@ protected CompletionStage handleMessage(ServiceRequestContext ctx, byte[ @Override protected CompletionStage handleMessage(ServiceRequestContext ctx, byte[] message) { try { - metricRequests.add(ExportMetricsServiceRequest.parseFrom(message)); + ExportMetricsServiceRequest request = ExportMetricsServiceRequest.parseFrom(message); + metricRequests.add(request); } catch (InvalidProtocolBufferException e) { throw new UncheckedIOException(e); } @@ -281,7 +283,8 @@ protected CompletionStage handleMessage(ServiceRequestContext ctx, byte[ @Override protected CompletionStage handleMessage(ServiceRequestContext ctx, byte[] message) { try { - logRequests.add(ExportLogsServiceRequest.parseFrom(message)); + ExportLogsServiceRequest request = ExportLogsServiceRequest.parseFrom(message); + logRequests.add(request); } catch (InvalidProtocolBufferException e) { throw new UncheckedIOException(e); } @@ -291,5 +294,4 @@ protected CompletionStage handleMessage(ServiceRequestContext ctx, byte[ sb.http(0); } } - } diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/GzipCompressionMethodIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/GzipCompressionMethodIntTest.java similarity index 83% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/GzipCompressionMethodIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/GzipCompressionMethodIntTest.java index babeaf7..0b95fe5 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/GzipCompressionMethodIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/GzipCompressionMethodIntTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters; +package rocks.inspectit.ocelot.eum.server.exporters; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -9,11 +9,12 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.CompressionMethod; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.ViewDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.CompressionMethod; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.exporters.metrics.OtlpMetricsExporterService; +import rocks.inspectit.ocelot.eum.server.exporters.tracing.DelegatingSpanExporter; import java.util.List; import java.util.Map; @@ -26,7 +27,7 @@ */ @DirtiesContext @ContextConfiguration(initializers = GzipCompressionMethodIntTest.EnvInitializer.class) -public class GzipCompressionMethodIntTest extends ExporterIntTestBaseWithOtelCollector { +class GzipCompressionMethodIntTest extends ExporterIntTestBaseWithOtelCollector { @Autowired EumServerConfiguration configuration; @@ -62,7 +63,7 @@ void testCompressionMethodSpanExporter() { .getCompression()).isEqualTo(CompressionMethod.GZIP); Optional spanExporter = spanExporters.stream() - .filter(sE -> sE instanceof OtlpGrpcSpanExporter) + .filter(sE -> sE instanceof DelegatingSpanExporter) .findFirst(); assertThat(spanExporter.isPresent()).isTrue(); } @@ -94,7 +95,7 @@ void verifyOtlpGrpcMetrics() throws Exception { beacon.put(BEACON_END_TIMESTAMP_KEY_NAME, "41"); sendBeacon(beacon); // wait until metrics have been exported - awaitMetricsExported(METRIC_END_TIMESTAMP_KEY_NAME, 41, ViewDefinitionSettings.Aggregation.LAST_VALUE); - assertMetric(1334, false, ViewDefinitionSettings.Aggregation.LAST_VALUE); + awaitMetricsExported(METRIC_END_TIMESTAMP_KEY_NAME); + assertGauge(METRIC_END_TIMESTAMP_KEY_NAME, 41); } -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporterAnnotationsTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporterAnnotationsTest.java similarity index 80% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporterAnnotationsTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporterAnnotationsTest.java index d7ddb65..c81f3f4 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporterAnnotationsTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporterAnnotationsTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters.beacon; +package rocks.inspectit.ocelot.eum.server.exporters.beacon; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -9,11 +9,11 @@ import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest -public class BeaconHttpExporterAnnotationsTest { +class BeaconHttpExporterAnnotationsTest { @TestPropertySource(properties = {"inspectit-eum-server.exporters.beacons.http.enabled=ENABLED"}) @Nested - public class Enabled { + class Enabled { @Autowired BeaconHttpExporter exporter; @@ -22,7 +22,7 @@ public class Enabled { ExportWorkerFactory factory; @Test - public void testBeanWasCreated() { + void testBeanWasCreated() { assertThat(exporter).isNotNull(); assertThat(factory).isNotNull(); } @@ -30,7 +30,7 @@ public void testBeanWasCreated() { @TestPropertySource(properties = {"inspectit-eum-server.exporters.beacons.http.enabled=DISABLED"}) @Nested - public class Disabled { + class Disabled { @Autowired(required = false) BeaconHttpExporter exporter; @@ -39,10 +39,9 @@ public class Disabled { ExportWorkerFactory factory; @Test - public void testBeanWasNotCreated() { + void testBeanWasNotCreated() { assertThat(exporter).isNull(); assertThat(factory).isNull(); } } - } diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporterTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporterTest.java similarity index 78% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporterTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporterTest.java index 8f88eff..0ffa98e 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/BeaconHttpExporterTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/BeaconHttpExporterTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters.beacon; +package rocks.inspectit.ocelot.eum.server.exporters.beacon; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -9,10 +9,10 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.beacon.BeaconHttpExporterSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.beacon.BeaconHttpExporterSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; import java.time.Duration; import java.util.concurrent.BlockingQueue; @@ -26,18 +26,18 @@ class BeaconHttpExporterTest { @InjectMocks - private BeaconHttpExporter exporter; + BeaconHttpExporter exporter; @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private EumServerConfiguration configuration; + EumServerConfiguration configuration; @Mock - private ExportWorkerFactory workerFactory; + ExportWorkerFactory workerFactory; - private BeaconHttpExporterSettings exporterSettings; + BeaconHttpExporterSettings exporterSettings; @BeforeEach - public void beforeEach() { + void beforeEach() { exporterSettings = new BeaconHttpExporterSettings(); exporterSettings.setEnabled(ExporterEnabledState.ENABLED); exporterSettings.setEndpointUrl("http//localhost:1000"); @@ -49,10 +49,10 @@ public void beforeEach() { } @Nested - public class Initialize { + class Initialize { @Test - public void initialize() { + void initialize() { exporter.initialize(); ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) ReflectionTestUtils.getField(exporter, "executor"); @@ -64,10 +64,10 @@ public void initialize() { } @Nested - public class Destroy { + class Destroy { @Test - public void destroyExporter() throws InterruptedException { + void destroyExporter() throws InterruptedException { exporter.initialize(); ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) ReflectionTestUtils.getField(exporter, "executor"); @@ -80,16 +80,16 @@ public void destroyExporter() throws InterruptedException { } @Nested - public class Export { + class Export { @Mock - private ScheduledExecutorService executor; + ScheduledExecutorService executor; @Mock - private Beacon beaconA, beaconB; + Beacon beaconA, beaconB; @Test - public void isDisabled() throws InterruptedException { + void isDisabled() { exporterSettings.setEnabled(ExporterEnabledState.DISABLED); exporter.initialize(); ReflectionTestUtils.setField(exporter, "executor", executor); @@ -100,7 +100,7 @@ public void isDisabled() throws InterruptedException { } @Test - public void successfulExport() throws InterruptedException { + void successfulExport() { exporterSettings.setMaxBatchSize(3); exporter.initialize(); ReflectionTestUtils.setField(exporter, "executor", executor); @@ -126,6 +126,5 @@ public void successfulExport() throws InterruptedException { assertThat(nextBeaconBuffer).isNotSameAs(beaconBuffer); assertThat(nextBeaconBuffer).isEmpty(); } - } } diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/ExportWorkerFactoryTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/ExportWorkerFactoryTest.java similarity index 76% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/ExportWorkerFactoryTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/ExportWorkerFactoryTest.java index 232cf14..3e2a5a3 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/beacon/ExportWorkerFactoryTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/beacon/ExportWorkerFactoryTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters.beacon; +package rocks.inspectit.ocelot.eum.server.exporters.beacon; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -11,10 +11,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.RestTemplate; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.exporters.beacon.ExportWorkerFactory.ExportWorker; -import rocks.inspectit.oce.eum.server.metrics.SelfMonitoringMetricManager; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.exporters.beacon.ExportWorkerFactory.ExportWorker; +import rocks.inspectit.ocelot.eum.server.metrics.SelfMonitoringMetricManager; import java.net.URI; import java.util.concurrent.BlockingQueue; @@ -28,22 +28,22 @@ class ExportWorkerFactoryTest { @InjectMocks - private ExportWorkerFactory factory; + ExportWorkerFactory factory; @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private EumServerConfiguration configuration; + EumServerConfiguration configuration; @Mock - private BlockingQueue buffer; + BlockingQueue buffer; @Mock - private SelfMonitoringMetricManager selfMonitoring; + SelfMonitoringMetricManager selfMonitoring; @Nested - public class Initialize { + class Initialize { @Test - public void successfulWithoutAuthentication() { + void successfulWithoutAuthentication() { when(configuration.getExporters().getBeacons().getHttp().getEndpointUrl()).thenReturn("http://target:8080"); factory.initialize(); @@ -55,7 +55,7 @@ public void successfulWithoutAuthentication() { } @Test - public void successfulWithAuthentication() { + void successfulWithAuthentication() { when(configuration.getExporters().getBeacons().getHttp().getEndpointUrl()).thenReturn("http://target:8080"); when(configuration.getExporters().getBeacons().getHttp().getUsername()).thenReturn("user"); when(configuration.getExporters().getBeacons().getHttp().getPassword()).thenReturn("passwd"); @@ -69,10 +69,10 @@ public void successfulWithAuthentication() { } @Nested - public class GetWorker { + class GetWorker { @Test - public void successful() { + void successful() { ExportWorker worker = factory.getWorker(buffer); BlockingQueue workerBuffer = (BlockingQueue) ReflectionTestUtils.getField(worker, "buffer"); @@ -82,16 +82,16 @@ public void successful() { } @Nested - public class ExportWorker_run { + class ExportWorker_run { @Mock - private RestTemplate restTemplate; + RestTemplate restTemplate; @Mock - private ResponseEntity response; + ResponseEntity response; @Test - public void successful() { + void successful() { when(response.getStatusCode()).thenReturn(HttpStatus.OK); when(restTemplate.postForEntity(any(), any(), eq(Void.class))).thenReturn(response); ReflectionTestUtils.setField(factory, "restTemplate", restTemplate); diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/OtlpGrpcMetricExporterServiceIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpGrpcMetricExporterServiceIntTest.java similarity index 77% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/OtlpGrpcMetricExporterServiceIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpGrpcMetricExporterServiceIntTest.java index c0663c4..c190b48 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/OtlpGrpcMetricExporterServiceIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpGrpcMetricExporterServiceIntTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters.metrics; +package rocks.inspectit.ocelot.eum.server.exporters.metrics; import org.junit.jupiter.api.Test; import org.springframework.boot.test.util.TestPropertyValues; @@ -6,15 +6,15 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; -import rocks.inspectit.oce.eum.server.exporters.ExporterIntTestBaseWithOtelCollector; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.exporters.ExporterIntTestBaseWithOtelCollector; import java.util.Map; @DirtiesContext @ContextConfiguration(initializers = OtlpGrpcMetricExporterServiceIntTest.EnvInitializer.class) -public class OtlpGrpcMetricExporterServiceIntTest extends ExporterIntTestBaseWithOtelCollector { +class OtlpGrpcMetricExporterServiceIntTest extends ExporterIntTestBaseWithOtelCollector { static class EnvInitializer implements ApplicationContextInitializer { @Override @@ -35,8 +35,7 @@ void verifyOtlpGrpcMetrics() throws Exception { beacon.put(BEACON_PAGE_READY_TIME_KEY_NAME, "42"); sendBeacon(beacon); // wait until metrics have been exported - awaitMetricsExported(METRIC_PAGE_READY_TIME_KEY_NAME, 42); - assertMetric(1338, false); + awaitMetricsExported(METRIC_PAGE_READY_TIME_KEY_NAME); + assertHistogram(METRIC_PAGE_READY_TIME_KEY_NAME, 1, 42); } - } diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/OtlpHttpMetricExporterServiceIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpHttpMetricExporterServiceIntTest.java similarity index 76% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/OtlpHttpMetricExporterServiceIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpHttpMetricExporterServiceIntTest.java index 0997e9e..976489e 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/OtlpHttpMetricExporterServiceIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/OtlpHttpMetricExporterServiceIntTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters.metrics; +package rocks.inspectit.ocelot.eum.server.exporters.metrics; import org.junit.jupiter.api.Test; import org.springframework.boot.test.util.TestPropertyValues; @@ -6,15 +6,15 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; -import rocks.inspectit.oce.eum.server.exporters.ExporterIntTestBaseWithOtelCollector; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.exporters.ExporterIntTestBaseWithOtelCollector; import java.util.Map; @DirtiesContext @ContextConfiguration(initializers = OtlpHttpMetricExporterServiceIntTest.EnvInitializer.class) -public class OtlpHttpMetricExporterServiceIntTest extends ExporterIntTestBaseWithOtelCollector { +class OtlpHttpMetricExporterServiceIntTest extends ExporterIntTestBaseWithOtelCollector { static class EnvInitializer implements ApplicationContextInitializer { @@ -35,7 +35,7 @@ void verifyOtlpHttpMetrics() throws Exception { beacon.put(BEACON_PAGE_READY_TIME_KEY_NAME, "1336"); sendBeacon(beacon); // wait until metrics have been exported - awaitMetricsExported(METRIC_PAGE_READY_TIME_KEY_NAME, 1336); - assertMetric(1339, false); + awaitMetricsExported(METRIC_PAGE_READY_TIME_KEY_NAME); + assertHistogram(METRIC_PAGE_READY_TIME_KEY_NAME, 1, 1336); } } diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/PrometheusExporterServiceIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/PrometheusExporterServiceIntTest.java similarity index 62% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/PrometheusExporterServiceIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/PrometheusExporterServiceIntTest.java index 0bbca76..28260c3 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/metrics/PrometheusExporterServiceIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/metrics/PrometheusExporterServiceIntTest.java @@ -1,14 +1,13 @@ -package rocks.inspectit.oce.eum.server.exporters.metrics; - -import io.prometheus.client.CollectorRegistry; -import org.apache.http.HttpResponse; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; +package rocks.inspectit.ocelot.eum.server.exporters.metrics; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -18,14 +17,13 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.util.TestSocketUtils; -import rocks.inspectit.oce.eum.server.exporters.ExporterIntMockMvcTestBase; +import rocks.inspectit.ocelot.eum.server.exporters.ExporterIntMockMvcTestBase; import java.util.Map; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; /** * Integration test of PrometheusExporterService @@ -33,7 +31,7 @@ @SpringBootTest @ContextConfiguration(initializers = PrometheusExporterServiceIntTest.EnvInitializer.class) @DirtiesContext -public class PrometheusExporterServiceIntTest extends ExporterIntMockMvcTestBase { +class PrometheusExporterServiceIntTest extends ExporterIntMockMvcTestBase { private static int PROMETHEUS_PORT; @@ -50,58 +48,51 @@ public void initialize(ConfigurableApplicationContext applicationContext) { } } - private static CloseableHttpClient httpClient; - @BeforeAll - public static void beforeClass() { - CollectorRegistry.defaultRegistry.clear(); - } + static CloseableHttpClient httpClient; @BeforeEach - public void initClient() { + void initClient() { + GlobalOpenTelemetry.resetForTest(); HttpClientBuilder builder = HttpClientBuilder.create(); httpClient = builder.build(); } @AfterEach - public void closeClient() throws Exception { + void closeClient() throws Exception { httpClient.close(); } @Test - public void testDefaultSettings() throws Exception { + void testDefaultSettings() throws Exception { HttpGet httpGet = new HttpGet("http://localhost:" + PROMETHEUS_PORT + "/metrics"); - int statusCode = httpClient.execute(httpGet).getStatusLine().getStatusCode(); + int statusCode = httpClient.execute(httpGet).getCode(); assertThat(statusCode).isEqualTo(200); } /** * The application should expose no view, since no beacon entry maps to the default implementation. - * - * @throws Exception */ @Test - public void expectNoViews() throws Exception { + void expectNoViews() throws Exception { Map beacon = getBasicBeacon(); beacon.put(FAKE_BEACON_KEY_NAME, "Fake Value"); sendBeacon(beacon); await().atMost(15, TimeUnit.SECONDS).pollInterval(2, TimeUnit.SECONDS).untilAsserted(() -> { - HttpResponse response = httpClient.execute(new HttpGet("http://localhost:" + PROMETHEUS_PORT + "/metrics)")); - ResponseHandler responseHandler = new BasicResponseHandler(); - assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200); + ClassicHttpResponse response = httpClient.execute(new HttpGet("http://localhost:" + PROMETHEUS_PORT + "/metrics)")); + HttpClientResponseHandler responseHandler = new BasicHttpClientResponseHandler(); + assertThat(response.getCode()).isEqualTo(200); assertThat(responseHandler.handleResponse(response)).doesNotContain("Fake Value"); }); } /** * The application should expose one view, since one beacon entry maps to the default implementation. - * - * @throws Exception */ @Test - public void expectOneView() throws Exception { + void expectOneView() throws Exception { Map beacon = getBasicBeacon(); // send the beacon. use a different metric (t_load) as the different metric exporter test cases overload the metrics beacon.put(BEACON_LOAD_TIME_KEY_NAME, "12"); @@ -111,9 +102,10 @@ public void expectOneView() throws Exception { String metricKeyName = METRIC_LOAD_TIME_KEY_NAME.replaceAll("/","_"); await().atMost(15, TimeUnit.SECONDS).pollInterval(2, TimeUnit.SECONDS).untilAsserted(() -> { - HttpResponse response = httpClient.execute(new HttpGet("http://localhost:" + PROMETHEUS_PORT + "/metrics)")); - ResponseHandler responseHandler = new BasicResponseHandler(); - assertThat(responseHandler.handleResponse(response)).contains(metricKeyName+"{COUNTRY_CODE=\"\",OS=\"\",URL=\"http://test.com/login\",} 12.0"); + ClassicHttpResponse response = httpClient.execute(new HttpGet("http://localhost:" + PROMETHEUS_PORT + "/metrics)")); + HttpClientResponseHandler responseHandler = new BasicHttpClientResponseHandler(); + String responseString = responseHandler.handleResponse(response); + assertThat(responseString).contains(metricKeyName+"_milliseconds_sum{COUNTRY_CODE=\"\",OS=\"\",URL=\"http://test.com/login\",otel_scope_name=\"rocks.inspectit.ocelot\"} 12.0"); }); } } diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/tracing/OtlpGrpcTraceExporterIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/OtlpGrpcTraceExporterIntTest.java similarity index 78% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/tracing/OtlpGrpcTraceExporterIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/OtlpGrpcTraceExporterIntTest.java index 1fb91d2..bfb302b 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/tracing/OtlpGrpcTraceExporterIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/OtlpGrpcTraceExporterIntTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters.tracing; +package rocks.inspectit.ocelot.eum.server.exporters.tracing; import org.junit.jupiter.api.Test; import org.springframework.boot.test.util.TestPropertyValues; @@ -6,13 +6,13 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; -import rocks.inspectit.oce.eum.server.exporters.ExporterIntTestBaseWithOtelCollector; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.exporters.ExporterIntTestBaseWithOtelCollector; @DirtiesContext @ContextConfiguration(initializers = OtlpGrpcTraceExporterIntTest.EnvInitializer.class) -public class OtlpGrpcTraceExporterIntTest extends ExporterIntTestBaseWithOtelCollector { +class OtlpGrpcTraceExporterIntTest extends ExporterIntTestBaseWithOtelCollector { static class EnvInitializer implements ApplicationContextInitializer { diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/tracing/OtlpHttpTraceExporterIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/OtlpHttpTraceExporterIntTest.java similarity index 78% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/tracing/OtlpHttpTraceExporterIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/OtlpHttpTraceExporterIntTest.java index d043956..9c0898c 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/tracing/OtlpHttpTraceExporterIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/OtlpHttpTraceExporterIntTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.exporters.tracing; +package rocks.inspectit.ocelot.eum.server.exporters.tracing; import org.junit.jupiter.api.Test; import org.springframework.boot.test.util.TestPropertyValues; @@ -6,13 +6,13 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.ExporterEnabledState; -import rocks.inspectit.oce.eum.server.configuration.model.exporters.TransportProtocol; -import rocks.inspectit.oce.eum.server.exporters.ExporterIntTestBaseWithOtelCollector; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.ExporterEnabledState; +import rocks.inspectit.ocelot.eum.server.configuration.model.exporters.TransportProtocol; +import rocks.inspectit.ocelot.eum.server.exporters.ExporterIntTestBaseWithOtelCollector; @DirtiesContext @ContextConfiguration(initializers = OtlpHttpTraceExporterIntTest.EnvInitializer.class) -public class OtlpHttpTraceExporterIntTest extends ExporterIntTestBaseWithOtelCollector { +class OtlpHttpTraceExporterIntTest extends ExporterIntTestBaseWithOtelCollector { static class EnvInitializer implements ApplicationContextInitializer { diff --git a/src/test/java/rocks/inspectit/oce/eum/server/exporters/configuration/TraceExportersConfigurationOtlpTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/TraceExportersConfigurationOtlpTest.java similarity index 74% rename from src/test/java/rocks/inspectit/oce/eum/server/exporters/configuration/TraceExportersConfigurationOtlpTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/TraceExportersConfigurationOtlpTest.java index f9e0dab..4e5be4b 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/exporters/configuration/TraceExportersConfigurationOtlpTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/exporters/tracing/TraceExportersConfigurationOtlpTest.java @@ -1,6 +1,5 @@ -package rocks.inspectit.oce.eum.server.exporters.configuration; +package rocks.inspectit.ocelot.eum.server.exporters.tracing; -import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -14,43 +13,43 @@ * i.e. whether the Bean only gets created when 'otlp.enabled' is not set to DISABLED and 'otlp.endpoint' is not empty. */ @SpringBootTest -public class TraceExportersConfigurationOtlpTest { +class TraceExportersConfigurationOtlpTest { @TestPropertySource(properties = {"inspectit-eum-server.exporters.tracing.otlp.endpoint=", "inspectit-eum-server.exporters.tracing.otlp.enabled=ENABLED"}) @Nested - public class OtlpEndpointTest { + class OtlpEndpointTest { @Autowired(required = false) - OtlpGrpcSpanExporter exporter; + DelegatingSpanExporter exporter; @Test - public void testBeanWasNotCreated() { + void testBeanWasNotCreated() { assertThat(exporter).isNull(); } } @TestPropertySource(properties = {"inspectit-eum-server.exporters.tracing.otlp.endpoint=localhost:1234", "inspectit-eum-server.exporters.tracing.otlp.enabled=DISABLED", "inspectit-eum-server.exporters.tracing.otlp.protocol=grpc"}) @Nested - public class DisabledTest { + class DisabledTest { @Autowired(required = false) - OtlpGrpcSpanExporter exporter; + DelegatingSpanExporter exporter; @Test - public void testBeanWasNotCreated() { + void testBeanWasNotCreated() { assertThat(exporter).isNull(); } } @TestPropertySource(properties = {"inspectit-eum-server.exporters.tracing.otlp.endpoint=localhost:1234", "inspectit-eum-server.exporters.tracing.otlp.enabled=ENABLED", "inspectit-eum-server.exporters.tracing.otlp.protocol=grpc"}) @Nested - public class BothAvailableTest { + class BothAvailableTest { @Autowired - OtlpGrpcSpanExporter exporter; + DelegatingSpanExporter exporter; @Test - public void testBeanWasCreated() { + void testBeanWasCreated() { assertThat(exporter).isNotNull(); } } diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/AttributesRegistryTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/AttributesRegistryTest.java new file mode 100644 index 0000000..c88f01b --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/AttributesRegistryTest.java @@ -0,0 +1,82 @@ +package rocks.inspectit.ocelot.eum.server.metrics; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class AttributesRegistryTest { + + @InjectMocks + AttributesRegistry registry; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + EumServerConfiguration configuration; + + @Test + void registerNoAttributes() { + ViewDefinitionSettings view = new ViewDefinitionSettings(); + view.setAttributes(Collections.emptyMap()); + + registry.processAttributeKeysForView(view); + + assertThat(registry.getRegisteredAttributes()).isEmpty(); + } + + @Test + void registerViewAttribute() { + when(configuration.getAttributes().getExtra()).thenReturn(Collections.singletonMap("first", "value")); + when(configuration.getAttributes().getDefineAsGlobal()).thenReturn(Collections.singleton("first")); + + ViewDefinitionSettings view = new ViewDefinitionSettings(); + view.setAttributes(Map.of("first", true)); + + registry.processAttributeKeysForView(view); + + assertThat(registry.getRegisteredAttributes()).containsExactly("first"); + } + + @Test + void registerExtraAttributes() { + Map extraAttributes = Map.of("first", "value1", "second", "value2"); + when(configuration.getAttributes().getExtra()).thenReturn(extraAttributes); + when(configuration.getAttributes().getDefineAsGlobal()).thenReturn(Set.of("first", "second")); + + ViewDefinitionSettings view = new ViewDefinitionSettings(); + view.setAttributes(Collections.emptyMap()); + + registry.processAttributeKeysForView(view); + + assertThat(registry.getRegisteredExtraAttributes()).containsExactlyInAnyOrder("first", "second"); + } + + @Test + void registerAttributesMultipleTimes() { + ViewDefinitionSettings view1 = new ViewDefinitionSettings(); + view1.setAttributes(Map.of("first", true)); + ViewDefinitionSettings view2 = new ViewDefinitionSettings(); + view2.setAttributes(Map.of("second", true)); + + // first execution + registry.processAttributeKeysForView(view1); + + assertThat(registry.getRegisteredAttributes()).containsExactly("first"); + + // second execution + registry.processAttributeKeysForView(view2); + + assertThat(registry.getRegisteredAttributes()).containsExactlyInAnyOrder("first", "second"); + } +} diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/BeaconMetricManagerTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/BeaconMetricManagerTest.java new file mode 100644 index 0000000..2930e8b --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/BeaconMetricManagerTest.java @@ -0,0 +1,162 @@ +package rocks.inspectit.ocelot.eum.server.metrics; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.sdk.metrics.InstrumentValueType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.recorder.BeaconRecorder; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.BeaconMetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.AggregationType; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.BeaconAttributeSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; + +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +/** + * Tests {@link BeaconMetricManager} + */ +@ExtendWith(MockitoExtension.class) +class BeaconMetricManagerTest { + + @InjectMocks + BeaconMetricManager beaconMetricManager; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + EumServerConfiguration configuration; + + @Mock + InstrumentManager instrumentManager; + + @Mock + AttributesRegistry attributesRegistry; + + BeaconRecorder recorder = mock(BeaconRecorder.class); + + @Spy + List beaconRecorders = new ArrayList<>(Collections.singletonList(recorder)); + + final Set registeredAttributes = new HashSet<>(Arrays.asList("first", "second", "third")); + + final String METRIC_NAME = "Dummy metric name"; + + @Nested + class ProcessUsedTags { + + @BeforeEach + void beforeEach() { + when(attributesRegistry.getRegisteredAttributes()).thenReturn(registeredAttributes); + } + + + @Test + void processOneUsedTag() { + Map beaconSettings = Collections.singletonMap("first", new BeaconAttributeSettings()); + when(configuration.getAttributes().getBeacon()).thenReturn(beaconSettings); + + beaconMetricManager.registerBeaconAttributes(); + + assertThat(beaconMetricManager.registeredBeaconAttributes).containsExactly("first"); + } + + @Test + void processMultipleUsedTags() { + Map beaconSettings = ImmutableMap.of("first", new BeaconAttributeSettings(), "third", new BeaconAttributeSettings()); + when(configuration.getAttributes().getBeacon()).thenReturn(beaconSettings); + + beaconMetricManager.registerBeaconAttributes(); + + assertThat(beaconMetricManager.registeredBeaconAttributes).containsExactlyInAnyOrder("first", "third"); + } + + @Test + void processNoTags() { + when(configuration.getAttributes().getBeacon()).thenReturn(Collections.emptyMap()); + + beaconMetricManager.registerBeaconAttributes(); + + assertThat(beaconMetricManager.registeredBeaconAttributes).isEmpty(); + } + } + + @Nested + class ProcessBeacon { + + private Map definitionMap; + + @BeforeEach + void setupConfiguration() { + ViewDefinitionSettings view = ViewDefinitionSettings.builder() + .bucketBoundaries(Arrays.asList(0d, 1d)) + .aggregation(AggregationType.HISTOGRAM) + .attribute("TAG_1", true) + .attribute("TAG_2", true) + .build(); + Map views = new HashMap<>(); + views.put(METRIC_NAME + "/HISTOGRAM", view); + + BeaconMetricDefinitionSettings dummyMetricDefinition = BeaconMetricDefinitionSettings.beaconMetricBuilder() + .valueExpression("{dummy_beacon_field}") + .description("Dummy description") + //.instrumentType() + .valueType(InstrumentValueType.DOUBLE) + .unit("ms") + .enabled(true) + .views(views) + .build(); + + definitionMap = new HashMap<>(); + definitionMap.put(METRIC_NAME, dummyMetricDefinition); + } + + @Test + void verifyNoViewIsGeneratedWithEmptyBeacon() { + when(configuration.getDefinitions()).thenReturn(definitionMap); + HashMap beaconMap = new HashMap<>(); + + beaconMetricManager.processBeacon(Beacon.of(beaconMap)); + + verifyNoMoreInteractions(instrumentManager); + } + + @Test + void verifyNoViewIsGeneratedWithFullBeacon() { + when(configuration.getDefinitions()).thenReturn(definitionMap); + HashMap beaconMap = new HashMap<>(); + beaconMap.put("fake_beacon_field", "12d"); + + beaconMetricManager.processBeacon(Beacon.of(beaconMap)); + + verifyNoMoreInteractions(instrumentManager); + } + + @Test + void beaconRecordersProcessed() { + when(instrumentManager.getBaggage(any())).thenReturn(Baggage.empty()); + when(configuration.getDefinitions()).thenReturn(definitionMap); + when(recorder.canRecord(anyString())).thenReturn(true); + + HashMap beaconMap = new HashMap<>(); + beaconMap.put("fake_beacon_field", "12d"); + Beacon beacon = Beacon.of(beaconMap); + + beaconMetricManager.processBeacon(beacon); + + assertThat(beaconRecorders).allSatisfy(beaconRecorder -> { + verify(beaconRecorder).canRecord(METRIC_NAME); + verify(beaconRecorder).record(beacon); + verifyNoMoreInteractions(beaconRecorder); + }); + verifyNoMoreInteractions(instrumentManager); + } + } +} diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentManagerTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentManagerTest.java new file mode 100644 index 0000000..1f517c1 --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/InstrumentManagerTest.java @@ -0,0 +1,93 @@ +package rocks.inspectit.ocelot.eum.server.metrics; + +import io.opentelemetry.api.baggage.Baggage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.AggregationType; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import java.util.*; + +@ExtendWith(MockitoExtension.class) +class InstrumentManagerTest { + + @InjectMocks + InstrumentManager manager; + + @Mock + InstrumentFactory factory; + + @Mock + AttributesRegistry registry; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + EumServerConfiguration configuration; + + final String metricName = "my-metric"; + + @Test + void shouldCreateInstrumentAndProcessAttributes() { + MetricDefinitionSettings metric = new MetricDefinitionSettings(); + ViewDefinitionSettings view = new ViewDefinitionSettings(); + metric.setViews(Map.of("my-view", view)); + + manager.createInstrument(metricName, metric); + + verify(factory).createInstrument(anyString(), any(MetricDefinitionSettings.class)); + verify(registry).processAttributeKeysForView(any(ViewDefinitionSettings.class)); + } + + @Test + void shouldCreateInstrumentAndProcessAttributesWhenNoViews() { + MetricDefinitionSettings metric = new MetricDefinitionSettings(); + + manager.createInstrument(metricName, metric); + + verify(factory).createInstrument(anyString(), any(MetricDefinitionSettings.class)); + verify(registry).processAttributeKeysForView(any(ViewDefinitionSettings.class)); + } + + @Test + void shouldNotCreateInstrumentAndProcessAttributesWhenTimeWindowView() { + MetricDefinitionSettings metric = new MetricDefinitionSettings(); + ViewDefinitionSettings view = new ViewDefinitionSettings(); + view.setAggregation(AggregationType.QUANTILES); + metric.setViews(Map.of("my-view", view)); + + manager.createInstrument(metricName, metric); + + verifyNoInteractions(factory); + verify(registry).processAttributeKeysForView(any(ViewDefinitionSettings.class)); + } + + @Test + void shouldReturnBaggageWithCustomAndGlobalAttributes() { + Map customAttributes = Map.of("key1", "value1", "key2", "value2"); + when(configuration.getAttributes().getExtra()).thenReturn(Map.of("key3", "value3")); + when(registry.getRegisteredExtraAttributes()).thenReturn(Set.of("key3")); + + Baggage baggage = manager.getBaggage(customAttributes); + + assertThat(baggage.asMap().get("key1").getValue()).isEqualTo("value1"); + assertThat(baggage.asMap().get("key2").getValue()).isEqualTo("value2"); + assertThat(baggage.asMap().get("key3").getValue()).isEqualTo("value3"); + } + + @Test + void shouldReturnBaggageWithOnlyCustomAttributes() { + Map customAttributes = Map.of("key1", "value1", "key2", "value2"); + when(registry.getRegisteredExtraAttributes()).thenReturn(Set.of("key3")); + + Baggage baggage = manager.getBaggage(customAttributes); + + assertThat(baggage.asMap().get("key1").getValue()).isEqualTo("value1"); + assertThat(baggage.asMap().get("key2").getValue()).isEqualTo("value2"); + } +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/metrics/SelfMonitoringMetricManagerTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/SelfMonitoringMetricManagerTest.java similarity index 52% rename from src/test/java/rocks/inspectit/oce/eum/server/metrics/SelfMonitoringMetricManagerTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/metrics/SelfMonitoringMetricManagerTest.java index 11bed3c..9f97a65 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/metrics/SelfMonitoringMetricManagerTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/SelfMonitoringMetricManagerTest.java @@ -1,19 +1,21 @@ -package rocks.inspectit.oce.eum.server.metrics; +package rocks.inspectit.ocelot.eum.server.metrics; -import io.opencensus.stats.*; +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.InstrumentValueType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.MetricDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.metric.definition.ViewDefinitionSettings; -import rocks.inspectit.oce.eum.server.configuration.model.selfmonitoring.SelfMonitoringSettings; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.MetricDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.AggregationType; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.selfmonitoring.SelfMonitoringSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; import java.util.HashMap; import java.util.Map; @@ -25,7 +27,7 @@ * Tests {@link SelfMonitoringMetricManager} */ @ExtendWith(MockitoExtension.class) -public class SelfMonitoringMetricManagerTest { +class SelfMonitoringMetricManagerTest { @InjectMocks SelfMonitoringMetricManager selfMonitoringMetricManager; @@ -34,34 +36,26 @@ public class SelfMonitoringMetricManagerTest { EumServerConfiguration configuration; @Mock - MeasuresAndViewsManager measuresAndViewsManager; - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - StatsRecorder statsRecorder; - - @Mock - ViewManager viewManager; - - @Mock - MeasureMap measureMap; + InstrumentManager instrumentManager; @Nested class Record { - private SelfMonitoringSettings selfMonitoringSettings = new SelfMonitoringSettings(); + final SelfMonitoringSettings selfMonitoringSettings = new SelfMonitoringSettings(); @BeforeEach void setupConfiguration() { ViewDefinitionSettings view = ViewDefinitionSettings.builder() - .aggregation(ViewDefinitionSettings.Aggregation.COUNT) - .tag("TAG_1", true) + .aggregation(AggregationType.SUM) + .attribute("TAG_1", true) .build(); Map views = new HashMap<>(); - views.put("inspectit-eum/self/beacons_received/COUNT", view); + views.put("beacons_received", view); MetricDefinitionSettings dummyMetricDefinition = MetricDefinitionSettings.builder() .description("Dummy description") - .type(MetricDefinitionSettings.MeasureType.DOUBLE) + .instrumentType(InstrumentType.COUNTER) + .valueType(InstrumentValueType.DOUBLE) .unit("number") .enabled(true) .views(views) @@ -71,7 +65,7 @@ void setupConfiguration() { definitionMap.put("beacons_received", dummyMetricDefinition); selfMonitoringSettings.setEnabled(true); selfMonitoringSettings.setMetrics(definitionMap); - selfMonitoringSettings.setMetricPrefix("inspectit-eum/self/"); + selfMonitoringSettings.setMetricPrefix("inspectit_eum_self_"); } @Test @@ -81,7 +75,7 @@ void verifyNoViewIsGeneratedWithDisabledSelfMonitoring() { selfMonitoringMetricManager.record("beacons_received", 1); - verifyNoMoreInteractions(viewManager, statsRecorder); + verifyNoMoreInteractions(instrumentManager); } @Test @@ -90,7 +84,7 @@ void verifyNoViewIsGeneratedWithNonExistentMetric() { selfMonitoringMetricManager.record("apples_received", 1); - verifyNoMoreInteractions(viewManager, statsRecorder); + verifyNoMoreInteractions(instrumentManager); } @Test @@ -100,10 +94,24 @@ void verifySelfMonitoringMetricManagerIsCalled() { selfMonitoringMetricManager.initMetrics(); ArgumentCaptor mdsCaptor = ArgumentCaptor.forClass(MetricDefinitionSettings.class); - verify(measuresAndViewsManager).updateMetrics(eq("inspectit-eum/self/beacons_received"), mdsCaptor.capture()); - verifyNoMoreInteractions(viewManager, statsRecorder, measureMap); + verify(instrumentManager).createInstrument(eq("inspectit_eum_self_beacons_received"), mdsCaptor.capture()); + verifyNoMoreInteractions(instrumentManager); + + assertThat(mdsCaptor.getValue().getViews().keySet()).containsExactly("inspectit_eum_self_beacons_received"); + } + + @Test + void verifyViewIsRecorded() { + when(configuration.getSelfMonitoring()).thenReturn(selfMonitoringSettings); + when(instrumentManager.getBaggage(any())).thenReturn(Baggage.empty()); + + selfMonitoringMetricManager.record("beacons_received", 1); + + ArgumentCaptor mdsCaptor = ArgumentCaptor.forClass(MetricDefinitionSettings.class); + verify(instrumentManager).recordMetric(eq("inspectit_eum_self_beacons_received"), mdsCaptor.capture(), any(Number.class)); + verifyNoMoreInteractions(instrumentManager); - assertThat(mdsCaptor.getValue().getViews().keySet()).containsExactly("inspectit-eum/self/beacons_received/COUNT"); + assertThat(mdsCaptor.getValue().getViews().keySet()).containsExactly("inspectit_eum_self_beacons_received"); } } } diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/TimeWindowViewManagerTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/TimeWindowViewManagerTest.java new file mode 100644 index 0000000..c93a6fb --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/TimeWindowViewManagerTest.java @@ -0,0 +1,100 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.attributes.AttributeSettings; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.AggregationType; +import rocks.inspectit.ocelot.eum.server.configuration.model.metrics.definition.view.ViewDefinitionSettings; +import rocks.inspectit.ocelot.eum.server.metrics.AttributesRegistry; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.TimeWindowView; + +import java.time.Duration; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class TimeWindowViewManagerTest { + + @InjectMocks + TimeWindowViewManager viewManager; + + @Mock + EumServerConfiguration configuration; + + @Mock + AttributesRegistry attributesRegistry; + + @BeforeEach + void beforeEach() { + lenient().when(configuration.getAttributes()).thenReturn(new AttributeSettings()); + } + + @Test + void shouldRegisterViewWhenQuantilesAggregation() { + String metricName = "my/metric"; + String viewName = "my/view"; + String unit = "ms"; + String desc = "description"; + Duration timeWindow = Duration.ofSeconds(1); + int bufferLimit = 1000; + ViewDefinitionSettings settings = new ViewDefinitionSettings(); + settings.setDescription(desc); + settings.setTimeWindow(timeWindow); + settings.setMaxBufferedPoints(bufferLimit); + settings.setAggregation(AggregationType.QUANTILES); + settings.setAttributes(Collections.emptyMap()); + + viewManager.registerView(metricName, viewName, unit, settings); + Collection views = viewManager.getViews(metricName); + + assertThat(views).allMatch(view -> view.getViewName().equals(viewName)); + assertThat(views).allMatch(view -> view.getUnit().equals(unit)); + assertThat(views).allMatch(view -> view.getDescription().equals(desc)); + assertThat(views).allMatch(view -> view.getTimeWindow().equals(timeWindow)); + assertThat(views).allMatch(view -> view.getBufferLimit() == bufferLimit); + } + + @Test + void shouldRegisterViewWhenSmoothedAverageAggregation() { + String metricName = "my/metric"; + String viewName = "my/view"; + String unit = "ms"; + String desc = "description"; + Duration timeWindow = Duration.ofSeconds(1); + int bufferLimit = 1000; + ViewDefinitionSettings settings = new ViewDefinitionSettings(); + settings.setDescription(desc); + settings.setTimeWindow(timeWindow); + settings.setMaxBufferedPoints(bufferLimit); + settings.setAggregation(AggregationType.SMOOTHED_AVERAGE); + settings.setAttributes(Collections.emptyMap()); + + viewManager.registerView(metricName, viewName, unit, settings); + Collection views = viewManager.getViews(metricName); + + assertThat(views).allMatch(view -> view.getViewName().equals(viewName)); + assertThat(views).allMatch(view -> view.getUnit().equals(unit)); + assertThat(views).allMatch(view -> view.getDescription().equals(desc)); + assertThat(views).allMatch(view -> view.getTimeWindow().equals(timeWindow)); + assertThat(views).allMatch(view -> view.getBufferLimit() == bufferLimit); + } + + @Test + void shouldNotRegisterViewWhenOpenTelemetryAggregation() { + String metricName = "my/metric"; + String viewName = "my/view"; + String unit = "ms"; + ViewDefinitionSettings settings = new ViewDefinitionSettings(); + settings.setAggregation(AggregationType.HISTOGRAM); + + assertThrows(IllegalArgumentException.class, () -> viewManager.registerView(metricName, viewName, unit, settings)); + } +} diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/utils/TimeWindowTestUtils.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/utils/TimeWindowTestUtils.java new file mode 100644 index 0000000..fb37793 --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/utils/TimeWindowTestUtils.java @@ -0,0 +1,76 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.utils; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.metrics.data.DoublePointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import org.springframework.util.CollectionUtils; + +import java.util.Collection; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TimeWindowTestUtils { + + /** + * Checks, if the collections of metrics contains the expected series count + * + * @param metrics the collection of metrics + * @param expectedSeriesCount the expected series count + */ + public static void assertTotalSeriesCount(Collection metrics, long expectedSeriesCount) { + long count = metrics + .stream() + .mapToLong(metric -> metric.getData().getPoints().size()) + .sum(); + assertThat(count).isEqualTo(expectedSeriesCount); + } + + /** + * Checks, if the collection of metrics contains a metric with the expected time series data (= value + attributes). + * + * @param metrics the collection of metrics + * @param name the metric name to check + * @param expectedValue the expected value for the time series + * @param expectedAttributes the expected attributes for the time series + */ + public static void assertContainsData(Collection metrics, String name, double expectedValue, Map expectedAttributes) { + assertThat(metrics) + .anySatisfy(m -> assertThat(m.getName()).isEqualTo(name)); + MetricData metric = metrics.stream() + .filter(m -> m.getName().equals(name)) + .findFirst() + .get(); + + assertThat(metric.getDoubleGaugeData().getPoints()) + .anyMatch(point -> containsExpectedTimeSeries(point, expectedValue, expectedAttributes)); + } + + /** + * Checks, if the expected values exists for the requested time series (== attributes) + * + * @param pointData the data point of the metric + * @param expectedValue the expected value + * @param expectedAttributes the expected attributes + * + * @return true, if the data point contains all expected attributes and value + */ + private static boolean containsExpectedTimeSeries(DoublePointData pointData, double expectedValue, Map expectedAttributes) { + if (!(expectedValue == pointData.getValue())) return false; + + if (CollectionUtils.isEmpty(expectedAttributes)) { + return pointData.getAttributes().isEmpty(); + } else { + for (Map.Entry keyValuePair : expectedAttributes.entrySet()) { + String key = keyValuePair.getKey(); + String expectedAttributeValue = keyValuePair.getValue(); + String value = pointData.getAttributes() + .get(AttributeKey.stringKey(key)); + + boolean containsAttribute = expectedAttributeValue.equals(value); + if(!containsAttribute) return false; + } + return true; + } + } +} diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/QuantilesViewTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/QuantilesViewTest.java new file mode 100644 index 0000000..b22fcb1 --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/QuantilesViewTest.java @@ -0,0 +1,225 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.views; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.MetricDataType; +import io.opentelemetry.sdk.resources.Resource; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static rocks.inspectit.ocelot.eum.server.metrics.timewindow.utils.TimeWindowTestUtils.assertContainsData; + +class QuantilesViewTest { + + final String name = "name"; + + final String desc = "description"; + + final String unit = "unit"; + + @Nested + class Constructor { + + @Test + void noQuantilesAndMinMaxSpecified() { + assertThatThrownBy(() -> new QuantilesView(name, desc, unit, Collections.emptySet(), + Duration.ofSeconds(1), 1000, Collections.emptySet(), false, false)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void invalidQuantile() { + assertThatThrownBy(() -> new QuantilesView(name, desc, unit, Collections.emptySet(), + Duration.ofSeconds(1), 1000, Set.of(1.0), false, false)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void blankName() { + assertThatThrownBy(() -> new QuantilesView(" ", desc, unit, Collections.emptySet(), + Duration.ofSeconds(1), 1000, Collections.emptySet(), false, false)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void blankDescription() { + assertThatThrownBy(() -> new QuantilesView(name, " ", unit, Collections.emptySet(), + Duration.ofSeconds(1), 1000, Collections.emptySet(), false, false)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void blankUnit() { + assertThatThrownBy(() -> new QuantilesView(name, desc, " ", Collections.emptySet(), + Duration.ofSeconds(1), 1000, Collections.emptySet(), false, false)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void invalidTimeWindow() { + assertThatThrownBy(() -> new QuantilesView(name, desc, unit, Collections.emptySet(), + Duration.ZERO, 1000, Collections.emptySet(), false, false)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void invalidBufferSize() { + assertThatThrownBy(() -> new QuantilesView(name, desc, " ", Collections.emptySet(), + Duration.ofSeconds(1), 0, Collections.emptySet(), false, false)) + .isInstanceOf(IllegalArgumentException.class); + } + + } + + @Nested + class GetQuantileAttribute { + + @Test + void unnecessaryZeroesOmitted() { + String tag = QuantilesView.getQuantileAttribute(0.50); + assertThat(tag).isEqualTo("0.5"); + } + + @Test + void tooLongValueRoundedDown() { + String tag = QuantilesView.getQuantileAttribute(1.0 / 3); + assertThat(tag).isEqualTo("0.33333"); + } + + @Test + void tooLongValueRoundedUp() { + String tag = QuantilesView.getQuantileAttribute(1.0 / 3 * 2); + assertThat(tag).isEqualTo("0.66667"); + } + } + + @Nested + class ComputeMetrics { + + @Test + void checkQuantileMetricData() { + TimeWindowView view = new QuantilesView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 1, Set.of(0.5), false, false); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertThat(result).hasSize(1); + Optional maybeMetric = result.stream().findFirst(); + + assertThat(maybeMetric).isNotEmpty(); + MetricData metric = maybeMetric.get(); + + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(desc); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.getType()).isEqualTo(MetricDataType.DOUBLE_GAUGE); + } + + @Test + void checkMinMetricData() { + TimeWindowView view = new QuantilesView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 1, Collections.emptySet(), false, true); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertThat(result).hasSize(1); + Optional maybeMetric = result.stream().findFirst(); + + assertThat(maybeMetric).isNotEmpty(); + MetricData metric = maybeMetric.get(); + + assertThat(metric.getName()).isEqualTo(name + "_min"); + assertThat(metric.getDescription()).isEqualTo(desc); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.getType()).isEqualTo(MetricDataType.DOUBLE_GAUGE); + } + + @Test + void checkMaxMetricData() { + TimeWindowView view = new QuantilesView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 1, Collections.emptySet(), true, false); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertThat(result).hasSize(1); + Optional maybeMetric = result.stream().findFirst(); + + assertThat(maybeMetric).isNotEmpty(); + MetricData metric = maybeMetric.get(); + + assertThat(metric.getName()).isEqualTo(name + "_max"); + assertThat(metric.getDescription()).isEqualTo(desc); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.getType()).isEqualTo(MetricDataType.DOUBLE_GAUGE); + } + + @Test + void checkMinimumMetric() { + TimeWindowView view = new QuantilesView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 4, Collections.emptySet(), false, true); + + insertValues(view); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name + "_min", 42, Map.of("my-tag", "foo")); + assertContainsData(result, name + "_min", 100, Map.of("my-tag", "bar")); + } + + @Test + void checkMaximumMetric() { + TimeWindowView view = new QuantilesView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 4, Collections.emptySet(), true, false); + + insertValues(view); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name + "_max", 99, Map.of("my-tag", "foo")); + assertContainsData(result, name + "_max", 101, Map.of("my-tag", "bar")); + } + + @Test + void checkQuantileMetrics() { + TimeWindowView view = new QuantilesView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 18, Set.of(0.5, 0.9), false, false); + + Baggage baggage1 = Baggage.builder().put("my-tag", "foo").build(); + Baggage baggage2 = Baggage.builder().put("my-tag", "bar").build(); + + for (int i = 1; i < 10; i++) { + Instant timestamp = Instant.now(); + view.insertValue(10 + i, timestamp, baggage1); + view.insertValue(100 + i, timestamp.plus(2, ChronoUnit.MILLIS), baggage2); + } + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name, 19, Map.of("my-tag", "foo", "quantile", "0.9")); + assertContainsData(result, name, 109, Map.of("my-tag", "bar", "quantile", "0.9")); + assertContainsData(result, name, 15, Map.of("my-tag", "foo", "quantile", "0.5")); + assertContainsData(result, name, 105, Map.of("my-tag", "bar", "quantile", "0.5")); + } + } + + /** + * Helper method to insert values into the view + */ + static void insertValues(TimeWindowView view) { + Baggage baggage1 = Baggage.builder().put("my-tag", "foo").build(); + Baggage baggage2 = Baggage.builder().put("my-tag", "bar").build(); + Instant timestamp = Instant.now(); + + view.insertValue(42, timestamp, baggage1); + view.insertValue(99, timestamp.plus(1, ChronoUnit.MILLIS), baggage1); + view.insertValue(101, timestamp.plus(2, ChronoUnit.MILLIS), baggage2); + view.insertValue(100, timestamp.plus(3, ChronoUnit.MILLIS), baggage2); + } +} diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/SmoothedAverageViewTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/SmoothedAverageViewTest.java new file mode 100644 index 0000000..4839d01 --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/SmoothedAverageViewTest.java @@ -0,0 +1,212 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.views; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.data.MetricDataType; +import io.opentelemetry.sdk.resources.Resource; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static rocks.inspectit.ocelot.eum.server.metrics.timewindow.utils.TimeWindowTestUtils.assertContainsData; + +class SmoothedAverageViewTest { + + @Nested + class Constructor { + + @Test + void invalidDropUpper() { + assertThatThrownBy(() -> new SmoothedAverageView("name", "description", "unit", + Collections.emptySet(), Duration.ofSeconds(1), 1000, -1.0, 0.0)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void invalidDropLower() { + assertThatThrownBy(() -> new SmoothedAverageView("name", "description", "unit", + Collections.emptySet(), Duration.ofSeconds(1), 1000, 0.05, 1.01)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + class ComputeMetrics { + + final String name = "name"; + + final String desc = "description"; + + final String unit = "unit"; + + @Test + void checkSmoothedAverageMetricData() { + TimeWindowView view = new SmoothedAverageView(name, desc, unit, Collections.emptySet(), + Duration.ofMillis(10), 1, 0.0, 0.05); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertThat(result).hasSize(1); + Optional maybeMetric = result.stream().findFirst(); + + assertThat(maybeMetric).isNotEmpty(); + MetricData metric = maybeMetric.get(); + + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(desc); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.getType()).isEqualTo(MetricDataType.DOUBLE_GAUGE); + } + + @Test + void checkSmoothedAveragePointData() { + TimeWindowView view = new SmoothedAverageView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 12, 0.05, 0.05); + + Baggage baggage1 = Baggage.builder().put("my-tag", "foo").build(); + Baggage baggage2 = Baggage.builder().put("my-tag", "bar").build(); + Instant timestamp = Instant.now(); + + view.insertValue(42, timestamp, baggage1); + view.insertValue(99, timestamp.plus(1, ChronoUnit.MILLIS), baggage1); + view.insertValue(101, timestamp.plus(1, ChronoUnit.MILLIS), baggage1); + view.insertValue(50, timestamp.plus(1, ChronoUnit.MILLIS), baggage1); + view.insertValue(68, timestamp.plus(1, ChronoUnit.MILLIS), baggage1); + view.insertValue(70, timestamp.plus(2, ChronoUnit.MILLIS), baggage1); + + view.insertValue(101, timestamp.plus(2, ChronoUnit.MILLIS), baggage2); + view.insertValue(150, timestamp.plus(3, ChronoUnit.MILLIS), baggage2); + view.insertValue(171, timestamp.plus(3, ChronoUnit.MILLIS), baggage2); + view.insertValue(250, timestamp.plus(4, ChronoUnit.MILLIS), baggage2); + view.insertValue(99, timestamp.plus(4, ChronoUnit.MILLIS), baggage2); + view.insertValue(101, timestamp.plus(5, ChronoUnit.MILLIS), baggage2); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name, 71.75, Map.of("my-tag", "foo")); + assertContainsData(result, name, 130.75, Map.of("my-tag", "bar")); + } + + @Test + void checkSmoothedAverageMetricGreatIndex() { + TimeWindowView view = new SmoothedAverageView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 24, 0.2, 0.2); + + insertValues(view); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name, 97.14285714285714, Map.of("my-tag", "foo")); + } + + @Test + void checkSmoothedAverageMetricDropOnlyLower() { + TimeWindowView view = new SmoothedAverageView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 24, 0.0, 0.2); + + insertValues(view); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name, 123.78947368421052, Map.of("my-tag", "foo")); + } + + @Test + void checkSmoothedAverageMetricDropOnlyUpper() { + TimeWindowView view = new SmoothedAverageView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 24, 0.11, 0.0); + + insertValues(view); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name, 92.04761904761905, Map.of("my-tag", "foo")); + } + + @Test + void checkSmoothedAverageMetricDropGreaterUpperIndex() { + TimeWindowView view = new SmoothedAverageView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 24, 0.9, 0.2); + + insertValues(view); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name, 68.0, Map.of("my-tag", "foo")); + } + + @Test + void checkSmoothedAverageMetricDropNothing() { + TimeWindowView view = new SmoothedAverageView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 4, 0.0, 0.0); + + Baggage baggage = Baggage.builder().put("my-tag", "foo").build(); + Instant timestamp = Instant.now(); + + view.insertValue(80, timestamp, baggage); + view.insertValue(87, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(100, timestamp.plus(2, ChronoUnit.MILLIS), baggage); + view.insertValue(150, timestamp.plus(2, ChronoUnit.MILLIS), baggage); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name, 104.25, Map.of("my-tag", "foo")); + } + + @Test + void checkSmoothedAverageMetricDropSmallValues() { + TimeWindowView view = new SmoothedAverageView(name, desc, unit, Set.of("my-tag"), + Duration.ofMillis(10), 4, 0.1, 0.0); + + Baggage baggage = Baggage.builder().put("my-tag", "foo").build(); + Instant timestamp = Instant.now(); + + view.insertValue(116, timestamp, baggage); + view.insertValue(125, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + + Collection result = view.computeMetrics(Instant.now(), Resource.empty()); + + assertContainsData(result, name, 116, Map.of("my-tag", "foo")); + } + + + /** + * Helper method to insert values into the view + */ + private static void insertValues(TimeWindowView view) { + Baggage baggage = Baggage.builder().put("my-tag", "foo").build(); + Instant timestamp = Instant.now(); + + view.insertValue(42, timestamp, baggage); + view.insertValue(99, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(101, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(50, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(68,timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(70, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(42, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(99, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(101, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(50, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(68, timestamp.plus(1, ChronoUnit.MILLIS), baggage); + view.insertValue(70, timestamp.plus(2, ChronoUnit.MILLIS), baggage); + view.insertValue(101, timestamp.plus(2, ChronoUnit.MILLIS), baggage); + view.insertValue(150, timestamp.plus(3, ChronoUnit.MILLIS), baggage); + view.insertValue(171, timestamp.plus(3, ChronoUnit.MILLIS), baggage); + view.insertValue(250, timestamp.plus(4, ChronoUnit.MILLIS), baggage); + view.insertValue(99, timestamp.plus(4, ChronoUnit.MILLIS), baggage); + view.insertValue(101, timestamp.plus(5, ChronoUnit.MILLIS), baggage); + view.insertValue(101, timestamp.plus(5, ChronoUnit.MILLIS), baggage); + view.insertValue(150, timestamp.plus(5, ChronoUnit.MILLIS), baggage); + view.insertValue(171, timestamp.plus(6, ChronoUnit.MILLIS), baggage); + view.insertValue(250, timestamp.plus(6, ChronoUnit.MILLIS), baggage); + view.insertValue(99, timestamp.plus(6, ChronoUnit.MILLIS), baggage); + view.insertValue(101, timestamp.plus(7, ChronoUnit.MILLIS), baggage); + } + } +} diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/WindowedDoubleQueueTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/WindowedDoubleQueueTest.java new file mode 100644 index 0000000..464a076 --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/views/WindowedDoubleQueueTest.java @@ -0,0 +1,237 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.views; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.stream.IntStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class WindowedDoubleQueueTest { + + @Nested + class RoundUpToPowerOfTwo { + + @Test + public void nonPowerOfTwoValues() { + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(0)).isEqualTo(0); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(3)).isEqualTo(4); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(5)).isEqualTo(8); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(7)).isEqualTo(8); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(22)).isEqualTo(32); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(5000)).isEqualTo(8192); + } + + @Test + public void powerOfTwoValues() { + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(2)).isEqualTo(2); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(4)).isEqualTo(4); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(16)).isEqualTo(16); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(256)).isEqualTo(256); + assertThat(WindowedDoubleQueue.roundUpToPowerOfTwo(8192)).isEqualTo(8192); + } + + } + + @Nested + public class Insert { + + @Test + void testAlignedGrowth() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(1)); + + for (int i = 0; i <= WindowedDoubleQueue.MIN_CAPACITY; i++) { + queue.insert(i * 100 + 1, 42); + + } + + double[] expectedResult = IntStream.range(0, WindowedDoubleQueue.MIN_CAPACITY + 1) + .mapToDouble(i -> i * 100 + 1) + .toArray(); + double[] result = queue.copy(); + assertThat(result).isEqualTo(expectedResult); + + } + + @Test + void testUnalignedGrowth() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(1)); + + queue.insert(-12345, 0); + queue.insert(-12345, 0); + queue.insert(-12345, 0); + for (int i = 0; i <= WindowedDoubleQueue.MIN_CAPACITY; i++) { + queue.removeStaleValues(1); + queue.insert(i * 100 + 1, 1); + + } + + double[] expectedResult = IntStream.range(0, WindowedDoubleQueue.MIN_CAPACITY + 1) + .mapToDouble(i -> i * 100 + 1) + .toArray(); + double[] result = queue.copy(); + assertThat(result).isEqualTo(expectedResult); + + } + + @Test + void verifyStreamingMaintainsCapacity() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(WindowedDoubleQueue.MIN_CAPACITY)); + + for (int i = 0; i < WindowedDoubleQueue.MIN_CAPACITY * 10; i++) { + queue.removeStaleValues(i); + queue.insert(i, i); + } + + assertThat(queue.capacity()).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY); + assertThat(queue.size()).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY); + } + + @Test + void invalidTimestamp() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(42)); + + queue.insert(1.0, 10); + assertThatThrownBy(() -> queue.insert(2.0, 9)).isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + class RemoveStaleValues { + + @Test + void removeAllValues() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(1)); + + for (int i = 0; i < WindowedDoubleQueue.MIN_CAPACITY * 100; i++) { + queue.insert(i, 0); + } + int removed = queue.removeStaleValues(1); + + assertThat(removed).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY * 100); + assertThat(queue.capacity()).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY); + assertThat(queue.size()).isEqualTo(0); + } + + @Test + void removeAllExceptOneValues() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(2)); + + for (int i = 0; i < WindowedDoubleQueue.MIN_CAPACITY * 100; i++) { + queue.insert(i, 0); + } + queue.insert(42, 1); + int removed = queue.removeStaleValues(2); + + assertThat(removed).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY * 100); + assertThat(queue.capacity()).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY); + assertThat(queue.copy()).contains(42); + } + + @Test + void removeAllExceptFew() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(2)); + + int keepCount = WindowedDoubleQueue.MIN_CAPACITY + 1; + for (int i = 0; i < WindowedDoubleQueue.MIN_CAPACITY * 100; i++) { + queue.insert(-9999999, 0); + } + for (int i = 0; i < keepCount; i++) { + queue.insert(42 + i, 1); + } + int removed = queue.removeStaleValues(2); + + double[] expectedResult = IntStream.range(0, keepCount).mapToDouble(i -> 42 + i).toArray(); + assertThat(removed).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY * 100); + assertThat(queue.capacity()).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY * 4); + assertThat(queue.copy()).isEqualTo(expectedResult); + } + + @Test + void removeAllExceptFewWithOverflow() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(WindowedDoubleQueue.MIN_CAPACITY * 2)); + + int keepCount = WindowedDoubleQueue.MIN_CAPACITY + 1; + int time = 0; + + for (int i = 0; i < WindowedDoubleQueue.MIN_CAPACITY * 3; i++) { + queue.removeStaleValues(time); + queue.insert(-9999999, time); + time++; + } + for (int i = 0; i < keepCount; i++) { + queue.removeStaleValues(time + 1); + queue.insert(42 + i, time + 1); + } + int removed = queue.removeStaleValues(time + WindowedDoubleQueue.MIN_CAPACITY * 2); + + assertThat(removed).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY * 3 - keepCount - 1); + double[] expectedResult = IntStream.range(0, keepCount).mapToDouble(i -> 42 + i).toArray(); + assertThat(queue.capacity()).isEqualTo(WindowedDoubleQueue.MIN_CAPACITY * 4); + assertThat(queue.copy()).isEqualTo(expectedResult); + } + + } + + @Nested + class Copy { + + @Test + void copyEmptyIntoNewBuffer() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(1)); + + double[] copy = queue.copy(); + + assertThat(copy).isEmpty(); + } + + @Test + void copyEmptyIntoExistingBuffer() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(1)); + double[] buffer = new double[42]; + + queue.copy(buffer); + + assertThat(buffer).containsOnly(0); + } + + @Test + void copyValuesIntoNewBuffer() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(1)); + for (int i = 0; i < 100; i++) { + queue.insert(i, 0); + } + + double[] result = queue.copy(); + + double[] expectedResult = IntStream.range(0, 100).mapToDouble(i -> i).toArray(); + assertThat(result).isEqualTo(expectedResult); + } + + @Test + void copyValuesIntoExistingBuffer() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(1)); + double[] buffer = new double[100]; + for (int i = 0; i < 100; i++) { + queue.insert(i, 0); + } + + queue.copy(buffer); + + double[] expectedResult = IntStream.range(0, 100).mapToDouble(i -> i).toArray(); + assertThat(buffer).isEqualTo(expectedResult); + } + + @Test + void copyValuesIntoTooSmallBuffer() { + WindowedDoubleQueue queue = new WindowedDoubleQueue(Duration.ofMillis(1)); + for (int i = 0; i < 100; i++) { + queue.insert(i, 0); + } + + assertThatThrownBy(() -> queue.copy(new double[99])).isInstanceOf(IllegalArgumentException.class); + } + } +} diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowMetricProducerTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowMetricProducerTest.java new file mode 100644 index 0000000..47bc5a8 --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowMetricProducerTest.java @@ -0,0 +1,215 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.worker; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.resources.Resource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.TimeWindowViewManager; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.QuantilesView; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.SmoothedAverageView; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.TimeWindowView; + +import java.time.Duration; +import java.util.*; + +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; +import static rocks.inspectit.ocelot.eum.server.metrics.timewindow.utils.TimeWindowTestUtils.assertContainsData; +import static rocks.inspectit.ocelot.eum.server.metrics.timewindow.utils.TimeWindowTestUtils.assertTotalSeriesCount; + +@ExtendWith(MockitoExtension.class) +class TimeWindowMetricProducerTest { + + @Mock + TimeWindowViewManager viewManager; + + @InjectMocks + TimeWindowMetricProducer producer; + + @InjectMocks + TimeWindowRecorder recorder; + + static final String metricName = "test"; + + static final String quantileViewName = "test/quantiles"; + + static final String smoothedAvgViewName = "test/smoothed/average"; + + @BeforeEach + void createViews() { + TimeWindowView quantilesView = createQuantilesView(1000); + TimeWindowView smoothedAvgView = createSmoothedAverageView(1000); + List views = List.of(quantilesView, smoothedAvgView); + lenient().when(viewManager.areAnyViewsRegistered(metricName)).thenReturn(true); + lenient().when(viewManager.getAllViews()).thenReturn(views); + lenient().when(viewManager.getViews(metricName)).thenReturn(views); + } + + @Test + void shouldReturnEmptySeriesWhenNoDataRecorded() { + Collection result = producer.produce(Resource.empty()); + + assertThat(result).hasSize(4); + assertTotalSeriesCount(result, 0); + } + + @Test + void shouldReturnSeriesWhenDataRecorded() { + for (int i = 1; i < 100; i++) { + recorder.recordMetric(metricName, i, Baggage.empty()); + } + awaitMetricsProcessing(); + + Collection result = producer.produce(Resource.empty()); + + assertThat(result).hasSize(4); + assertTotalSeriesCount(result, 5); + assertContainsData(result, quantileViewName, 50, Map.of("quantile", "0.5")); + assertContainsData(result, quantileViewName, 95, Map.of("quantile", "0.95")); + assertContainsData(result, quantileViewName + "_max", 99, emptyMap()); + assertContainsData(result, quantileViewName + "_min", 1, emptyMap()); + assertContainsData(result, smoothedAvgViewName, 50, emptyMap()); + } + + @Test + void shouldReturnMultipleSeriesWhenDataRecorded() { + Baggage baggage = Baggage.builder() + .put("key1", "foo") + .put("key2", "bar") + .build(); + + for (int i = 1; i < 100; i++) { + recorder.recordMetric(metricName, i, Baggage.empty()); + recorder.recordMetric(metricName, 1000+i, baggage); + } + awaitMetricsProcessing(); + + Collection result = producer.produce(Resource.empty()); + + assertThat(result).hasSize(4); + assertTotalSeriesCount(result, 10); + + assertContainsData(result, quantileViewName, 50, Map.of("quantile", "0.5")); + assertContainsData(result, quantileViewName, 95, Map.of("quantile", "0.95")); + assertContainsData(result, quantileViewName, 1050, Map.of("key1", "foo", "key2", "bar", "quantile", "0.5")); + assertContainsData(result, quantileViewName, 1095, Map.of("key1", "foo", "key2", "bar", "quantile", "0.95")); + assertContainsData(result, quantileViewName + "_min", 1, emptyMap()); + assertContainsData(result, quantileViewName + "_min", 1001, Map.of("key1", "foo", "key2", "bar")); + assertContainsData(result, quantileViewName + "_max", 99, emptyMap()); + assertContainsData(result, quantileViewName + "_max", 1099, Map.of("key1", "foo", "key2", "bar")); + assertContainsData(result, smoothedAvgViewName, 50, emptyMap()); + assertContainsData(result, smoothedAvgViewName, 1050, Map.of("key1", "foo", "key2", "bar")); + } + + + @Test + void shouldReturnEmptySeriesWhenStaleData() throws Exception{ + for (int i = 1; i < 100; i++) { + recorder.recordMetric(metricName, i, Baggage.empty()); + } + awaitMetricsProcessing(); + Thread.sleep(300); // wait longer than timeWindow + + Collection result = producer.produce(Resource.empty()); + + assertThat(result).hasSize(4); + assertTotalSeriesCount(result, 0); + } + + @Test + void testDroppingBecauseBufferIsFull() { + TimeWindowView quantilesView = createQuantilesView(10); + TimeWindowView smoothedAvgView = createSmoothedAverageView(10); + List views = List.of(quantilesView, smoothedAvgView); + when(viewManager.getAllViews()).thenReturn(views); + when(viewManager.getViews(metricName)).thenReturn(views); + + Baggage baggage = Baggage.builder().put("key1", "foo").build(); + + for (int i = 0; i < 20; i++) { + recorder.recordMetric(metricName, 20-i, baggage); + } + awaitMetricsProcessing(); + + Collection result = producer.produce(Resource.empty()); + + assertThat(result).hasSize(4); + assertTotalSeriesCount(result, 5); + assertContainsData(result, quantileViewName + "_min", 11.0, Map.of("key1", "foo")); + assertContainsData(result, smoothedAvgViewName, 15.5, Map.of("key1", "foo")); + } + + @Test + void testDroppingPreventedThroughCleanupTask() throws Exception { + TimeWindowView quantilesView = createQuantilesView(10); + TimeWindowView smoothedAvgView = createSmoothedAverageView(10); + List views = List.of(quantilesView, smoothedAvgView); + when(viewManager.getAllViews()).thenReturn(views); + when(viewManager.getViews(metricName)).thenReturn(views); + + Baggage baggage = Baggage.builder().put("key1", "foo").build(); + + // fill buffer + for (int i = 0; i < 10; i++) { + recorder.recordMetric(metricName, i, baggage); + } + awaitMetricsProcessing(); + Thread.sleep(300); // wait longer than timeWindow + + // clean-up task should clean the full buffer, so we can record new values again + recorder.recordMetric(metricName, 1000, baggage); + awaitMetricsProcessing(); + + Collection result = producer.produce(Resource.empty()); + + assertThat(result).hasSize(4); + assertTotalSeriesCount(result, 5); + assertContainsData(result, quantileViewName + "_min", 1000, Map.of("key1", "foo")); + assertContainsData(result, smoothedAvgViewName, 1000, Map.of("key1", "foo")); + } + + @Test + void shouldCacheResultMetricsWithinCacheDuration() { + Collection result1 = producer.produce(Resource.empty()); + + Collection result2 = producer.produce(Resource.empty()); + + assertThat(result1).isSameAs(result2); + } + + @Test + void shouldCreateNewResultMetricsWhenCacheDurationExceeded() throws Exception { + Collection result1 = producer.produce(Resource.empty()); + + Thread.sleep(1100); // wait longer than cache duration + + Collection result2 = producer.produce(Resource.empty()); + + assertThat(result1).isNotSameAs(result2); + } + + static TimeWindowView createQuantilesView(int bufferLimit) { + return new QuantilesView(quantileViewName, "desc", "ms", Set.of("key1", "key2"), + Duration.ofMillis(200), bufferLimit, Set.of(0.5, 0.95), true, true); + } + + static TimeWindowView createSmoothedAverageView(int bufferLimit) { + return new SmoothedAverageView(smoothedAvgViewName, "desc", "ms", + Set.of("key1", "key2"), Duration.ofMillis(200), bufferLimit, 0.2, 0.2); + } + + /** + * Processes all metrics records until the queue is empty + */ + void awaitMetricsProcessing() { + do { + recorder.record(); + } while(!recorder.recordsQueue.isEmpty()); + } +} diff --git a/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowRecorderTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowRecorderTest.java new file mode 100644 index 0000000..a58b596 --- /dev/null +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/metrics/timewindow/worker/TimeWindowRecorderTest.java @@ -0,0 +1,45 @@ +package rocks.inspectit.ocelot.eum.server.metrics.timewindow.worker; + +import io.opentelemetry.api.baggage.Baggage; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.TimeWindowViewManager; +import rocks.inspectit.ocelot.eum.server.metrics.timewindow.views.TimeWindowView; + +import java.time.Instant; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TimeWindowRecorderTest { + + @InjectMocks + TimeWindowRecorder recorder; + + @Mock + TimeWindowViewManager viewManager; + + @Mock + TimeWindowView view; + + String metricName = "my/metric"; + + @Test + void shouldRecordValueForView() { + when(viewManager.areAnyViewsRegistered(metricName)).thenReturn(true); + when(viewManager.getViews(metricName)).thenReturn(Collections.singletonList(view)); + Baggage baggage = Baggage.current(); + + recorder.recordMetric(metricName, 42.0, baggage); + recorder.record(); + + verify(view).insertValue(eq(42.0), any(Instant.class), eq(baggage)); + } +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/rest/BeaconControllerTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/rest/BeaconControllerTest.java similarity index 82% rename from src/test/java/rocks/inspectit/oce/eum/server/rest/BeaconControllerTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/rest/BeaconControllerTest.java index 53722df..b762774 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/rest/BeaconControllerTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/rest/BeaconControllerTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.rest; +package rocks.inspectit.ocelot.eum.server.rest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -11,16 +11,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.beacon.processor.CompositeBeaconProcessor; -import rocks.inspectit.oce.eum.server.exporters.beacon.BeaconHttpExporter; -import rocks.inspectit.oce.eum.server.metrics.BeaconMetricManager; -import rocks.inspectit.oce.eum.server.metrics.SelfMonitoringMetricManager; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.processor.CompositeBeaconProcessor; +import rocks.inspectit.ocelot.eum.server.exporters.beacon.BeaconHttpExporter; +import rocks.inspectit.ocelot.eum.server.metrics.BeaconMetricManager; +import rocks.inspectit.ocelot.eum.server.metrics.SelfMonitoringMetricManager; import java.util.Collections; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -28,25 +27,25 @@ class BeaconControllerTest { @InjectMocks - private BeaconController controller; + BeaconController controller; @Mock - private BeaconMetricManager beaconMetricManager; + BeaconMetricManager beaconMetricManager; @Mock - private CompositeBeaconProcessor beaconProcessor; + CompositeBeaconProcessor beaconProcessor; @Mock - private SelfMonitoringMetricManager selfMonitoringService; + SelfMonitoringMetricManager selfMonitoringService; @Mock - private BeaconHttpExporter beaconHttpExporter; + BeaconHttpExporter beaconHttpExporter; @Nested - public class BeaconPost { + class BeaconPost { @Test - public void successful() { + void successful() { when(beaconProcessor.process(any())).then(i -> i.getArguments()[0]); when(beaconMetricManager.processBeacon(any())).thenReturn(true); @@ -66,7 +65,7 @@ public void successful() { } @Test - public void notSuccessful() { + void notSuccessful() { when(beaconProcessor.process(any())).then(i -> i.getArguments()[0]); when(beaconMetricManager.processBeacon(any())).thenReturn(false); @@ -85,10 +84,10 @@ public void notSuccessful() { } @Nested - public class BeaconGet { + class BeaconGet { @Test - public void successful() { + void successful() { when(beaconProcessor.process(any())).then(i -> i.getArguments()[0]); when(beaconMetricManager.processBeacon(any())).thenReturn(true); @@ -108,7 +107,7 @@ public void successful() { } @Test - public void notSuccessful() { + void notSuccessful() { when(beaconProcessor.process(any())).then(i -> i.getArguments()[0]); when(beaconMetricManager.processBeacon(any())).thenReturn(false); @@ -123,6 +122,5 @@ public void notSuccessful() { assertThat(result).extracting(ResponseEntity::getStatusCode).isEqualTo(HttpStatus.OK); } - } -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/rest/TraceControllerIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/rest/TraceControllerIntTest.java similarity index 90% rename from src/test/java/rocks/inspectit/oce/eum/server/rest/TraceControllerIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/rest/TraceControllerIntTest.java index 37fa0dc..5e2df72 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/rest/TraceControllerIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/rest/TraceControllerIntTest.java @@ -1,10 +1,9 @@ -package rocks.inspectit.oce.eum.server.rest; +package rocks.inspectit.ocelot.eum.server.rest; import com.google.common.io.CharStreams; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -18,6 +17,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.test.context.junit.jupiter.SpringExtension; +import rocks.inspectit.ocelot.eum.server.exporters.tracing.DelegatingSpanExporter; import java.io.InputStreamReader; import java.io.Reader; @@ -28,39 +28,39 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ExtendWith(SpringExtension.class) -public class TraceControllerIntTest { +class TraceControllerIntTest { @Autowired TestRestTemplate restTemplate; @Value("classpath:ot-trace-large-v0.48.0.json") - private Resource resourceSpan; + Resource resourceSpan; @Value("classpath:ot-trace-prod-v0.48.0.json") - private Resource prodResourceSpans; + Resource prodResourceSpans; @MockBean - SpanExporter spanExporter; + DelegatingSpanExporter spanExporter; @Captor ArgumentCaptor> spanCaptor; @Test - public void empty() { + void empty() { ResponseEntity result = restTemplate.postForEntity("/spans", null, Void.class); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } @Test - public void badData() { + void badData() { ResponseEntity result = restTemplate.postForEntity("/spans", "{\"bad\": \"data'\"}", Void.class); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } @Test - public void verifyLargeTrace() throws Exception { + void verifyLargeTrace() throws Exception { try (Reader reader = new InputStreamReader(resourceSpan.getInputStream())) { String json = CharStreams.toString(reader); @@ -87,7 +87,7 @@ public void verifyLargeTrace() throws Exception { } @Test - public void verifyTraceWithMultipleResourceSpans() throws Exception { + void verifyTraceWithMultipleResourceSpans() throws Exception { try (Reader reader = new InputStreamReader(prodResourceSpans.getInputStream())) { String json = CharStreams.toString(reader); diff --git a/src/test/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderIntTest.java similarity index 92% rename from src/test/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderIntTest.java index 6a5dcf6..0a54ad0 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderIntTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.security.authprovider; +package rocks.inspectit.ocelot.eum.server.security.authprovider; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -20,10 +20,10 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ContextConfiguration(initializers = SimpleApiTokenAuthenticationProviderIntTest.Initializer.class) @DirtiesContext -public class SimpleApiTokenAuthenticationProviderIntTest { +class SimpleApiTokenAuthenticationProviderIntTest { @Autowired - private TestRestTemplate rest; + TestRestTemplate rest; static class Initializer implements ApplicationContextInitializer { @@ -36,7 +36,7 @@ public void initialize(ConfigurableApplicationContext applicationContext) { } @Nested - public class Authorized { + class Authorized { @Test void authorizedRequest() { @@ -52,7 +52,7 @@ void authorizedRequest() { } @Nested - public class Forbidden { + class Forbidden { @Test void forbiddenRequest() { @@ -66,6 +66,4 @@ void forbiddenRequest() { assertThat(result.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); } } - } - diff --git a/src/test/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderTest.java similarity index 83% rename from src/test/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderTest.java index 1f6b80b..711928a 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/security/authprovider/SimpleApiTokenAuthenticationProviderTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.security.authprovider; +package rocks.inspectit.ocelot.eum.server.security.authprovider; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -9,8 +9,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; -import rocks.inspectit.oce.eum.server.security.ApiTokenAuthentication; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.security.ApiTokenAuthentication; import java.io.File; import java.util.HashMap; @@ -23,15 +23,15 @@ class SimpleApiTokenAuthenticationProviderTest { @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private EumServerConfiguration configuration; + EumServerConfiguration configuration; @Mock - private ScheduledExecutorService executorService; + ScheduledExecutorService executorService; @InjectMocks - private SimpleApiTokenAuthenticationProvider authenticationProvider; + SimpleApiTokenAuthenticationProvider authenticationProvider; - private HashMap defaultTokens() { + private static HashMap defaultTokens() { HashMap tokens = new HashMap<>(); tokens.put("dummy1", new SimpleApiTokenAuthenticationProvider.TokenPrincipal("dummy1 app", "dummy1")); tokens.put("dummy2", new SimpleApiTokenAuthenticationProvider.TokenPrincipal("dummy2 app", "dummy2")); @@ -51,7 +51,7 @@ private void configurationReturnsDefaultTokensDirectory() { class Authentication { @Test - public void authenticationSuccess() { + void authenticationSuccess() { configurationReturnsDefaultTokensDirectory(); authenticationProvider.init(); @@ -64,7 +64,7 @@ public void authenticationSuccess() { } @Test - public void authenticationFailure() { + void authenticationFailure() { configurationReturnsDefaultTokensDirectory(); authenticationProvider.init(); @@ -81,7 +81,7 @@ public void authenticationFailure() { class LoadTokens { @Test - public void loadExistingTokens() { + void loadExistingTokens() { configurationReturnsDefaultTokensDirectory(); authenticationProvider.init(); @@ -91,7 +91,7 @@ public void loadExistingTokens() { } @Test - public void createTokenDirectoryAndCreateInitialToken(@TempDir File tempTokenDir) { + void createTokenDirectoryAndCreateInitialToken(@TempDir File tempTokenDir) { String tokenDir = tempTokenDir.getAbsolutePath() + File.separator + "tokens"; when(configuration.getSecurity().getAuthProvider().getSimple().getTokenDirectory()).thenReturn(tokenDir); @@ -107,8 +107,5 @@ public void createTokenDirectoryAndCreateInitialToken(@TempDir File tempTokenDir assertThat(new File(expectedFile).exists()).isEqualTo(true); } - } - } - diff --git a/src/test/java/rocks/inspectit/oce/eum/server/security/cors/CorsTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/security/cors/CorsTest.java similarity index 91% rename from src/test/java/rocks/inspectit/oce/eum/server/security/cors/CorsTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/security/cors/CorsTest.java index 051ba6b..1927931 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/security/cors/CorsTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/security/cors/CorsTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.security.cors; +package rocks.inspectit.ocelot.eum.server.security.cors; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -16,10 +16,10 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ContextConfiguration(initializers = CorsTest.Initializer.class) @DirtiesContext -public class CorsTest { +class CorsTest { @Autowired - private TestRestTemplate restTemplate; + TestRestTemplate restTemplate; static class Initializer implements ApplicationContextInitializer { @@ -32,7 +32,7 @@ public void initialize(ConfigurableApplicationContext applicationContext) { } @Test - public void successfulCorsForGetBeacons() { + void successfulCorsForGetBeacons() { String endpoint = "/beacon"; HttpHeaders headers = new HttpHeaders(); @@ -46,7 +46,7 @@ public void successfulCorsForGetBeacons() { } @Test - public void successfulCorsForPostBeacons() { + void successfulCorsForPostBeacons() { String endpoint = "/beacon"; HttpHeaders headers = new HttpHeaders(); @@ -60,7 +60,7 @@ public void successfulCorsForPostBeacons() { } @Test - public void successfulCorsForSpans() { + void successfulCorsForSpans() { String endpoint = "/spans"; HttpHeaders headers = new HttpHeaders(); diff --git a/src/test/java/rocks/inspectit/oce/eum/server/tracing/opentelemtry/OpenTelemetryProtoConverterTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/tracing/opentelemetry/OpenTelemetryProtoConverterTest.java similarity index 88% rename from src/test/java/rocks/inspectit/oce/eum/server/tracing/opentelemtry/OpenTelemetryProtoConverterTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/tracing/opentelemetry/OpenTelemetryProtoConverterTest.java index 821d043..3ce4f0e 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/tracing/opentelemtry/OpenTelemetryProtoConverterTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/tracing/opentelemetry/OpenTelemetryProtoConverterTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.tracing.opentelemtry; +package rocks.inspectit.ocelot.eum.server.tracing.opentelemetry; import com.google.protobuf.util.JsonFormat; import io.opentelemetry.api.trace.SpanKind; @@ -21,7 +21,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import rocks.inspectit.oce.eum.server.configuration.model.EumServerConfiguration; +import rocks.inspectit.ocelot.eum.server.configuration.model.EumServerConfiguration; import jakarta.servlet.http.HttpServletRequest; import java.io.InputStream; @@ -37,32 +37,32 @@ @ExtendWith(MockitoExtension.class) class OpenTelemetryProtoConverterTest { - public static final String TRACE_REQUEST_FILE_SMALL = "/ot-trace-small-v0.48.0.json"; + static final String TRACE_REQUEST_FILE_SMALL = "/ot-trace-small-v0.48.0.json"; - public static final String TRACE_REQUEST_FILE_LARGE = "/ot-trace-large-v0.48.0.json"; + static final String TRACE_REQUEST_FILE_LARGE = "/ot-trace-large-v0.48.0.json"; - public static final String TRACE_REQUEST_FILE_PROD = "/ot-trace-prod-v0.48.0.json"; + static final String TRACE_REQUEST_FILE_PROD = "/ot-trace-prod-v0.48.0.json"; @InjectMocks - private OpenTelemetryProtoConverter converter; + OpenTelemetryProtoConverter converter; @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private EumServerConfiguration configuration; + EumServerConfiguration configuration; - private ExportTraceServiceRequest getSmallTestRequest() throws Exception { + private static ExportTraceServiceRequest getSmallTestRequest() throws Exception { return getTestRequest(TRACE_REQUEST_FILE_SMALL); } - private ExportTraceServiceRequest getLargeTestRequest() throws Exception { + private static ExportTraceServiceRequest getLargeTestRequest() throws Exception { return getTestRequest(TRACE_REQUEST_FILE_LARGE); } - private ExportTraceServiceRequest getProdTestRequest() throws Exception { + private static ExportTraceServiceRequest getProdTestRequest() throws Exception { return getTestRequest(TRACE_REQUEST_FILE_PROD); } - private ExportTraceServiceRequest getTestRequest(String file) throws Exception { - InputStream resource = this.getClass().getResourceAsStream(file); + private static ExportTraceServiceRequest getTestRequest(String file) throws Exception { + InputStream resource = OpenTelemetryProtoConverterTest.class.getResourceAsStream(file); String traceRequestJson = IOUtils.toString(resource, StandardCharsets.UTF_8); ExportTraceServiceRequest.Builder requestBuilder = ExportTraceServiceRequest.newBuilder(); @@ -74,7 +74,7 @@ private ExportTraceServiceRequest getTestRequest(String file) throws Exception { class Convert { @Test - public void convertSmallRequest() throws Exception { + void convertSmallRequest() throws Exception { ExportTraceServiceRequest request = getSmallTestRequest(); Collection result = converter.convert(request); @@ -108,7 +108,7 @@ public void convertSmallRequest() throws Exception { } @Test - public void convertLargeRequest() throws Exception { + void convertLargeRequest() throws Exception { ExportTraceServiceRequest request = getLargeTestRequest(); Collection result = converter.convert(request); @@ -171,7 +171,7 @@ void convertArrayRequest() throws Exception { } @Test - public void emptyIgnored() { + void emptyIgnored() { ExportTraceServiceRequest data = ExportTraceServiceRequest.newBuilder() .addResourceSpans(ResourceSpans.newBuilder() .setResource(Resource.newBuilder().build()) @@ -193,15 +193,15 @@ public void emptyIgnored() { class AnonymizeIpAddress { @Mock - private HttpServletRequest mockRequest; + HttpServletRequest mockRequest; @BeforeEach - public void beforeEach() { + void beforeEach() { converter.requestSupplier = () -> mockRequest; } @Test - public void ipv4_singleDigit() { + void ipv4_singleDigit() { when(configuration.getExporters().getTracing().isMaskSpanIpAddresses()).thenReturn(true); when(mockRequest.getRemoteAddr()).thenReturn("127.1.1.1"); @@ -211,7 +211,7 @@ public void ipv4_singleDigit() { } @Test - public void ipv4_multipleDigits() { + void ipv4_multipleDigits() { when(configuration.getExporters().getTracing().isMaskSpanIpAddresses()).thenReturn(true); when(mockRequest.getRemoteAddr()).thenReturn("127.1.1.254"); @@ -221,7 +221,7 @@ public void ipv4_multipleDigits() { } @Test - public void ipv4_noMasking() { + void ipv4_noMasking() { when(configuration.getExporters().getTracing().isMaskSpanIpAddresses()).thenReturn(false); when(mockRequest.getRemoteAddr()).thenReturn("127.1.1.1"); @@ -231,7 +231,7 @@ public void ipv4_noMasking() { } @Test - public void ipv6_mask() { + void ipv6_mask() { when(configuration.getExporters().getTracing().isMaskSpanIpAddresses()).thenReturn(true); when(mockRequest.getRemoteAddr()).thenReturn("1:2:3:4:5:6:7:8"); @@ -241,7 +241,7 @@ public void ipv6_mask() { } @Test - public void ipv6_noMasking() { + void ipv6_noMasking() { when(configuration.getExporters().getTracing().isMaskSpanIpAddresses()).thenReturn(false); when(mockRequest.getRemoteAddr()).thenReturn("1:2:3:4:5:6:7:8"); diff --git a/src/test/java/rocks/inspectit/oce/eum/server/utils/DirectoryPollerTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/utils/DirectoryPollerTest.java similarity index 98% rename from src/test/java/rocks/inspectit/oce/eum/server/utils/DirectoryPollerTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/utils/DirectoryPollerTest.java index 0ea7902..611e4cf 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/utils/DirectoryPollerTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/utils/DirectoryPollerTest.java @@ -1,4 +1,4 @@ -package rocks.inspectit.oce.eum.server.utils; +package rocks.inspectit.ocelot.eum.server.utils; import org.apache.commons.io.monitor.FileAlterationListener; import org.junit.jupiter.api.Test; @@ -100,5 +100,4 @@ void testChangeCallbackInvoked(@TempDir File tempDir) throws Exception { verify(changeCallbackMock, times(1)).run(); } - -} \ No newline at end of file +} diff --git a/src/test/java/rocks/inspectit/oce/eum/server/utils/GeolocationResolverIntTest.java b/src/test/java/rocks/inspectit/ocelot/eum/server/utils/GeolocationResolverIntTest.java similarity index 72% rename from src/test/java/rocks/inspectit/oce/eum/server/utils/GeolocationResolverIntTest.java rename to src/test/java/rocks/inspectit/ocelot/eum/server/utils/GeolocationResolverIntTest.java index dbc19aa..5392375 100644 --- a/src/test/java/rocks/inspectit/oce/eum/server/utils/GeolocationResolverIntTest.java +++ b/src/test/java/rocks/inspectit/ocelot/eum/server/utils/GeolocationResolverIntTest.java @@ -1,22 +1,22 @@ -package rocks.inspectit.oce.eum.server.utils; +package rocks.inspectit.ocelot.eum.server.utils; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.message.BasicNameValuePair; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; -import rocks.inspectit.oce.eum.server.beacon.Beacon; -import rocks.inspectit.oce.eum.server.beacon.processor.CountryCodeBeaconProcessor; -import rocks.inspectit.oce.eum.server.metrics.BeaconMetricManager; +import rocks.inspectit.ocelot.eum.server.beacon.Beacon; +import rocks.inspectit.ocelot.eum.server.beacon.processor.CountryCodeBeaconProcessor; +import rocks.inspectit.ocelot.eum.server.metrics.BeaconMetricManager; import java.util.HashMap; import java.util.List; @@ -34,16 +34,16 @@ @SpringBootTest @AutoConfigureMockMvc @DirtiesContext -public class GeolocationResolverIntTest { +class GeolocationResolverIntTest { - private static String URL_KEY = "u"; - private static String SUT_URL = "http://test.com/login"; - private static String BEACON_KEY_NAME = "t_page"; + static final String URL_KEY = "u"; + static final String SUT_URL = "http://test.com/login"; + static final String BEACON_KEY_NAME = "t_page"; @Autowired - protected MockMvc mockMvc; + MockMvc mockMvc; - @MockBean + @SpyBean BeaconMetricManager beaconMetricManager; @Captor @@ -51,16 +51,13 @@ public class GeolocationResolverIntTest { /** * Sends beacon to mocked endpoint /beacon - * - * @param beacon - * @throws Exception */ private void sendBeacon(Map beacon, String requesterIP) throws Exception { List params = beacon.entrySet().stream().map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue())).collect(Collectors.toList()); mockMvc.perform(post("/beacon").header("X-Forwarded-For", requesterIP).contentType(MediaType.APPLICATION_FORM_URLENCODED).content(EntityUtils.toString(new UrlEncodedFormEntity(params)))).andExpect(status().isOk()); } - private Map getBasicBeacon() { + private static Map getBasicBeacon() { Map beacon = new HashMap<>(); beacon.put(URL_KEY, SUT_URL); return beacon; @@ -68,11 +65,9 @@ private Map getBasicBeacon() { /** * The application should process a beacon, where the tag COUNTRY_CODE is set to DE - * - * @throws Exception */ @Test - public void expectCountryCodeToBeSet() throws Exception { + void expectCountryCodeToBeSet() throws Exception { Map beacon = getBasicBeacon(); beacon.put(BEACON_KEY_NAME, "12"); @@ -84,11 +79,9 @@ public void expectCountryCodeToBeSet() throws Exception { /** * The application should process a beacon, where the tag COUNTRY_CODE is not set - * - * @throws Exception */ @Test - public void expectCountryCodeToBeNotSetNotResolvableIP() throws Exception { + void expectCountryCodeToBeNotSetNotResolvableIP() throws Exception { Map beacon = getBasicBeacon(); beacon.put(BEACON_KEY_NAME, "12"); @@ -100,11 +93,9 @@ public void expectCountryCodeToBeNotSetNotResolvableIP() throws Exception { /** * The application should process a beacon, where the tag COUNTRY_CODE is not set - * - * @throws Exception */ @Test - public void expectCountryCodeToBeNotSetWrongFormattedIP() throws Exception { + void expectCountryCodeToBeNotSetWrongFormattedIP() throws Exception { Map beacon = getBasicBeacon(); beacon.put(BEACON_KEY_NAME, "12"); @@ -113,5 +104,4 @@ public void expectCountryCodeToBeNotSetWrongFormattedIP() throws Exception { verify(beaconMetricManager).processBeacon(beaconCaptor.capture()); assertThat(beaconCaptor.getValue().get(CountryCodeBeaconProcessor.TAG_COUNTRY_CODE)).isEmpty(); } - } diff --git a/src/test/resources/otel-config.yaml b/src/test/resources/otel-config.yaml index 08a9175..dbb0609 100644 --- a/src/test/resources/otel-config.yaml +++ b/src/test/resources/otel-config.yaml @@ -21,33 +21,30 @@ receivers: static_configs: - targets: [$PROMETHEUS_INTEGRATION_TEST_SCRAPE_TARGET] - # accept InfluxDB - influxdb: - # accept Zipkin zipkin: exporters: - logging: - logLevel: $LOGGING_EXPORTER_LOG_lEVEL otlp: endpoint: $OTLP_EXPORTER_ENDPOINT tls: insecure: true compression: none + debug: +# verbosity: detailed service: extensions: [health_check] pipelines: metrics: - receivers: [otlp] #, prometheus, influxdb] - exporters: [logging, otlp] + receivers: [otlp] #, prometheus] + exporters: [debug, otlp] traces: receivers: [otlp, zipkin] - exporters: [logging, otlp] + exporters: [debug, otlp] logs: receivers: [otlp] - exporters: [logging, otlp] + exporters: [debug, otlp] telemetry: logs: level: "info"