From 5df103ab87bc4cd5b6dac461dc7392a6ef5a2e6c Mon Sep 17 00:00:00 2001 From: Victoria Xu Date: Mon, 22 Jun 2026 16:02:13 -0700 Subject: [PATCH 1/4] 1. add stability annotations to existing receiver metadata Required by mdatagen for nginxplusreceiver, nginxreceiver, and containermetricsreceiver metrics. No behaviour change. --- .../generated_component_test.go | 18 +- .../scraper/cpuscraper/documentation.md | 20 +- .../cpuscraper/generated_component_test.go | 18 +- .../internal/metadata/generated_config.go | 2 +- .../metadata/generated_config_test.go | 4 +- .../metadata/generated_resource_test.go | 4 +- .../internal/scraper/cpuscraper/metadata.yaml | 19 +- .../scraper/memoryscraper/documentation.md | 14 +- .../memoryscraper/generated_component_test.go | 18 +- .../internal/metadata/generated_config.go | 2 +- .../metadata/generated_config_test.go | 4 +- .../metadata/generated_resource_test.go | 4 +- .../scraper/memoryscraper/metadata.yaml | 3 + .../nginxplusreceiver/documentation.md | 898 +++++++++--------- .../generated_component_test.go | 18 +- .../metadata/generated_config_test.go | 4 +- .../collector/nginxplusreceiver/metadata.yaml | 118 +++ .../collector/nginxreceiver/documentation.md | 48 +- .../nginxreceiver/generated_component_test.go | 18 +- .../metadata/generated_config_test.go | 4 +- .../collector/nginxreceiver/metadata.yaml | 10 + 21 files changed, 736 insertions(+), 512 deletions(-) diff --git a/internal/collector/containermetricsreceiver/generated_component_test.go b/internal/collector/containermetricsreceiver/generated_component_test.go index 5ad9c6c9f..91e4bf1ad 100644 --- a/internal/collector/containermetricsreceiver/generated_component_test.go +++ b/internal/collector/containermetricsreceiver/generated_component_test.go @@ -58,7 +58,7 @@ func TestComponentLifecycle(t *testing.T) { t.Run(tt.name+"-lifecycle", func(t *testing.T) { firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg) require.NoError(t, err) - host := componenttest.NewNopHost() + host := newMdatagenNopHost() require.NoError(t, err) require.NoError(t, firstRcvr.Start(context.Background(), host)) require.NoError(t, firstRcvr.Shutdown(context.Background())) @@ -69,3 +69,19 @@ func TestComponentLifecycle(t *testing.T) { }) } } + +var _ component.Host = (*mdatagenNopHost)(nil) + +type mdatagenNopHost struct{} + +func newMdatagenNopHost() component.Host { + return &mdatagenNopHost{} +} + +func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component { + return nil +} + +func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory { + return nil +} diff --git a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/documentation.md b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/documentation.md index 924cc7069..d54011a9e 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/documentation.md +++ b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/documentation.md @@ -16,26 +16,26 @@ metrics: Number of available logical CPUs. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| {cpu} | Sum | Int | Cumulative | false | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| {cpu} | Sum | Int | Cumulative | false | Development | ### system.cpu.utilization Difference in system.cpu.time since the last measurement per logical CPU, divided by the elapsed time (value in interval [0,1]). -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| 1 | Gauge | Double | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| 1 | Gauge | Double | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| state | CPU usage type. | Str: ``idle``, ``interrupt``, ``nice``, ``softirq``, ``steal``, ``system``, ``user``, ``wait`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| state | CPU usage type. | Str: ``idle``, ``interrupt``, ``nice``, ``softirq``, ``steal``, ``system``, ``user``, ``wait`` | Recommended | ## Resource Attributes | Name | Description | Values | Enabled | | ---- | ----------- | ------ | ------- | -| resource.id | The resource id. | Any Str | false | +| resource.id | The resource id. | Any Str | true | diff --git a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/generated_component_test.go b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/generated_component_test.go index 41cd8cf8b..1e942e362 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/generated_component_test.go +++ b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/generated_component_test.go @@ -57,7 +57,7 @@ func TestComponentLifecycle(t *testing.T) { t.Run(tt.name+"-lifecycle", func(t *testing.T) { firstRcvr, err := tt.createFn(context.Background(), scrapertest.NewNopSettings(typ), cfg) require.NoError(t, err) - host := componenttest.NewNopHost() + host := newMdatagenNopHost() require.NoError(t, err) require.NoError(t, firstRcvr.Start(context.Background(), host)) require.NoError(t, firstRcvr.Shutdown(context.Background())) @@ -68,3 +68,19 @@ func TestComponentLifecycle(t *testing.T) { }) } } + +var _ component.Host = (*mdatagenNopHost)(nil) + +type mdatagenNopHost struct{} + +func newMdatagenNopHost() component.Host { + return &mdatagenNopHost{} +} + +func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component { + return nil +} + +func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory { + return nil +} diff --git a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_config.go b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_config.go index 6a4051486..b816b47bb 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_config.go +++ b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_config.go @@ -77,7 +77,7 @@ type ResourceAttributesConfig struct { func DefaultResourceAttributesConfig() ResourceAttributesConfig { return ResourceAttributesConfig{ ResourceID: ResourceAttributeConfig{ - Enabled: false, + Enabled: true, }, } } diff --git a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_config_test.go b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_config_test.go index 03a411c81..9e01ba7cf 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_config_test.go +++ b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_config_test.go @@ -9,6 +9,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" ) @@ -61,7 +63,7 @@ func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig { sub, err := cm.Sub(name) require.NoError(t, err) cfg := DefaultMetricsBuilderConfig() - require.NoError(t, sub.Unmarshal(&cfg)) + require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused())) return cfg } diff --git a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_resource_test.go b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_resource_test.go index 78a371484..4ca9cd3b8 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_resource_test.go +++ b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/internal/metadata/generated_resource_test.go @@ -20,7 +20,7 @@ func TestResourceBuilder(t *testing.T) { switch tt { case "default": - assert.Equal(t, 0, res.Attributes().Len()) + assert.Equal(t, 1, res.Attributes().Len()) case "all_set": assert.Equal(t, 1, res.Attributes().Len()) case "none_set": @@ -31,7 +31,7 @@ func TestResourceBuilder(t *testing.T) { } val, ok := res.Attributes().Get("resource.id") - assert.Equal(t, tt == "all_set", ok) + assert.True(t, ok) if ok { assert.Equal(t, "resource.id-val", val.Str()) } diff --git a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/metadata.yaml b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/metadata.yaml index 19ab64549..a08a5aaac 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/metadata.yaml +++ b/internal/collector/containermetricsreceiver/internal/scraper/cpuscraper/metadata.yaml @@ -12,6 +12,7 @@ resource_attributes: resource.id: description: The resource id. type: string + enabled: true attributes: state: @@ -20,19 +21,23 @@ attributes: enum: [idle, interrupt, nice, softirq, steal, system, user, wait] metrics: - system.cpu.utilization: - enabled: true - description: Difference in system.cpu.time since the last measurement per logical CPU, divided by the elapsed time (value in interval [0,1]). - unit: "1" - gauge: - value_type: double - attributes: [ state ] system.cpu.logical.count: enabled: true description: Number of available logical CPUs. + stability: + level: development unit: "{cpu}" sum: value_type: int monotonic: false aggregation_temporality: cumulative + system.cpu.utilization: + enabled: true + description: Difference in system.cpu.time since the last measurement per logical CPU, divided by the elapsed time (value in interval [0,1]). + stability: + level: development + unit: "1" + gauge: + value_type: double + attributes: [ state ] diff --git a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/documentation.md b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/documentation.md index 8fabb2f73..28aad83ce 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/documentation.md +++ b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/documentation.md @@ -16,18 +16,18 @@ metrics: Bytes of memory in use. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| By | Sum | Int | Cumulative | false | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| By | Sum | Int | Cumulative | false | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| state | Breakdown of memory usage by type. | Str: ``buffered``, ``cached``, ``inactive``, ``free``, ``slab_reclaimable``, ``slab_unreclaimable``, ``used`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| state | Breakdown of memory usage by type. | Str: ``buffered``, ``cached``, ``inactive``, ``free``, ``slab_reclaimable``, ``slab_unreclaimable``, ``used`` | Recommended | ## Resource Attributes | Name | Description | Values | Enabled | | ---- | ----------- | ------ | ------- | -| resource.id | The resource id. | Any Str | false | +| resource.id | The resource id. | Any Str | true | diff --git a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/generated_component_test.go b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/generated_component_test.go index 323b92a0b..075b15abb 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/generated_component_test.go +++ b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/generated_component_test.go @@ -57,7 +57,7 @@ func TestComponentLifecycle(t *testing.T) { t.Run(tt.name+"-lifecycle", func(t *testing.T) { firstRcvr, err := tt.createFn(context.Background(), scrapertest.NewNopSettings(typ), cfg) require.NoError(t, err) - host := componenttest.NewNopHost() + host := newMdatagenNopHost() require.NoError(t, err) require.NoError(t, firstRcvr.Start(context.Background(), host)) require.NoError(t, firstRcvr.Shutdown(context.Background())) @@ -68,3 +68,19 @@ func TestComponentLifecycle(t *testing.T) { }) } } + +var _ component.Host = (*mdatagenNopHost)(nil) + +type mdatagenNopHost struct{} + +func newMdatagenNopHost() component.Host { + return &mdatagenNopHost{} +} + +func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component { + return nil +} + +func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory { + return nil +} diff --git a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_config.go b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_config.go index cdbb79da9..a2c3e799d 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_config.go +++ b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_config.go @@ -73,7 +73,7 @@ type ResourceAttributesConfig struct { func DefaultResourceAttributesConfig() ResourceAttributesConfig { return ResourceAttributesConfig{ ResourceID: ResourceAttributeConfig{ - Enabled: false, + Enabled: true, }, } } diff --git a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_config_test.go b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_config_test.go index 49ddecbb1..58c3a3738 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_config_test.go +++ b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_config_test.go @@ -9,6 +9,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" ) @@ -59,7 +61,7 @@ func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig { sub, err := cm.Sub(name) require.NoError(t, err) cfg := DefaultMetricsBuilderConfig() - require.NoError(t, sub.Unmarshal(&cfg)) + require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused())) return cfg } diff --git a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_resource_test.go b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_resource_test.go index 78a371484..4ca9cd3b8 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_resource_test.go +++ b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/internal/metadata/generated_resource_test.go @@ -20,7 +20,7 @@ func TestResourceBuilder(t *testing.T) { switch tt { case "default": - assert.Equal(t, 0, res.Attributes().Len()) + assert.Equal(t, 1, res.Attributes().Len()) case "all_set": assert.Equal(t, 1, res.Attributes().Len()) case "none_set": @@ -31,7 +31,7 @@ func TestResourceBuilder(t *testing.T) { } val, ok := res.Attributes().Get("resource.id") - assert.Equal(t, tt == "all_set", ok) + assert.True(t, ok) if ok { assert.Equal(t, "resource.id-val", val.Str()) } diff --git a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/metadata.yaml b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/metadata.yaml index bdac21dfd..090ad97fc 100644 --- a/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/metadata.yaml +++ b/internal/collector/containermetricsreceiver/internal/scraper/memoryscraper/metadata.yaml @@ -12,6 +12,7 @@ resource_attributes: resource.id: description: The resource id. type: string + enabled: true attributes: state: @@ -23,6 +24,8 @@ metrics: system.memory.usage: enabled: true description: Bytes of memory in use. + stability: + level: development unit: By sum: value_type: int diff --git a/internal/collector/nginxplusreceiver/documentation.md b/internal/collector/nginxplusreceiver/documentation.md index d3a9a5745..6bee56b33 100644 --- a/internal/collector/nginxplusreceiver/documentation.md +++ b/internal/collector/nginxplusreceiver/documentation.md @@ -16,919 +16,919 @@ metrics: The total number of bytes read from the cache or proxied server. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| bytes | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| bytes | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.cache.name | The name of the cache. | Any Str | -| nginx.cache.outcome | The outcome for an attempt to fetch content from NGINX's cache | Str: ``BYPASS``, ``EXPIRED``, ``HIT``, ``MISS``, ``REVALIDATED``, ``STALE``, ``UPDATING`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.cache.name | The name of the cache. | Any Str | Recommended | +| nginx.cache.outcome | The outcome for an attempt to fetch content from NGINX's cache | Str: ``BYPASS``, ``EXPIRED``, ``HIT``, ``MISS``, ``REVALIDATED``, ``STALE``, ``UPDATING`` | Recommended | ### nginx.cache.memory.limit The limit on the maximum size of the cache specified in the configuration. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| bytes | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| bytes | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.cache.name | The name of the cache. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.cache.name | The name of the cache. | Any Str | Recommended | ### nginx.cache.memory.usage The current size of the cache. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| bytes | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| bytes | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.cache.name | The name of the cache. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.cache.name | The name of the cache. | Any Str | Recommended | ### nginx.cache.responses The total number of responses read from the cache or proxied server. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| responses | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| responses | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.cache.name | The name of the cache. | Any Str | -| nginx.cache.outcome | The outcome for an attempt to fetch content from NGINX's cache | Str: ``BYPASS``, ``EXPIRED``, ``HIT``, ``MISS``, ``REVALIDATED``, ``STALE``, ``UPDATING`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.cache.name | The name of the cache. | Any Str | Recommended | +| nginx.cache.outcome | The outcome for an attempt to fetch content from NGINX's cache | Str: ``BYPASS``, ``EXPIRED``, ``HIT``, ``MISS``, ``REVALIDATED``, ``STALE``, ``UPDATING`` | Recommended | ### nginx.config.reloads The total number of NGINX config reloads. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| reloads | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| reloads | Sum | Int | Cumulative | true | Development | ### nginx.http.connection.count The current number of connections. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| connections | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| connections | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.connections.outcome | The outcome of a connection | Str: ``ACCEPTED``, ``ACTIVE``, ``HANDLED``, ``READING``, ``WRITING``, ``WAITING``, ``DROPPED``, ``IDLE`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.connections.outcome | The outcome of a connection | Str: ``ACCEPTED``, ``ACTIVE``, ``HANDLED``, ``READING``, ``WRITING``, ``WAITING``, ``DROPPED``, ``IDLE`` | Recommended | ### nginx.http.connections The total number of connections. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| connections | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| connections | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.connections.outcome | The outcome of a connection | Str: ``ACCEPTED``, ``ACTIVE``, ``HANDLED``, ``READING``, ``WRITING``, ``WAITING``, ``DROPPED``, ``IDLE`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.connections.outcome | The outcome of a connection | Str: ``ACCEPTED``, ``ACTIVE``, ``HANDLED``, ``READING``, ``WRITING``, ``WAITING``, ``DROPPED``, ``IDLE`` | Recommended | ### nginx.http.limit_conn.requests The total number of connections to an endpoint with a limit_conn directive. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| connections | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| connections | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.limit_conn.outcome | The outcome for attempting to establish a connection to an endpoint that has a limit_conn directive configured. | Str: ``PASSED``, ``REJECTED``, ``REJECTED_DRY_RUN`` | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.limit_conn.outcome | The outcome for attempting to establish a connection to an endpoint that has a limit_conn directive configured. | Str: ``PASSED``, ``REJECTED``, ``REJECTED_DRY_RUN`` | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.limit_req.requests The total number of requests to an endpoint with a limit_req directive. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.limit_req.outcome | The outcome for attempting to establish a connection to an endpoint that has a limit_req directive configured. | Str: ``PASSED``, ``REJECTED``, ``REJECTED_DRY_RUN``, ``DELAYED``, ``DELAYED_DRY_RUN`` | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.limit_req.outcome | The outcome for attempting to establish a connection to an endpoint that has a limit_req directive configured. | Str: ``PASSED``, ``REJECTED``, ``REJECTED_DRY_RUN``, ``DELAYED``, ``DELAYED_DRY_RUN`` | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.request.count The total number of client requests received, since the last collection interval. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| requests | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| requests | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | -| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | Recommended | ### nginx.http.request.discarded The total number of requests completed without sending a response. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | -| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | Recommended | ### nginx.http.request.io The total number of HTTP byte IO. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| bytes | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| bytes | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.io.direction | The direction of byte traffic. | Str: ``receive``, ``transmit`` | -| nginx.zone.name | The name of the shared memory zone. | Any Str | -| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.io.direction | The direction of byte traffic. | Str: ``receive``, ``transmit`` | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | Recommended | ### nginx.http.request.processing.count The number of client requests that are currently being processed. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| requests | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| requests | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | -| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | Recommended | ### nginx.http.requests The total number of client requests received, since NGINX was last started or reloaded. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | -| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | Recommended | ### nginx.http.response.count The total number of HTTP responses sent to clients since the last collection interval, grouped by status code range. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| responses | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| responses | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | -| nginx.zone.name | The name of the shared memory zone. | Any Str | -| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | Recommended | ### nginx.http.response.status The total number of responses since NGINX was last started or reloaded, grouped by status code range. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| responses | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| responses | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | -| nginx.zone.name | The name of the shared memory zone. | Any Str | -| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | Recommended | ### nginx.http.responses The total number of HTTP responses sent to clients. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| responses | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| responses | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | -| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | +| nginx.zone.type | The type of shared memory zone, depending on what block it was defined in the NGINX configuration. | Str: ``SERVER``, ``LOCATION`` | Recommended | ### nginx.http.upstream.keepalive.count The current number of idle keepalive connections per HTTP upstream. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| connections | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| connections | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.connection.count The average number of active connections per HTTP upstream peer. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| connections | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| connections | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.count The current count of peers on the HTTP upstream grouped by state. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| peers | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| peers | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.state | The current state of an upstream peer. | Str: ``CHECKING``, ``DOWN``, ``DRAINING``, ``UNAVAILABLE``, ``UNHEALTHY``, ``UP`` | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.state | The current state of an upstream peer. | Str: ``CHECKING``, ``DOWN``, ``DRAINING``, ``UNAVAILABLE``, ``UNHEALTHY``, ``UP`` | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.fails The total number of unsuccessful attempts to communicate with the HTTP upstream peer. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| attempts | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| attempts | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.header.time The average time to get the response header from the HTTP upstream peer. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.health_checks The total number of health check requests made to a HTTP upstream peer. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.health_check | The state received from a health check. | Str: ``UNHEALTHY``, ``FAIL`` | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.health_check | The state received from a health check. | Str: ``UNHEALTHY``, ``FAIL`` | Recommended | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.io The total number of byte IO per HTTP upstream peer. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| bytes | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| bytes | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.io.direction | The direction of byte traffic. | Str: ``receive``, ``transmit`` | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.io.direction | The direction of byte traffic. | Str: ``receive``, ``transmit`` | Recommended | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.requests The total number of client requests forwarded to the HTTP upstream peer. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.response.time The average time to get the full response from the HTTP upstream peer. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.responses The total number of responses obtained from the HTTP upstream peer grouped by status range. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| responses | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| responses | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.state Current state of an upstream peer in deployment. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| is_deployed | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| is_deployed | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.peer.state | The current state of an upstream peer. | Str: ``CHECKING``, ``DOWN``, ``DRAINING``, ``UNAVAILABLE``, ``UNHEALTHY``, ``UP`` | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.peer.state | The current state of an upstream peer. | Str: ``CHECKING``, ``DOWN``, ``DRAINING``, ``UNAVAILABLE``, ``UNHEALTHY``, ``UP`` | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.peer.unavailables The total number of times the server became unavailable for client requests ('unavail'). -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.queue.limit The maximum number of requests that can be in the queue at the same time. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| requests | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| requests | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.queue.overflows The total number of requests rejected due to the queue overflow. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| responses | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| responses | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.queue.usage The current number of requests in the queue. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| requests | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| requests | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.http.upstream.zombie.count The current number of upstream peers removed from the group but still processing active client requests. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| is_deployed | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| is_deployed | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.slab.page.free The current number of free memory pages. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| pages | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| pages | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.slab.page.limit The total number of memory pages (free and used). -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| pages | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| pages | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.slab.page.usage The current number of used memory pages. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| pages | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| pages | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.slab.page.utilization The current percentage of used memory pages. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| pages | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| pages | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.slab.slot.allocations The number of attempts to allocate memory of specified size. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| allocations | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| allocations | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.slab.slot.allocation.result | Result of an attempt to allocate memory to a slab slot. | Str: ``FAILURE``, ``SUCCESS`` | -| nginx.slab.slot.limit | The upper limit for a slab slot, used as the identifier for the slot. | Any Int | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.slab.slot.allocation.result | Result of an attempt to allocate memory to a slab slot. | Str: ``FAILURE``, ``SUCCESS`` | Recommended | +| nginx.slab.slot.limit | The upper limit for a slab slot, used as the identifier for the slot. | Any Int | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.slab.slot.free The current number of free memory slots. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| slots | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| slots | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.slab.slot.limit | The upper limit for a slab slot, used as the identifier for the slot. | Any Int | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.slab.slot.limit | The upper limit for a slab slot, used as the identifier for the slot. | Any Int | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.slab.slot.usage The current number of used memory slots. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| slots | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| slots | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.slab.slot.limit | The upper limit for a slab slot, used as the identifier for the slot. | Any Int | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.slab.slot.limit | The upper limit for a slab slot, used as the identifier for the slot. | Any Int | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.ssl.certificate.verify_failures The total number of SSL certificate verification failures. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| certificates | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| certificates | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.ssl.verify_failure.reason | The reason for a SSL certificate verification failure. | Str: ``NO_CERT``, ``EXPIRED_CERT``, ``REVOKED_CERT``, ``HOSTNAME_MISMATCH``, ``OTHER`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.ssl.verify_failure.reason | The reason for a SSL certificate verification failure. | Str: ``NO_CERT``, ``EXPIRED_CERT``, ``REVOKED_CERT``, ``HOSTNAME_MISMATCH``, ``OTHER`` | Recommended | ### nginx.ssl.handshakes The total number of SSL handshakes. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| handshakes | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| handshakes | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.ssl.handshake.reason | The reason for a SSL handshake failure. | Str: ``NO_COMMON_PROTOCOL``, ``NO_COMMON_CIPHER``, ``TIMEOUT``, ``CERT_REJECTED`` | -| nginx.ssl.status | The status of a SSL handshake. | Str: ``FAILED``, ``REUSE`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.ssl.handshake.reason | The reason for a SSL handshake failure. | Str: ``NO_COMMON_PROTOCOL``, ``NO_COMMON_CIPHER``, ``TIMEOUT``, ``CERT_REJECTED`` | Recommended | +| nginx.ssl.status | The status of a SSL handshake. | Str: ``FAILED``, ``REUSE`` | Recommended | ### nginx.stream.connection.accepted The total number of connections accepted from clients. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| connections | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| connections | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.connection.discarded Total number of connections completed without creating a session. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| connections | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| connections | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.connection.processing.count The number of client connections that are currently being processed. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| connections | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| connections | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.io The total number of Stream byte IO. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| bytes | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| bytes | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.io.direction | The direction of byte traffic. | Str: ``receive``, ``transmit`` | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.io.direction | The direction of byte traffic. | Str: ``receive``, ``transmit`` | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.session.status The total number of completed sessions. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| sessions | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| sessions | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.connection.count The current number of Stream Upstream Peer connections. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| connections | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| connections | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.connection.time The average time to connect to the stream upstream peer. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.connections The total number of client connections forwarded to this stream upstream peer. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| connections | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| connections | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.count The current number of stream upstream peers grouped by state. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| peers | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| peers | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.state | The current state of an upstream peer. | Str: ``CHECKING``, ``DOWN``, ``DRAINING``, ``UNAVAILABLE``, ``UNHEALTHY``, ``UP`` | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.state | The current state of an upstream peer. | Str: ``CHECKING``, ``DOWN``, ``DRAINING``, ``UNAVAILABLE``, ``UNHEALTHY``, ``UP`` | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.fails The total number of unsuccessful attempts to communicate with the stream upstream peer. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| attempts | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| attempts | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.health_checks The total number of health check requests made to the stream upstream peer. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.health_check | The state received from a health check. | Str: ``UNHEALTHY``, ``FAIL`` | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.health_check | The state received from a health check. | Str: ``UNHEALTHY``, ``FAIL`` | Recommended | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.io The total number of Stream Upstream Peer byte IO. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| bytes | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| bytes | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.io.direction | The direction of byte traffic. | Str: ``receive``, ``transmit`` | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.io.direction | The direction of byte traffic. | Str: ``receive``, ``transmit`` | Recommended | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.response.time The average time to receive the last byte of data for the stream upstream peer. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.state Current state of upstream peers in deployment. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| deployments | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| deployments | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.peer.state | The current state of an upstream peer. | Str: ``CHECKING``, ``DOWN``, ``DRAINING``, ``UNAVAILABLE``, ``UNHEALTHY``, ``UP`` | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.peer.state | The current state of an upstream peer. | Str: ``CHECKING``, ``DOWN``, ``DRAINING``, ``UNAVAILABLE``, ``UNHEALTHY``, ``UP`` | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.ttfb.time The average time to receive the first byte of data for the stream upstream peer. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.peer.unavailables How many times the server became unavailable for client connections (state 'unavail') due to the number of unsuccessful attempts reaching the max_fails threshold. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.peer.address | The address of the peer. | Any Str | -| nginx.peer.name | The name of the peer. | Any Str | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.peer.address | The address of the peer. | Any Str | Recommended | +| nginx.peer.name | The name of the peer. | Any Str | Recommended | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ### nginx.stream.upstream.zombie.count The current number of peers removed from the group but still processing active client connections. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| deployments | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| deployments | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.upstream.name | The name of the upstream block. | Any Str | -| nginx.zone.name | The name of the shared memory zone. | Any Str | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.upstream.name | The name of the upstream block. | Any Str | Recommended | +| nginx.zone.name | The name of the shared memory zone. | Any Str | Recommended | ## Resource Attributes diff --git a/internal/collector/nginxplusreceiver/generated_component_test.go b/internal/collector/nginxplusreceiver/generated_component_test.go index b9cbd993e..0f28b5fe5 100644 --- a/internal/collector/nginxplusreceiver/generated_component_test.go +++ b/internal/collector/nginxplusreceiver/generated_component_test.go @@ -58,7 +58,7 @@ func TestComponentLifecycle(t *testing.T) { t.Run(tt.name+"-lifecycle", func(t *testing.T) { firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg) require.NoError(t, err) - host := componenttest.NewNopHost() + host := newMdatagenNopHost() require.NoError(t, err) require.NoError(t, firstRcvr.Start(context.Background(), host)) require.NoError(t, firstRcvr.Shutdown(context.Background())) @@ -69,3 +69,19 @@ func TestComponentLifecycle(t *testing.T) { }) } } + +var _ component.Host = (*mdatagenNopHost)(nil) + +type mdatagenNopHost struct{} + +func newMdatagenNopHost() component.Host { + return &mdatagenNopHost{} +} + +func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component { + return nil +} + +func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory { + return nil +} diff --git a/internal/collector/nginxplusreceiver/internal/metadata/generated_config_test.go b/internal/collector/nginxplusreceiver/internal/metadata/generated_config_test.go index 7333954b9..520f7f91f 100644 --- a/internal/collector/nginxplusreceiver/internal/metadata/generated_config_test.go +++ b/internal/collector/nginxplusreceiver/internal/metadata/generated_config_test.go @@ -9,6 +9,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" ) @@ -177,7 +179,7 @@ func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig { sub, err := cm.Sub(name) require.NoError(t, err) cfg := DefaultMetricsBuilderConfig() - require.NoError(t, sub.Unmarshal(&cfg)) + require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused())) return cfg } diff --git a/internal/collector/nginxplusreceiver/metadata.yaml b/internal/collector/nginxplusreceiver/metadata.yaml index 50d1c12ed..1bf694262 100644 --- a/internal/collector/nginxplusreceiver/metadata.yaml +++ b/internal/collector/nginxplusreceiver/metadata.yaml @@ -149,6 +149,8 @@ metrics: nginx.cache.bytes_read: enabled: true description: The total number of bytes read from the cache or proxied server. + stability: + level: development sum: value_type: int monotonic: true @@ -160,6 +162,8 @@ metrics: nginx.cache.memory.limit: enabled: true description: The limit on the maximum size of the cache specified in the configuration. + stability: + level: development gauge: value_type: int unit: bytes @@ -168,6 +172,8 @@ metrics: nginx.cache.memory.usage: enabled: true description: The current size of the cache. + stability: + level: development gauge: value_type: int unit: bytes @@ -176,6 +182,8 @@ metrics: nginx.cache.responses: enabled: true description: The total number of responses read from the cache or proxied server. + stability: + level: development sum: value_type: int monotonic: true @@ -187,6 +195,8 @@ metrics: nginx.config.reloads: enabled: true description: The total number of NGINX config reloads. + stability: + level: development sum: value_type: int monotonic: true @@ -195,6 +205,8 @@ metrics: nginx.http.connection.count: enabled: true description: The current number of connections. + stability: + level: development gauge: value_type: int unit: connections @@ -203,6 +215,8 @@ metrics: nginx.http.connections: enabled: true description: The total number of connections. + stability: + level: development sum: value_type: int monotonic: true @@ -213,6 +227,8 @@ metrics: nginx.http.limit_conn.requests: enabled: true description: The total number of connections to an endpoint with a limit_conn directive. + stability: + level: development sum: value_type: int monotonic: true @@ -224,6 +240,8 @@ metrics: nginx.http.limit_req.requests: enabled: true description: The total number of requests to an endpoint with a limit_req directive. + stability: + level: development sum: value_type: int monotonic: true @@ -235,6 +253,8 @@ metrics: nginx.http.request.count: enabled: true description: The total number of client requests received, since the last collection interval. + stability: + level: development gauge: value_type: int unit: requests @@ -244,6 +264,8 @@ metrics: nginx.http.request.discarded: enabled: true description: The total number of requests completed without sending a response. + stability: + level: development sum: value_type: int monotonic: true @@ -255,6 +277,8 @@ metrics: nginx.http.request.io: enabled: true description: The total number of HTTP byte IO. + stability: + level: development sum: value_type: int monotonic: true @@ -267,6 +291,8 @@ metrics: nginx.http.request.processing.count: enabled: true description: The number of client requests that are currently being processed. + stability: + level: development gauge: value_type: int unit: requests @@ -276,6 +302,8 @@ metrics: nginx.http.requests: enabled: true description: The total number of client requests received, since NGINX was last started or reloaded. + stability: + level: development sum: value_type: int monotonic: true @@ -287,6 +315,8 @@ metrics: nginx.http.response.count: enabled: true description: The total number of HTTP responses sent to clients since the last collection interval, grouped by status code range. + stability: + level: development gauge: value_type: int unit: responses @@ -297,6 +327,8 @@ metrics: nginx.http.response.status: enabled: true description: The total number of responses since NGINX was last started or reloaded, grouped by status code range. + stability: + level: development sum: value_type: int monotonic: true @@ -309,6 +341,8 @@ metrics: nginx.http.responses: enabled: true description: The total number of HTTP responses sent to clients. + stability: + level: development sum: value_type: int monotonic: true @@ -320,6 +354,8 @@ metrics: nginx.http.upstream.keepalive.count: enabled: true description: The current number of idle keepalive connections per HTTP upstream. + stability: + level: development gauge: value_type: int unit: connections @@ -329,6 +365,8 @@ metrics: nginx.http.upstream.peer.connection.count: enabled: true description: The average number of active connections per HTTP upstream peer. + stability: + level: development gauge: value_type: int unit: connections @@ -340,6 +378,8 @@ metrics: nginx.http.upstream.peer.count: enabled: true description: The current count of peers on the HTTP upstream grouped by state. + stability: + level: development gauge: value_type: int unit: peers @@ -350,6 +390,8 @@ metrics: nginx.http.upstream.peer.fails: enabled: true description: The total number of unsuccessful attempts to communicate with the HTTP upstream peer. + stability: + level: development sum: value_type: int monotonic: true @@ -363,6 +405,8 @@ metrics: nginx.http.upstream.peer.header.time: enabled: true description: The average time to get the response header from the HTTP upstream peer. + stability: + level: development gauge: value_type: int unit: ms @@ -374,6 +418,8 @@ metrics: nginx.http.upstream.peer.health_checks: enabled: true description: The total number of health check requests made to a HTTP upstream peer. + stability: + level: development sum: value_type: int monotonic: true @@ -388,6 +434,8 @@ metrics: nginx.http.upstream.peer.io: enabled: true description: The total number of byte IO per HTTP upstream peer. + stability: + level: development sum: value_type: int monotonic: true @@ -402,6 +450,8 @@ metrics: nginx.http.upstream.peer.requests: enabled: true description: The total number of client requests forwarded to the HTTP upstream peer. + stability: + level: development sum: value_type: int monotonic: true @@ -415,6 +465,8 @@ metrics: nginx.http.upstream.peer.response.time: enabled: true description: The average time to get the full response from the HTTP upstream peer. + stability: + level: development gauge: value_type: int unit: ms @@ -426,6 +478,8 @@ metrics: nginx.http.upstream.peer.responses: enabled: true description: The total number of responses obtained from the HTTP upstream peer grouped by status range. + stability: + level: development sum: value_type: int monotonic: true @@ -440,6 +494,8 @@ metrics: nginx.http.upstream.peer.state: enabled: true description: Current state of an upstream peer in deployment. + stability: + level: development gauge: value_type: int unit: is_deployed @@ -452,6 +508,8 @@ metrics: nginx.http.upstream.peer.unavailables: enabled: true description: The total number of times the server became unavailable for client requests ('unavail'). + stability: + level: development sum: value_type: int monotonic: true @@ -465,6 +523,8 @@ metrics: nginx.http.upstream.queue.limit: enabled: true description: The maximum number of requests that can be in the queue at the same time. + stability: + level: development gauge: value_type: int unit: requests @@ -474,6 +534,8 @@ metrics: nginx.http.upstream.queue.overflows: enabled: true description: The total number of requests rejected due to the queue overflow. + stability: + level: development sum: value_type: int monotonic: true @@ -485,6 +547,8 @@ metrics: nginx.http.upstream.queue.usage: enabled: true description: The current number of requests in the queue. + stability: + level: development gauge: value_type: int unit: requests @@ -494,6 +558,8 @@ metrics: nginx.http.upstream.zombie.count: enabled: true description: The current number of upstream peers removed from the group but still processing active client requests. + stability: + level: development gauge: value_type: int unit: is_deployed @@ -503,6 +569,8 @@ metrics: nginx.slab.page.free: enabled: true description: The current number of free memory pages. + stability: + level: development gauge: value_type: int unit: pages @@ -511,6 +579,8 @@ metrics: nginx.slab.page.limit: enabled: true description: The total number of memory pages (free and used). + stability: + level: development gauge: value_type: int unit: pages @@ -519,6 +589,8 @@ metrics: nginx.slab.page.usage: enabled: true description: The current number of used memory pages. + stability: + level: development gauge: value_type: int unit: pages @@ -527,6 +599,8 @@ metrics: nginx.slab.page.utilization: enabled: true description: The current percentage of used memory pages. + stability: + level: development gauge: value_type: int unit: pages @@ -535,6 +609,8 @@ metrics: nginx.slab.slot.allocations: enabled: true description: The number of attempts to allocate memory of specified size. + stability: + level: development sum: value_type: int monotonic: true @@ -547,6 +623,8 @@ metrics: nginx.slab.slot.free: enabled: true description: The current number of free memory slots. + stability: + level: development gauge: value_type: int unit: slots @@ -556,6 +634,8 @@ metrics: nginx.slab.slot.usage: enabled: true description: The current number of used memory slots. + stability: + level: development gauge: value_type: int unit: slots @@ -565,6 +645,8 @@ metrics: nginx.ssl.certificate.verify_failures: enabled: true description: The total number of SSL certificate verification failures. + stability: + level: development sum: value_type: int monotonic: true @@ -575,6 +657,8 @@ metrics: nginx.ssl.handshakes: enabled: true description: The total number of SSL handshakes. + stability: + level: development sum: value_type: int monotonic: true @@ -586,6 +670,8 @@ metrics: nginx.stream.connection.accepted: enabled: true description: The total number of connections accepted from clients. + stability: + level: development sum: value_type: int monotonic: true @@ -596,6 +682,8 @@ metrics: nginx.stream.connection.discarded: enabled: true description: Total number of connections completed without creating a session. + stability: + level: development sum: value_type: int monotonic: true @@ -606,6 +694,8 @@ metrics: nginx.stream.connection.processing.count: enabled: true description: The number of client connections that are currently being processed. + stability: + level: development gauge: value_type: int unit: connections @@ -614,6 +704,8 @@ metrics: nginx.stream.io: enabled: true description: The total number of Stream byte IO. + stability: + level: development sum: value_type: int monotonic: true @@ -625,6 +717,8 @@ metrics: nginx.stream.session.status: enabled: true description: The total number of completed sessions. + stability: + level: development sum: value_type: int monotonic: true @@ -636,6 +730,8 @@ metrics: nginx.stream.upstream.peer.connection.count: enabled: true description: The current number of Stream Upstream Peer connections. + stability: + level: development gauge: value_type: int unit: connections @@ -647,6 +743,8 @@ metrics: nginx.stream.upstream.peer.connection.time: enabled: true description: The average time to connect to the stream upstream peer. + stability: + level: development gauge: value_type: int unit: ms @@ -658,6 +756,8 @@ metrics: nginx.stream.upstream.peer.connections: enabled: true description: The total number of client connections forwarded to this stream upstream peer. + stability: + level: development sum: value_type: int monotonic: true @@ -671,6 +771,8 @@ metrics: nginx.stream.upstream.peer.count: enabled: true description: The current number of stream upstream peers grouped by state. + stability: + level: development gauge: value_type: int unit: peers @@ -681,6 +783,8 @@ metrics: nginx.stream.upstream.peer.fails: enabled: true description: The total number of unsuccessful attempts to communicate with the stream upstream peer. + stability: + level: development sum: value_type: int monotonic: true @@ -693,6 +797,8 @@ metrics: nginx.stream.upstream.peer.health_checks: enabled: true description: The total number of health check requests made to the stream upstream peer. + stability: + level: development sum: value_type: int monotonic: true @@ -707,6 +813,8 @@ metrics: nginx.stream.upstream.peer.io: enabled: true description: The total number of Stream Upstream Peer byte IO. + stability: + level: development sum: value_type: int monotonic: true @@ -721,6 +829,8 @@ metrics: nginx.stream.upstream.peer.response.time: enabled: true description: The average time to receive the last byte of data for the stream upstream peer. + stability: + level: development gauge: value_type: int unit: ms @@ -732,6 +842,8 @@ metrics: nginx.stream.upstream.peer.state: enabled: true description: Current state of upstream peers in deployment. + stability: + level: development sum: value_type: int monotonic: true @@ -746,6 +858,8 @@ metrics: nginx.stream.upstream.peer.ttfb.time: enabled: true description: The average time to receive the first byte of data for the stream upstream peer. + stability: + level: development gauge: value_type: int unit: ms @@ -757,6 +871,8 @@ metrics: nginx.stream.upstream.peer.unavailables: enabled: true description: How many times the server became unavailable for client connections (state 'unavail') due to the number of unsuccessful attempts reaching the max_fails threshold. + stability: + level: development sum: value_type: int monotonic: true @@ -770,6 +886,8 @@ metrics: nginx.stream.upstream.zombie.count: enabled: true description: The current number of peers removed from the group but still processing active client connections. + stability: + level: development gauge: value_type: int unit: deployments diff --git a/internal/collector/nginxreceiver/documentation.md b/internal/collector/nginxreceiver/documentation.md index eb603fbb2..25bbb5105 100644 --- a/internal/collector/nginxreceiver/documentation.md +++ b/internal/collector/nginxreceiver/documentation.md @@ -16,59 +16,59 @@ metrics: The current number of connections. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| connections | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| connections | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.connections.outcome | The outcome of a connection | Str: ``ACCEPTED``, ``ACTIVE``, ``HANDLED``, ``READING``, ``WRITING``, ``WAITING`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.connections.outcome | The outcome of a connection | Str: ``ACCEPTED``, ``ACTIVE``, ``HANDLED``, ``READING``, ``WRITING``, ``WAITING`` | Recommended | ### nginx.http.connections The total number of connections, since NGINX was last started or reloaded. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| connections | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| connections | Sum | Int | Cumulative | true | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.connections.outcome | The outcome of a connection | Str: ``ACCEPTED``, ``ACTIVE``, ``HANDLED``, ``READING``, ``WRITING``, ``WAITING`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.connections.outcome | The outcome of a connection | Str: ``ACCEPTED``, ``ACTIVE``, ``HANDLED``, ``READING``, ``WRITING``, ``WAITING`` | Recommended | ### nginx.http.request.count The total number of client requests received, since the last collection interval. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| requests | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| requests | Gauge | Int | Development | ### nginx.http.requests The total number of client requests received, since NGINX was last started or reloaded. -| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | -| ---- | ----------- | ---------- | ----------------------- | --------- | -| requests | Sum | Int | Cumulative | true | +| Unit | Metric Type | Value Type | Aggregation Temporality | Monotonic | Stability | +| ---- | ----------- | ---------- | ----------------------- | --------- | --------- | +| requests | Sum | Int | Cumulative | true | Development | ### nginx.http.response.count The total number of HTTP responses since the last collection interval, grouped by status code range. -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| responses | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| responses | Gauge | Int | Development | #### Attributes -| Name | Description | Values | -| ---- | ----------- | ------ | -| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| nginx.status_range | A status code range or bucket for a HTTP response's status code. | Str: ``1xx``, ``2xx``, ``3xx``, ``4xx``, ``5xx`` | Recommended | ## Resource Attributes diff --git a/internal/collector/nginxreceiver/generated_component_test.go b/internal/collector/nginxreceiver/generated_component_test.go index 73e21a82d..23f74b9d2 100644 --- a/internal/collector/nginxreceiver/generated_component_test.go +++ b/internal/collector/nginxreceiver/generated_component_test.go @@ -58,7 +58,7 @@ func TestComponentLifecycle(t *testing.T) { t.Run(tt.name+"-lifecycle", func(t *testing.T) { firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg) require.NoError(t, err) - host := componenttest.NewNopHost() + host := newMdatagenNopHost() require.NoError(t, err) require.NoError(t, firstRcvr.Start(context.Background(), host)) require.NoError(t, firstRcvr.Shutdown(context.Background())) @@ -69,3 +69,19 @@ func TestComponentLifecycle(t *testing.T) { }) } } + +var _ component.Host = (*mdatagenNopHost)(nil) + +type mdatagenNopHost struct{} + +func newMdatagenNopHost() component.Host { + return &mdatagenNopHost{} +} + +func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component { + return nil +} + +func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory { + return nil +} diff --git a/internal/collector/nginxreceiver/internal/metadata/generated_config_test.go b/internal/collector/nginxreceiver/internal/metadata/generated_config_test.go index a1e3ad3ff..6e5b36425 100644 --- a/internal/collector/nginxreceiver/internal/metadata/generated_config_test.go +++ b/internal/collector/nginxreceiver/internal/metadata/generated_config_test.go @@ -9,6 +9,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" ) @@ -69,7 +71,7 @@ func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig { sub, err := cm.Sub(name) require.NoError(t, err) cfg := DefaultMetricsBuilderConfig() - require.NoError(t, sub.Unmarshal(&cfg)) + require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused())) return cfg } diff --git a/internal/collector/nginxreceiver/metadata.yaml b/internal/collector/nginxreceiver/metadata.yaml index 1b9fd9de6..635c8cde3 100644 --- a/internal/collector/nginxreceiver/metadata.yaml +++ b/internal/collector/nginxreceiver/metadata.yaml @@ -45,6 +45,8 @@ metrics: nginx.http.connection.count: enabled: true description: The current number of connections. + stability: + level: development gauge: value_type: int unit: connections @@ -53,6 +55,8 @@ metrics: nginx.http.connections: enabled: true description: The total number of connections, since NGINX was last started or reloaded. + stability: + level: development sum: value_type: int monotonic: true @@ -63,12 +67,16 @@ metrics: nginx.http.request.count: enabled: true description: The total number of client requests received, since the last collection interval. + stability: + level: development gauge: value_type: int unit: requests nginx.http.requests: enabled: true description: The total number of client requests received, since NGINX was last started or reloaded. + stability: + level: development sum: value_type: int monotonic: true @@ -77,6 +85,8 @@ metrics: nginx.http.response.count: enabled: true description: The total number of HTTP responses since the last collection interval, grouped by status code range. + stability: + level: development gauge: value_type: int unit: responses From f679ebd958aa9985ce8b7a247942922aaec9ac7c Mon Sep 17 00:00:00 2001 From: Victoria Xu Date: Mon, 22 Jun 2026 16:03:42 -0700 Subject: [PATCH 2/4] 2. define certificate receiver metadata and config types Add metadata.yaml defining nginx.certificate.expiry (gauge, Unix timestamp) with attributes file_path, public_key_algorithm, serial_number, subject.common_name. Add CertificateReceiver config type with InstanceID and CertFilePaths []string. --- .../certificatereceiver/metadata.yaml | 44 +++++++++++++++++++ internal/config/types.go | 28 +++++++----- .../metadata/metrics_metadata.yaml.j2 | 2 + 3 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 internal/collector/certificatereceiver/metadata.yaml diff --git a/internal/collector/certificatereceiver/metadata.yaml b/internal/collector/certificatereceiver/metadata.yaml new file mode 100644 index 000000000..fcceca9a2 --- /dev/null +++ b/internal/collector/certificatereceiver/metadata.yaml @@ -0,0 +1,44 @@ +type: certificate +scope_name: otelcol/certificatereceiver + +status: + class: receiver + stability: + beta: [metrics] + distributions: [contrib] + +resource_attributes: + instance.id: + description: The nginx instance id. + type: string + enabled: true + +attributes: + file_path: + description: "The full file path of the certificate." + type: string + public_key_algorithm: + description: "The public key algorithm." + type: string + serial_number: + description: "The serial number of the certificate." + type: string + subject.common_name: + description: "The Common Name of the certificate." + type: string + +metrics: + nginx.certificate.expiry: + enabled: true + description: "The Unix timestamp (in seconds) at which an SSL/TLS certificate expires" + stability: + level: development + gauge: + value_type: int + unit: "s" + attributes: + - file_path + - public_key_algorithm + - serial_number + - subject.common_name + diff --git a/internal/config/types.go b/internal/config/types.go index b04e600f4..bc9c77a8d 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -225,12 +225,13 @@ type ( // OTel Collector Receiver configuration. Receivers struct { - ContainerMetrics *ContainerMetricsReceiver `yaml:"container_metrics" mapstructure:"container_metrics"` - HostMetrics *HostMetrics `yaml:"host_metrics" mapstructure:"host_metrics"` - OtlpReceivers map[string]*OtlpReceiver `yaml:"otlp" mapstructure:"otlp"` - TcplogReceivers map[string]*TcplogReceiver `yaml:"tcplog" mapstructure:"tcplog"` - NginxReceivers []NginxReceiver `yaml:"-"` - NginxPlusReceivers []NginxPlusReceiver `yaml:"-"` + ContainerMetrics *ContainerMetricsReceiver `yaml:"container_metrics" mapstructure:"container_metrics"` + HostMetrics *HostMetrics `yaml:"host_metrics" mapstructure:"host_metrics"` + OtlpReceivers map[string]*OtlpReceiver `yaml:"otlp" mapstructure:"otlp"` + TcplogReceivers map[string]*TcplogReceiver `yaml:"tcplog" mapstructure:"tcplog"` + NginxReceivers []NginxReceiver `yaml:"-"` + NginxPlusReceivers []NginxPlusReceiver `yaml:"-"` + CertificateReceivers []CertificateReceiver `yaml:"-"` } OtlpReceiver struct { @@ -281,6 +282,12 @@ type ( CollectionInterval time.Duration `yaml:"collection_interval" mapstructure:"collection_interval"` } + CertificateReceiver struct { + InstanceID string `yaml:"instance_id" mapstructure:"instance_id"` + CertFilePaths []string `yaml:"-"` + CollectionInterval time.Duration `yaml:"collection_interval" mapstructure:"collection_interval"` + } + HostMetrics struct { Scrapers *HostMetricsScrapers `yaml:"scrapers" mapstructure:"scrapers"` CollectionInterval time.Duration `yaml:"collection_interval" mapstructure:"collection_interval"` @@ -465,16 +472,13 @@ func (c *Config) AreReceiversConfigured() bool { return false } - return c.Collector.Receivers.NginxPlusReceivers != nil || - len(c.Collector.Receivers.NginxPlusReceivers) > 0 || - c.Collector.Receivers.OtlpReceivers != nil || + return len(c.Collector.Receivers.NginxPlusReceivers) > 0 || len(c.Collector.Receivers.OtlpReceivers) > 0 || - c.Collector.Receivers.NginxReceivers != nil || len(c.Collector.Receivers.NginxReceivers) > 0 || c.Collector.Receivers.HostMetrics != nil || c.Collector.Receivers.ContainerMetrics != nil || - c.Collector.Receivers.TcplogReceivers != nil || - len(c.Collector.Receivers.TcplogReceivers) > 0 + len(c.Collector.Receivers.TcplogReceivers) > 0 || + len(c.Collector.Receivers.CertificateReceivers) > 0 } func (c *Config) NewContextWithLabels(ctx context.Context) context.Context { diff --git a/templates/registry/metadata/metrics_metadata.yaml.j2 b/templates/registry/metadata/metrics_metadata.yaml.j2 index 123daf42f..7127c7980 100644 --- a/templates/registry/metadata/metrics_metadata.yaml.j2 +++ b/templates/registry/metadata/metrics_metadata.yaml.j2 @@ -5,6 +5,8 @@ metrics: {{group.metric_name | safe}}: enabled: true description: {{group.brief | trim}} + stability: + level: {{group.stability | replace("experimental", "development") | safe}} {%- if group.instrument == "counter" %} sum: value_type: int From 845caa9d9cdb66065612d545eff545be9c8c574b Mon Sep 17 00:00:00 2001 From: Victoria Xu Date: Mon, 22 Jun 2026 16:04:27 -0700 Subject: [PATCH 3/4] 3. generate certificate receiver boilerplate via mdatagen Run: cd internal/collector/certificatereceiver && mdatagen metadata.yaml --- .../certificatereceiver/documentation.md | 36 +++ .../generated_component_test.go | 87 +++++++ .../generated_package_test.go | 12 + .../internal/metadata/generated_config.go | 92 +++++++ .../metadata/generated_config_test.go | 109 ++++++++ .../internal/metadata/generated_metrics.go | 242 ++++++++++++++++++ .../metadata/generated_metrics_test.go | 127 +++++++++ .../internal/metadata/generated_resource.go | 36 +++ .../metadata/generated_resource_test.go | 40 +++ .../internal/metadata/generated_status.go | 16 ++ .../internal/metadata/testdata/config.yaml | 27 ++ 11 files changed, 824 insertions(+) create mode 100644 internal/collector/certificatereceiver/documentation.md create mode 100644 internal/collector/certificatereceiver/generated_component_test.go create mode 100644 internal/collector/certificatereceiver/generated_package_test.go create mode 100644 internal/collector/certificatereceiver/internal/metadata/generated_config.go create mode 100644 internal/collector/certificatereceiver/internal/metadata/generated_config_test.go create mode 100644 internal/collector/certificatereceiver/internal/metadata/generated_metrics.go create mode 100644 internal/collector/certificatereceiver/internal/metadata/generated_metrics_test.go create mode 100644 internal/collector/certificatereceiver/internal/metadata/generated_resource.go create mode 100644 internal/collector/certificatereceiver/internal/metadata/generated_resource_test.go create mode 100644 internal/collector/certificatereceiver/internal/metadata/generated_status.go create mode 100644 internal/collector/certificatereceiver/internal/metadata/testdata/config.yaml diff --git a/internal/collector/certificatereceiver/documentation.md b/internal/collector/certificatereceiver/documentation.md new file mode 100644 index 000000000..11ba2ed78 --- /dev/null +++ b/internal/collector/certificatereceiver/documentation.md @@ -0,0 +1,36 @@ +[comment]: <> (Code generated by mdatagen. DO NOT EDIT.) + +# certificate + +## Default Metrics + +The following metrics are emitted by default. Each of them can be disabled by applying the following configuration: + +```yaml +metrics: + : + enabled: false +``` + +### nginx.certificate.expiry + +The Unix timestamp (in seconds) at which an SSL/TLS certificate expires + +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| s | Gauge | Int | Development | + +#### Attributes + +| Name | Description | Values | Requirement Level | +| ---- | ----------- | ------ | -------- | +| file_path | The full file path of the certificate. | Any Str | Recommended | +| public_key_algorithm | The public key algorithm. | Any Str | Recommended | +| serial_number | The serial number of the certificate. | Any Str | Recommended | +| subject.common_name | The Common Name of the certificate. | Any Str | Recommended | + +## Resource Attributes + +| Name | Description | Values | Enabled | +| ---- | ----------- | ------ | ------- | +| instance.id | The nginx instance id. | Any Str | true | diff --git a/internal/collector/certificatereceiver/generated_component_test.go b/internal/collector/certificatereceiver/generated_component_test.go new file mode 100644 index 000000000..9861cfc67 --- /dev/null +++ b/internal/collector/certificatereceiver/generated_component_test.go @@ -0,0 +1,87 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package certificatereceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +var typ = component.MustNewType("certificate") + +func TestComponentFactoryType(t *testing.T) { + require.Equal(t, typ, NewFactory().Type()) +} + +func TestComponentConfigStruct(t *testing.T) { + require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig())) +} + +func TestComponentLifecycle(t *testing.T) { + factory := NewFactory() + + tests := []struct { + createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) + name string + }{ + + { + name: "metrics", + createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) { + return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop()) + }, + }, + } + + cm, err := confmaptest.LoadConf("metadata.yaml") + require.NoError(t, err) + cfg := factory.CreateDefaultConfig() + sub, err := cm.Sub("tests::config") + require.NoError(t, err) + require.NoError(t, sub.Unmarshal(&cfg)) + + for _, tt := range tests { + t.Run(tt.name+"-shutdown", func(t *testing.T) { + c, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg) + require.NoError(t, err) + err = c.Shutdown(context.Background()) + require.NoError(t, err) + }) + t.Run(tt.name+"-lifecycle", func(t *testing.T) { + firstRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg) + require.NoError(t, err) + host := newMdatagenNopHost() + require.NoError(t, err) + require.NoError(t, firstRcvr.Start(context.Background(), host)) + require.NoError(t, firstRcvr.Shutdown(context.Background())) + secondRcvr, err := tt.createFn(context.Background(), receivertest.NewNopSettings(typ), cfg) + require.NoError(t, err) + require.NoError(t, secondRcvr.Start(context.Background(), host)) + require.NoError(t, secondRcvr.Shutdown(context.Background())) + }) + } +} + +var _ component.Host = (*mdatagenNopHost)(nil) + +type mdatagenNopHost struct{} + +func newMdatagenNopHost() component.Host { + return &mdatagenNopHost{} +} + +func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component { + return nil +} + +func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory { + return nil +} diff --git a/internal/collector/certificatereceiver/generated_package_test.go b/internal/collector/certificatereceiver/generated_package_test.go new file mode 100644 index 000000000..fa903c91b --- /dev/null +++ b/internal/collector/certificatereceiver/generated_package_test.go @@ -0,0 +1,12 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package certificatereceiver + +import ( + "go.uber.org/goleak" + "testing" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/internal/collector/certificatereceiver/internal/metadata/generated_config.go b/internal/collector/certificatereceiver/internal/metadata/generated_config.go new file mode 100644 index 000000000..4e5eb7853 --- /dev/null +++ b/internal/collector/certificatereceiver/internal/metadata/generated_config.go @@ -0,0 +1,92 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/filter" +) + +// MetricConfig provides common config for a particular metric. +type MetricConfig struct { + Enabled bool `mapstructure:"enabled"` + + enabledSetByUser bool +} + +func (ms *MetricConfig) Unmarshal(parser *confmap.Conf) error { + if parser == nil { + return nil + } + err := parser.Unmarshal(ms) + if err != nil { + return err + } + ms.enabledSetByUser = parser.IsSet("enabled") + return nil +} + +// MetricsConfig provides config for certificate metrics. +type MetricsConfig struct { + NginxCertificateExpiry MetricConfig `mapstructure:"nginx.certificate.expiry"` +} + +func DefaultMetricsConfig() MetricsConfig { + return MetricsConfig{ + NginxCertificateExpiry: MetricConfig{ + Enabled: true, + }, + } +} + +// ResourceAttributeConfig provides common config for a particular resource attribute. +type ResourceAttributeConfig struct { + Enabled bool `mapstructure:"enabled"` + // Experimental: MetricsInclude defines a list of filters for attribute values. + // If the list is not empty, only metrics with matching resource attribute values will be emitted. + MetricsInclude []filter.Config `mapstructure:"metrics_include"` + // Experimental: MetricsExclude defines a list of filters for attribute values. + // If the list is not empty, metrics with matching resource attribute values will not be emitted. + // MetricsInclude has higher priority than MetricsExclude. + MetricsExclude []filter.Config `mapstructure:"metrics_exclude"` + + enabledSetByUser bool +} + +func (rac *ResourceAttributeConfig) Unmarshal(parser *confmap.Conf) error { + if parser == nil { + return nil + } + err := parser.Unmarshal(rac) + if err != nil { + return err + } + rac.enabledSetByUser = parser.IsSet("enabled") + return nil +} + +// ResourceAttributesConfig provides config for certificate resource attributes. +type ResourceAttributesConfig struct { + InstanceID ResourceAttributeConfig `mapstructure:"instance.id"` +} + +func DefaultResourceAttributesConfig() ResourceAttributesConfig { + return ResourceAttributesConfig{ + InstanceID: ResourceAttributeConfig{ + Enabled: true, + }, + } +} + +// MetricsBuilderConfig is a configuration for certificate metrics builder. +type MetricsBuilderConfig struct { + Metrics MetricsConfig `mapstructure:"metrics"` + ResourceAttributes ResourceAttributesConfig `mapstructure:"resource_attributes"` +} + +func DefaultMetricsBuilderConfig() MetricsBuilderConfig { + return MetricsBuilderConfig{ + Metrics: DefaultMetricsConfig(), + ResourceAttributes: DefaultResourceAttributesConfig(), + } +} diff --git a/internal/collector/certificatereceiver/internal/metadata/generated_config_test.go b/internal/collector/certificatereceiver/internal/metadata/generated_config_test.go new file mode 100644 index 000000000..17b5053b2 --- /dev/null +++ b/internal/collector/certificatereceiver/internal/metadata/generated_config_test.go @@ -0,0 +1,109 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/confmap/confmaptest" +) + +func TestMetricsBuilderConfig(t *testing.T) { + tests := []struct { + name string + want MetricsBuilderConfig + }{ + { + name: "default", + want: DefaultMetricsBuilderConfig(), + }, + { + name: "all_set", + want: MetricsBuilderConfig{ + Metrics: MetricsConfig{ + NginxCertificateExpiry: MetricConfig{Enabled: true}, + }, + ResourceAttributes: ResourceAttributesConfig{ + InstanceID: ResourceAttributeConfig{Enabled: true}, + }, + }, + }, + { + name: "none_set", + want: MetricsBuilderConfig{ + Metrics: MetricsConfig{ + NginxCertificateExpiry: MetricConfig{Enabled: false}, + }, + ResourceAttributes: ResourceAttributesConfig{ + InstanceID: ResourceAttributeConfig{Enabled: false}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := loadMetricsBuilderConfig(t, tt.name) + diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(MetricConfig{}, ResourceAttributeConfig{})) + require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff) + }) + } +} + +func loadMetricsBuilderConfig(t *testing.T, name string) MetricsBuilderConfig { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + sub, err := cm.Sub(name) + require.NoError(t, err) + cfg := DefaultMetricsBuilderConfig() + require.NoError(t, sub.Unmarshal(&cfg, confmap.WithIgnoreUnused())) + return cfg +} + +func TestResourceAttributesConfig(t *testing.T) { + tests := []struct { + name string + want ResourceAttributesConfig + }{ + { + name: "default", + want: DefaultResourceAttributesConfig(), + }, + { + name: "all_set", + want: ResourceAttributesConfig{ + InstanceID: ResourceAttributeConfig{Enabled: true}, + }, + }, + { + name: "none_set", + want: ResourceAttributesConfig{ + InstanceID: ResourceAttributeConfig{Enabled: false}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := loadResourceAttributesConfig(t, tt.name) + diff := cmp.Diff(tt.want, cfg, cmpopts.IgnoreUnexported(ResourceAttributeConfig{})) + require.Emptyf(t, diff, "Config mismatch (-expected +actual):\n%s", diff) + }) + } +} + +func loadResourceAttributesConfig(t *testing.T, name string) ResourceAttributesConfig { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml")) + require.NoError(t, err) + sub, err := cm.Sub(name) + require.NoError(t, err) + sub, err = sub.Sub("resource_attributes") + require.NoError(t, err) + cfg := DefaultResourceAttributesConfig() + require.NoError(t, sub.Unmarshal(&cfg)) + return cfg +} diff --git a/internal/collector/certificatereceiver/internal/metadata/generated_metrics.go b/internal/collector/certificatereceiver/internal/metadata/generated_metrics.go new file mode 100644 index 000000000..dcec3019f --- /dev/null +++ b/internal/collector/certificatereceiver/internal/metadata/generated_metrics.go @@ -0,0 +1,242 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/filter" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver" +) + +var MetricsInfo = metricsInfo{ + NginxCertificateExpiry: metricInfo{ + Name: "nginx.certificate.expiry", + }, +} + +type metricsInfo struct { + NginxCertificateExpiry metricInfo +} + +type metricInfo struct { + Name string +} + +type metricNginxCertificateExpiry struct { + data pmetric.Metric // data buffer for generated metric. + config MetricConfig // metric config provided by user. + capacity int // max observed number of data points added to the metric. +} + +// init fills nginx.certificate.expiry metric with initial data. +func (m *metricNginxCertificateExpiry) init() { + m.data.SetName("nginx.certificate.expiry") + m.data.SetDescription("The Unix timestamp (in seconds) at which an SSL/TLS certificate expires") + m.data.SetUnit("s") + m.data.SetEmptyGauge() + m.data.Gauge().DataPoints().EnsureCapacity(m.capacity) +} + +func (m *metricNginxCertificateExpiry) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, filePathAttributeValue string, publicKeyAlgorithmAttributeValue string, serialNumberAttributeValue string, subjectCommonNameAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Gauge().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("file_path", filePathAttributeValue) + dp.Attributes().PutStr("public_key_algorithm", publicKeyAlgorithmAttributeValue) + dp.Attributes().PutStr("serial_number", serialNumberAttributeValue) + dp.Attributes().PutStr("subject.common_name", subjectCommonNameAttributeValue) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricNginxCertificateExpiry) updateCapacity() { + if m.data.Gauge().DataPoints().Len() > m.capacity { + m.capacity = m.data.Gauge().DataPoints().Len() + } +} + +// emit appends recorded metric data to a metrics slice and prepares it for recording another set of data points. +func (m *metricNginxCertificateExpiry) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricNginxCertificateExpiry(cfg MetricConfig) metricNginxCertificateExpiry { + m := metricNginxCertificateExpiry{config: cfg} + if cfg.Enabled { + m.data = pmetric.NewMetric() + m.init() + } + return m +} + +// MetricsBuilder provides an interface for scrapers to report metrics while taking care of all the transformations +// required to produce metric representation defined in metadata and user config. +type MetricsBuilder struct { + config MetricsBuilderConfig // config of the metrics builder. + startTime pcommon.Timestamp // start time that will be applied to all recorded data points. + metricsCapacity int // maximum observed number of metrics per resource. + metricsBuffer pmetric.Metrics // accumulates metrics data before emitting. + buildInfo component.BuildInfo // contains version information. + resourceAttributeIncludeFilter map[string]filter.Filter + resourceAttributeExcludeFilter map[string]filter.Filter + metricNginxCertificateExpiry metricNginxCertificateExpiry +} + +// MetricBuilderOption applies changes to default metrics builder. +type MetricBuilderOption interface { + apply(*MetricsBuilder) +} + +type metricBuilderOptionFunc func(mb *MetricsBuilder) + +func (mbof metricBuilderOptionFunc) apply(mb *MetricsBuilder) { + mbof(mb) +} + +// WithStartTime sets startTime on the metrics builder. +func WithStartTime(startTime pcommon.Timestamp) MetricBuilderOption { + return metricBuilderOptionFunc(func(mb *MetricsBuilder) { + mb.startTime = startTime + }) +} +func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, options ...MetricBuilderOption) *MetricsBuilder { + mb := &MetricsBuilder{ + config: mbc, + startTime: pcommon.NewTimestampFromTime(time.Now()), + metricsBuffer: pmetric.NewMetrics(), + buildInfo: settings.BuildInfo, + metricNginxCertificateExpiry: newMetricNginxCertificateExpiry(mbc.Metrics.NginxCertificateExpiry), + resourceAttributeIncludeFilter: make(map[string]filter.Filter), + resourceAttributeExcludeFilter: make(map[string]filter.Filter), + } + if mbc.ResourceAttributes.InstanceID.MetricsInclude != nil { + mb.resourceAttributeIncludeFilter["instance.id"] = filter.CreateFilter(mbc.ResourceAttributes.InstanceID.MetricsInclude) + } + if mbc.ResourceAttributes.InstanceID.MetricsExclude != nil { + mb.resourceAttributeExcludeFilter["instance.id"] = filter.CreateFilter(mbc.ResourceAttributes.InstanceID.MetricsExclude) + } + + for _, op := range options { + op.apply(mb) + } + return mb +} + +// NewResourceBuilder returns a new resource builder that should be used to build a resource associated with for the emitted metrics. +func (mb *MetricsBuilder) NewResourceBuilder() *ResourceBuilder { + return NewResourceBuilder(mb.config.ResourceAttributes) +} + +// updateCapacity updates max length of metrics and resource attributes that will be used for the slice capacity. +func (mb *MetricsBuilder) updateCapacity(rm pmetric.ResourceMetrics) { + if mb.metricsCapacity < rm.ScopeMetrics().At(0).Metrics().Len() { + mb.metricsCapacity = rm.ScopeMetrics().At(0).Metrics().Len() + } +} + +// ResourceMetricsOption applies changes to provided resource metrics. +type ResourceMetricsOption interface { + apply(pmetric.ResourceMetrics) +} + +type resourceMetricsOptionFunc func(pmetric.ResourceMetrics) + +func (rmof resourceMetricsOptionFunc) apply(rm pmetric.ResourceMetrics) { + rmof(rm) +} + +// WithResource sets the provided resource on the emitted ResourceMetrics. +// It's recommended to use ResourceBuilder to create the resource. +func WithResource(res pcommon.Resource) ResourceMetricsOption { + return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) { + res.CopyTo(rm.Resource()) + }) +} + +// WithStartTimeOverride overrides start time for all the resource metrics data points. +// This option should be only used if different start time has to be set on metrics coming from different resources. +func WithStartTimeOverride(start pcommon.Timestamp) ResourceMetricsOption { + return resourceMetricsOptionFunc(func(rm pmetric.ResourceMetrics) { + var dps pmetric.NumberDataPointSlice + metrics := rm.ScopeMetrics().At(0).Metrics() + for i := 0; i < metrics.Len(); i++ { + switch metrics.At(i).Type() { + case pmetric.MetricTypeGauge: + dps = metrics.At(i).Gauge().DataPoints() + case pmetric.MetricTypeSum: + dps = metrics.At(i).Sum().DataPoints() + } + for j := 0; j < dps.Len(); j++ { + dps.At(j).SetStartTimestamp(start) + } + } + }) +} + +// EmitForResource saves all the generated metrics under a new resource and updates the internal state to be ready for +// recording another set of data points as part of another resource. This function can be helpful when one scraper +// needs to emit metrics from several resources. Otherwise calling this function is not required, +// just `Emit` function can be called instead. +// Resource attributes should be provided as ResourceMetricsOption arguments. +func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) { + rm := pmetric.NewResourceMetrics() + ils := rm.ScopeMetrics().AppendEmpty() + ils.Scope().SetName(ScopeName) + ils.Scope().SetVersion(mb.buildInfo.Version) + ils.Metrics().EnsureCapacity(mb.metricsCapacity) + mb.metricNginxCertificateExpiry.emit(ils.Metrics()) + + for _, op := range options { + op.apply(rm) + } + for attr, filter := range mb.resourceAttributeIncludeFilter { + if val, ok := rm.Resource().Attributes().Get(attr); ok && !filter.Matches(val.AsString()) { + return + } + } + for attr, filter := range mb.resourceAttributeExcludeFilter { + if val, ok := rm.Resource().Attributes().Get(attr); ok && filter.Matches(val.AsString()) { + return + } + } + + if ils.Metrics().Len() > 0 { + mb.updateCapacity(rm) + rm.MoveTo(mb.metricsBuffer.ResourceMetrics().AppendEmpty()) + } +} + +// Emit returns all the metrics accumulated by the metrics builder and updates the internal state to be ready for +// recording another set of metrics. This function will be responsible for applying all the transformations required to +// produce metric representation defined in metadata and user config, e.g. delta or cumulative. +func (mb *MetricsBuilder) Emit(options ...ResourceMetricsOption) pmetric.Metrics { + mb.EmitForResource(options...) + metrics := mb.metricsBuffer + mb.metricsBuffer = pmetric.NewMetrics() + return metrics +} + +// RecordNginxCertificateExpiryDataPoint adds a data point to nginx.certificate.expiry metric. +func (mb *MetricsBuilder) RecordNginxCertificateExpiryDataPoint(ts pcommon.Timestamp, val int64, filePathAttributeValue string, publicKeyAlgorithmAttributeValue string, serialNumberAttributeValue string, subjectCommonNameAttributeValue string) { + mb.metricNginxCertificateExpiry.recordDataPoint(mb.startTime, ts, val, filePathAttributeValue, publicKeyAlgorithmAttributeValue, serialNumberAttributeValue, subjectCommonNameAttributeValue) +} + +// Reset resets metrics builder to its initial state. It should be used when external metrics source is restarted, +// and metrics builder should update its startTime and reset it's internal state accordingly. +func (mb *MetricsBuilder) Reset(options ...MetricBuilderOption) { + mb.startTime = pcommon.NewTimestampFromTime(time.Now()) + for _, op := range options { + op.apply(mb) + } +} diff --git a/internal/collector/certificatereceiver/internal/metadata/generated_metrics_test.go b/internal/collector/certificatereceiver/internal/metadata/generated_metrics_test.go new file mode 100644 index 000000000..d674a3dee --- /dev/null +++ b/internal/collector/certificatereceiver/internal/metadata/generated_metrics_test.go @@ -0,0 +1,127 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver/receivertest" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" +) + +type testDataSet int + +const ( + testDataSetDefault testDataSet = iota + testDataSetAll + testDataSetNone +) + +func TestMetricsBuilder(t *testing.T) { + tests := []struct { + name string + metricsSet testDataSet + resAttrsSet testDataSet + expectEmpty bool + }{ + { + name: "default", + }, + { + name: "all_set", + metricsSet: testDataSetAll, + resAttrsSet: testDataSetAll, + }, + { + name: "none_set", + metricsSet: testDataSetNone, + resAttrsSet: testDataSetNone, + expectEmpty: true, + }, + { + name: "filter_set_include", + resAttrsSet: testDataSetAll, + }, + { + name: "filter_set_exclude", + resAttrsSet: testDataSetAll, + expectEmpty: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + start := pcommon.Timestamp(1_000_000_000) + ts := pcommon.Timestamp(1_000_001_000) + observedZapCore, observedLogs := observer.New(zap.WarnLevel) + settings := receivertest.NewNopSettings(receivertest.NopType) + settings.Logger = zap.New(observedZapCore) + mb := NewMetricsBuilder(loadMetricsBuilderConfig(t, tt.name), settings, WithStartTime(start)) + + expectedWarnings := 0 + + assert.Equal(t, expectedWarnings, observedLogs.Len()) + + defaultMetricsCount := 0 + allMetricsCount := 0 + + defaultMetricsCount++ + allMetricsCount++ + mb.RecordNginxCertificateExpiryDataPoint(ts, 1, "file_path-val", "public_key_algorithm-val", "serial_number-val", "subject.common_name-val") + + rb := mb.NewResourceBuilder() + rb.SetInstanceID("instance.id-val") + res := rb.Emit() + metrics := mb.Emit(WithResource(res)) + + if tt.expectEmpty { + assert.Equal(t, 0, metrics.ResourceMetrics().Len()) + return + } + + assert.Equal(t, 1, metrics.ResourceMetrics().Len()) + rm := metrics.ResourceMetrics().At(0) + assert.Equal(t, res, rm.Resource()) + assert.Equal(t, 1, rm.ScopeMetrics().Len()) + ms := rm.ScopeMetrics().At(0).Metrics() + if tt.metricsSet == testDataSetDefault { + assert.Equal(t, defaultMetricsCount, ms.Len()) + } + if tt.metricsSet == testDataSetAll { + assert.Equal(t, allMetricsCount, ms.Len()) + } + validatedMetrics := make(map[string]bool) + for i := 0; i < ms.Len(); i++ { + switch ms.At(i).Name() { + case "nginx.certificate.expiry": + assert.False(t, validatedMetrics["nginx.certificate.expiry"], "Found a duplicate in the metrics slice: nginx.certificate.expiry") + validatedMetrics["nginx.certificate.expiry"] = true + assert.Equal(t, pmetric.MetricTypeGauge, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Gauge().DataPoints().Len()) + assert.Equal(t, "The Unix timestamp (in seconds) at which an SSL/TLS certificate expires", ms.At(i).Description()) + assert.Equal(t, "s", ms.At(i).Unit()) + dp := ms.At(i).Gauge().DataPoints().At(0) + assert.Equal(t, start, dp.StartTimestamp()) + assert.Equal(t, ts, dp.Timestamp()) + assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType()) + assert.Equal(t, int64(1), dp.IntValue()) + attrVal, ok := dp.Attributes().Get("file_path") + assert.True(t, ok) + assert.Equal(t, "file_path-val", attrVal.Str()) + attrVal, ok = dp.Attributes().Get("public_key_algorithm") + assert.True(t, ok) + assert.Equal(t, "public_key_algorithm-val", attrVal.Str()) + attrVal, ok = dp.Attributes().Get("serial_number") + assert.True(t, ok) + assert.Equal(t, "serial_number-val", attrVal.Str()) + attrVal, ok = dp.Attributes().Get("subject.common_name") + assert.True(t, ok) + assert.Equal(t, "subject.common_name-val", attrVal.Str()) + } + } + }) + } +} diff --git a/internal/collector/certificatereceiver/internal/metadata/generated_resource.go b/internal/collector/certificatereceiver/internal/metadata/generated_resource.go new file mode 100644 index 000000000..80330c232 --- /dev/null +++ b/internal/collector/certificatereceiver/internal/metadata/generated_resource.go @@ -0,0 +1,36 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/pdata/pcommon" +) + +// ResourceBuilder is a helper struct to build resources predefined in metadata.yaml. +// The ResourceBuilder is not thread-safe and must not to be used in multiple goroutines. +type ResourceBuilder struct { + config ResourceAttributesConfig + res pcommon.Resource +} + +// NewResourceBuilder creates a new ResourceBuilder. This method should be called on the start of the application. +func NewResourceBuilder(rac ResourceAttributesConfig) *ResourceBuilder { + return &ResourceBuilder{ + config: rac, + res: pcommon.NewResource(), + } +} + +// SetInstanceID sets provided value as "instance.id" attribute. +func (rb *ResourceBuilder) SetInstanceID(val string) { + if rb.config.InstanceID.Enabled { + rb.res.Attributes().PutStr("instance.id", val) + } +} + +// Emit returns the built resource and resets the internal builder state. +func (rb *ResourceBuilder) Emit() pcommon.Resource { + r := rb.res + rb.res = pcommon.NewResource() + return r +} diff --git a/internal/collector/certificatereceiver/internal/metadata/generated_resource_test.go b/internal/collector/certificatereceiver/internal/metadata/generated_resource_test.go new file mode 100644 index 000000000..01654e680 --- /dev/null +++ b/internal/collector/certificatereceiver/internal/metadata/generated_resource_test.go @@ -0,0 +1,40 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResourceBuilder(t *testing.T) { + for _, tt := range []string{"default", "all_set", "none_set"} { + t.Run(tt, func(t *testing.T) { + cfg := loadResourceAttributesConfig(t, tt) + rb := NewResourceBuilder(cfg) + rb.SetInstanceID("instance.id-val") + + res := rb.Emit() + assert.Equal(t, 0, rb.Emit().Attributes().Len()) // Second call should return empty Resource + + switch tt { + case "default": + assert.Equal(t, 1, res.Attributes().Len()) + case "all_set": + assert.Equal(t, 1, res.Attributes().Len()) + case "none_set": + assert.Equal(t, 0, res.Attributes().Len()) + return + default: + assert.Failf(t, "unexpected test case: %s", tt) + } + + val, ok := res.Attributes().Get("instance.id") + assert.True(t, ok) + if ok { + assert.Equal(t, "instance.id-val", val.Str()) + } + }) + } +} diff --git a/internal/collector/certificatereceiver/internal/metadata/generated_status.go b/internal/collector/certificatereceiver/internal/metadata/generated_status.go new file mode 100644 index 000000000..431c6d0a9 --- /dev/null +++ b/internal/collector/certificatereceiver/internal/metadata/generated_status.go @@ -0,0 +1,16 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/component" +) + +var ( + Type = component.MustNewType("certificate") + ScopeName = "otelcol/certificatereceiver" +) + +const ( + MetricsStability = component.StabilityLevelBeta +) diff --git a/internal/collector/certificatereceiver/internal/metadata/testdata/config.yaml b/internal/collector/certificatereceiver/internal/metadata/testdata/config.yaml new file mode 100644 index 000000000..ce5adf8ca --- /dev/null +++ b/internal/collector/certificatereceiver/internal/metadata/testdata/config.yaml @@ -0,0 +1,27 @@ +default: +all_set: + metrics: + nginx.certificate.expiry: + enabled: true + resource_attributes: + instance.id: + enabled: true +none_set: + metrics: + nginx.certificate.expiry: + enabled: false + resource_attributes: + instance.id: + enabled: false +filter_set_include: + resource_attributes: + instance.id: + enabled: true + metrics_include: + - regexp: ".*" +filter_set_exclude: + resource_attributes: + instance.id: + enabled: true + metrics_exclude: + - strict: "instance.id-val" From a9a74042215e26304eecd48bb2cb354192d0302e Mon Sep 17 00:00:00 2001 From: Victoria Xu Date: Mon, 22 Jun 2026 16:09:06 -0700 Subject: [PATCH 4/4] 4. implement certificate receiver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scraper reads cert files via crypto/x509 on each 15s scrape and emits nginx.certificate.expiry (Unix timestamp) per cert — renewals are picked up immediately without a collector restart. Gated on FeatureCertificates. Collector restarts only when the set of watched cert file paths changes. --- .../collector/certificatereceiver/config.go | 33 +++ internal/collector/certificatereceiver/doc.go | 8 + .../collector/certificatereceiver/factory.go | 59 ++++ .../collector/certificatereceiver/scraper.go | 165 +++++++++++ .../certificatereceiver/scraper_test.go | 277 ++++++++++++++++++ internal/collector/factories.go | 2 + internal/collector/factories_test.go | 2 +- internal/collector/otel_collector_plugin.go | 78 ++++- internal/collector/otelcol.tmpl | 29 +- .../health/health_watcher_service_test.go | 21 +- test/load/benchmarks.json | 32 ++ 11 files changed, 693 insertions(+), 13 deletions(-) create mode 100644 internal/collector/certificatereceiver/config.go create mode 100644 internal/collector/certificatereceiver/doc.go create mode 100644 internal/collector/certificatereceiver/factory.go create mode 100644 internal/collector/certificatereceiver/scraper.go create mode 100644 internal/collector/certificatereceiver/scraper_test.go create mode 100644 test/load/benchmarks.json diff --git a/internal/collector/certificatereceiver/config.go b/internal/collector/certificatereceiver/config.go new file mode 100644 index 000000000..d2e3c6307 --- /dev/null +++ b/internal/collector/certificatereceiver/config.go @@ -0,0 +1,33 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. +package certificatereceiver + +import ( + "time" + + "github.com/nginx/agent/v3/internal/collector/certificatereceiver/internal/metadata" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/scraper/scraperhelper" +) + +const defaultCollectInterval = 15 * time.Second + +type Config struct { + InstanceID string `mapstructure:"instance_id"` + CertFilePaths []string `mapstructure:"cert_file_paths"` + MetricsBuilderConfig metadata.MetricsBuilderConfig `mapstructure:",squash"` + scraperhelper.ControllerConfig `mapstructure:",squash"` +} + +//nolint:ireturn // must return default controller interface +func createDefaultConfig() component.Config { + cfg := scraperhelper.NewDefaultControllerConfig() + cfg.CollectionInterval = defaultCollectInterval + + return &Config{ + ControllerConfig: cfg, + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + } +} diff --git a/internal/collector/certificatereceiver/doc.go b/internal/collector/certificatereceiver/doc.go new file mode 100644 index 000000000..96a8c8497 --- /dev/null +++ b/internal/collector/certificatereceiver/doc.go @@ -0,0 +1,8 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. + +//go:generate mdatagen metadata.yaml + +package certificatereceiver diff --git a/internal/collector/certificatereceiver/factory.go b/internal/collector/certificatereceiver/factory.go new file mode 100644 index 000000000..b520ed49d --- /dev/null +++ b/internal/collector/certificatereceiver/factory.go @@ -0,0 +1,59 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. +package certificatereceiver + +import ( + "context" + "errors" + + "github.com/nginx/agent/v3/internal/collector/certificatereceiver/internal/metadata" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/scraper" + "go.opentelemetry.io/collector/scraper/scraperhelper" +) + +//nolint:ireturn // must return metrics receiver interface +func NewFactory() receiver.Factory { + return receiver.NewFactory( + metadata.Type, + createDefaultConfig, + receiver.WithMetrics(createMetricsReceiver, metadata.MetricsStability)) +} + +//nolint:ireturn // must return metrics receiver interface +func createMetricsReceiver( + ctx context.Context, + params receiver.Settings, + rConf component.Config, + metricsConsumer consumer.Metrics, +) (receiver.Metrics, error) { + logger := params.Logger.Sugar() + + logger.Info("Creating new certificate metrics receiver") + + cfg, ok := rConf.(*Config) + if !ok { + return nil, errors.New("failed to cast to Config in certificate metrics receiver") + } + + cs := newCertificateScraper(params, cfg) + csMetrics, csMetricsError := scraper.NewMetrics( + cs.Scrape, + scraper.WithStart(cs.Start), + scraper.WithShutdown(cs.Shutdown), + ) + if csMetricsError != nil { + return nil, csMetricsError + } + + return scraperhelper.NewMetricsController( + &cfg.ControllerConfig, + params, + metricsConsumer, + scraperhelper.AddMetricsScraper(metadata.Type, csMetrics), + ) +} diff --git a/internal/collector/certificatereceiver/scraper.go b/internal/collector/certificatereceiver/scraper.go new file mode 100644 index 000000000..dd54c2242 --- /dev/null +++ b/internal/collector/certificatereceiver/scraper.go @@ -0,0 +1,165 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. +package certificatereceiver + +import ( + "context" + "crypto/x509" + "encoding/pem" + "fmt" + "log/slog" + "os" + "time" + + "github.com/nginx/agent/v3/internal/collector/certificatereceiver/internal/metadata" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" +) + +type certCacheEntry struct { + mtime time.Time + certs []*x509.Certificate +} + +type CertificateScraper struct { + cfg *Config + mb *metadata.MetricsBuilder + rb *metadata.ResourceBuilder + logger *zap.Logger + cache map[string]*certCacheEntry +} + +func newCertificateScraper( + settings receiver.Settings, + cfg *Config, +) *CertificateScraper { + logger := settings.Logger + logger.Info("Creating certificate scraper") + mb := metadata.NewMetricsBuilder(cfg.MetricsBuilderConfig, settings) + rb := mb.NewResourceBuilder() + + return &CertificateScraper{ + cfg: cfg, + mb: mb, + rb: rb, + logger: logger, + cache: make(map[string]*certCacheEntry), + } +} + +func (c *CertificateScraper) Start(ctx context.Context, _ component.Host) error { + for _, path := range c.cfg.CertFilePaths { + if err := c.refreshCacheEntry(path); err != nil { + slog.WarnContext(ctx, "Failed to pre-load certificate file", "path", path, "error", err) + } + } + + return nil +} + +func (c *CertificateScraper) Scrape(ctx context.Context) (pmetric.Metrics, error) { + if len(c.cfg.CertFilePaths) == 0 { + return pmetric.NewMetrics(), nil + } + + now := pcommon.NewTimestampFromTime(time.Now()) + + if c.cfg.InstanceID != "" { + c.rb.SetInstanceID(c.cfg.InstanceID) + } + + const hexBase = 16 + + for _, path := range c.cfg.CertFilePaths { + if err := c.refreshCacheEntry(path); err != nil { + slog.WarnContext(ctx, "Failed to load certificate file, skipping", "path", path, "error", err) + + continue + } + + for _, cert := range c.cache[path].certs { + c.mb.RecordNginxCertificateExpiryDataPoint( + now, cert.NotAfter.Unix(), + path, + cert.PublicKeyAlgorithm.String(), + cert.SerialNumber.Text(hexBase), + cert.Subject.CommonName, + ) + } + } + + return c.mb.Emit(metadata.WithResource(c.rb.Emit())), nil +} + +func (c *CertificateScraper) Shutdown(_ context.Context) error { + return nil +} + +// refreshCacheEntry stats the file and re-parses it only if the mtime has changed. +// On any error the cache entry is cleared so the next call retries. +func (c *CertificateScraper) refreshCacheEntry(path string) error { + info, err := os.Stat(path) + if err != nil { + delete(c.cache, path) + + return fmt.Errorf("stat %s: %w", path, err) + } + + if entry, ok := c.cache[path]; ok && entry.mtime.Equal(info.ModTime()) { + return nil + } + + certs, err := parseCertFile(path) + if err != nil { + delete(c.cache, path) + + return err + } + + c.cache[path] = &certCacheEntry{ + mtime: info.ModTime(), + certs: certs, + } + + return nil +} + +func parseCertFile(path string) ([]*x509.Certificate, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read file: %w", err) + } + + var certs []*x509.Certificate + + for len(data) > 0 { + var block *pem.Block + block, data = pem.Decode(data) + + if block == nil { + break + } + + if block.Type != "CERTIFICATE" { + continue + } + + cert, parseErr := x509.ParseCertificate(block.Bytes) + if parseErr != nil { + return nil, fmt.Errorf("parse certificate in %s: %w", path, parseErr) + } + + certs = append(certs, cert) + } + + if len(certs) == 0 { + return nil, fmt.Errorf("no certificates found in %s", path) + } + + return certs, nil +} diff --git a/internal/collector/certificatereceiver/scraper_test.go b/internal/collector/certificatereceiver/scraper_test.go new file mode 100644 index 000000000..5f4da4945 --- /dev/null +++ b/internal/collector/certificatereceiver/scraper_test.go @@ -0,0 +1,277 @@ +// Copyright (c) F5, Inc. +// +// This source code is licensed under the Apache License, Version 2.0 license found in the +// LICENSE file in the root directory of this source tree. +package certificatereceiver + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "os" + "path/filepath" + "testing" + "time" + + "github.com/nginx/agent/v3/internal/collector/certificatereceiver/internal/metadata" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +// writeTempCert writes a self-signed x509 cert with the given NotAfter to a new temp file +// and returns the file path. The file is cleaned up when the test ends. +func writeTempCert(t *testing.T, notAfter time.Time, commonName string) string { + t.Helper() + + f, err := os.CreateTemp(t.TempDir(), "*.crt") + require.NoError(t, err) + defer f.Close() + + appendCertToFile(t, f, notAfter, commonName, 1) + + return f.Name() +} + +// writeTempCertBundle writes multiple certs into a single PEM file and returns the path. +func writeTempCertBundle(t *testing.T, certs []struct { + notAfter time.Time + commonName string +}, +) string { + t.Helper() + + f, err := os.CreateTemp(t.TempDir(), "*.crt") + require.NoError(t, err) + defer f.Close() + + for i, c := range certs { + appendCertToFile(t, f, c.notAfter, c.commonName, int64(i+1)) + } + + return f.Name() +} + +func appendCertToFile(t *testing.T, f *os.File, notAfter time.Time, commonName string, serial int64) { + t.Helper() + + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + template := &x509.Certificate{ + SerialNumber: big.NewInt(serial), + Subject: pkix.Name{CommonName: commonName}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: notAfter, + } + + der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) + require.NoError(t, err) + + require.NoError(t, pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: der})) +} + +func newTestScraper(t *testing.T, cfg *Config) *CertificateScraper { + t.Helper() + settings := receivertest.NewNopSettings(component.MustNewType("certificate")) + + return newCertificateScraper(settings, cfg) +} + +func TestScrape_FutureExpiry(t *testing.T) { + notAfter := time.Now().Add(30 * 24 * time.Hour) + path := writeTempCert(t, notAfter, "example.com") + + cfg := &Config{ + InstanceID: "test-instance", + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + CertFilePaths: []string{path}, + } + scraper := newTestScraper(t, cfg) + + metrics, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + + require.Equal(t, 1, metrics.ResourceMetrics().Len()) + rm := metrics.ResourceMetrics().At(0) + + instanceID, ok := rm.Resource().Attributes().Get("instance.id") + require.True(t, ok) + require.Equal(t, "test-instance", instanceID.AsString()) + + ms := rm.ScopeMetrics().At(0).Metrics() + require.Equal(t, 1, ms.Len()) + require.Equal(t, "nginx.certificate.expiry", ms.At(0).Name()) + + dp := ms.At(0).Gauge().DataPoints().At(0) + require.Equal(t, notAfter.Unix(), dp.IntValue(), "expiry should be the cert's NotAfter Unix timestamp") + + filePathAttr, ok := dp.Attributes().Get("file_path") + require.True(t, ok) + require.Equal(t, path, filePathAttr.AsString()) + + commonName, ok := dp.Attributes().Get("subject.common_name") + require.True(t, ok) + require.Equal(t, "example.com", commonName.AsString()) + + pubKeyAlgo, ok := dp.Attributes().Get("public_key_algorithm") + require.True(t, ok) + require.Equal(t, "ECDSA", pubKeyAlgo.AsString()) +} + +func TestScrape_ExpiredCert(t *testing.T) { + notAfter := time.Now().Add(-7 * 24 * time.Hour) + path := writeTempCert(t, notAfter, "expired.example.com") + + cfg := &Config{ + InstanceID: "test-instance", + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + CertFilePaths: []string{path}, + } + scraper := newTestScraper(t, cfg) + + metrics, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + + require.Equal(t, 1, metrics.ResourceMetrics().Len()) + dp := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Gauge().DataPoints().At(0) + require.Equal(t, notAfter.Unix(), dp.IntValue(), "expiry should be the cert's NotAfter Unix timestamp") +} + +func TestScrape_NoCerts(t *testing.T) { + cfg := &Config{ + InstanceID: "test-instance", + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + CertFilePaths: nil, + } + scraper := newTestScraper(t, cfg) + + metrics, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + require.Equal(t, 0, metrics.ResourceMetrics().Len(), "Should return empty metrics when no certs configured") +} + +func TestScrape_MultipleCerts(t *testing.T) { + path1 := writeTempCert(t, time.Now().Add(10*24*time.Hour), "one.example.com") + path2 := writeTempCert(t, time.Now().Add(60*24*time.Hour), "two.example.com") + + cfg := &Config{ + InstanceID: "test-instance", + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + CertFilePaths: []string{path1, path2}, + } + scraper := newTestScraper(t, cfg) + + metrics, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + + require.Equal(t, 1, metrics.ResourceMetrics().Len()) + ms := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics() + require.Equal(t, 1, ms.Len()) + require.Equal(t, 2, ms.At(0).Gauge().DataPoints().Len(), "Should emit one data point per certificate") +} + +func TestScrape_CertBundle(t *testing.T) { + // A single PEM file containing two certificates (e.g. a cert chain) + notAfter1 := time.Now().Add(10 * 24 * time.Hour) + notAfter2 := time.Now().Add(60 * 24 * time.Hour) + + bundlePath := writeTempCertBundle(t, []struct { + notAfter time.Time + commonName string + }{ + {notAfter1, "leaf.example.com"}, + {notAfter2, "intermediate.example.com"}, + }) + + cfg := &Config{ + InstanceID: "test-instance", + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + CertFilePaths: []string{bundlePath}, + } + scraper := newTestScraper(t, cfg) + + metrics, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + + require.Equal(t, 1, metrics.ResourceMetrics().Len()) + dps := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Gauge().DataPoints() + require.Equal(t, 2, dps.Len(), "Should emit one data point per cert in a bundle file") +} + +func TestScrape_MissingFile(t *testing.T) { + cfg := &Config{ + InstanceID: "test-instance", + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + CertFilePaths: []string{filepath.Join(t.TempDir(), "nonexistent.crt")}, + } + scraper := newTestScraper(t, cfg) + + // Missing file is logged and skipped — no error, no metrics + metrics, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + require.Equal(t, 0, metrics.ResourceMetrics().Len()) +} + +func TestScrape_CacheHit(t *testing.T) { + // Verify that after the first scrape, a subsequent scrape with an unchanged + // file uses the cache (mtime unchanged) and still returns correct data. + notAfter := time.Now().Add(30 * 24 * time.Hour) + path := writeTempCert(t, notAfter, "cached.example.com") + + cfg := &Config{ + InstanceID: "test-instance", + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + CertFilePaths: []string{path}, + } + scraper := newTestScraper(t, cfg) + + for range 2 { + metrics, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + dps := metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Gauge().DataPoints() + require.Equal(t, 1, dps.Len()) + } +} + +func TestScrape_CacheInvalidatedOnRenewal(t *testing.T) { + // Simulate cert renewal: overwrite the file (new mtime) and verify + // the scraper picks up the new expiry on the next scrape. + dir := t.TempDir() + path := filepath.Join(dir, "cert.pem") + + notAfter1 := time.Now().Add(10 * 24 * time.Hour).Truncate(time.Second) + f1, err := os.Create(path) + require.NoError(t, err) + appendCertToFile(t, f1, notAfter1, "before.example.com", 1) + require.NoError(t, f1.Close()) + + cfg := &Config{ + InstanceID: "test-instance", + MetricsBuilderConfig: metadata.DefaultMetricsBuilderConfig(), + CertFilePaths: []string{path}, + } + scraper := newTestScraper(t, cfg) + + metrics1, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + dp1 := metrics1.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Gauge().DataPoints().At(0) + require.Equal(t, notAfter1.Unix(), dp1.IntValue()) + + // Overwrite with a new cert (different expiry, new mtime) + notAfter2 := time.Now().Add(90 * 24 * time.Hour).Truncate(time.Second) + f2, err := os.Create(path) + require.NoError(t, err) + appendCertToFile(t, f2, notAfter2, "after.example.com", 2) + require.NoError(t, f2.Close()) + + metrics2, err := scraper.Scrape(context.Background()) + require.NoError(t, err) + dp2 := metrics2.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics().At(0).Gauge().DataPoints().At(0) + require.Equal(t, notAfter2.Unix(), dp2.IntValue(), "cache should be invalidated after file renewal") +} diff --git a/internal/collector/factories.go b/internal/collector/factories.go index e8117187b..55b2fdb92 100644 --- a/internal/collector/factories.go +++ b/internal/collector/factories.go @@ -6,6 +6,7 @@ package collector import ( + "github.com/nginx/agent/v3/internal/collector/certificatereceiver" "github.com/nginx/agent/v3/internal/collector/containermetricsreceiver" "github.com/nginx/agent/v3/internal/collector/nginxplusreceiver" "github.com/nginx/agent/v3/internal/collector/nginxreceiver" @@ -89,6 +90,7 @@ func createReceiverFactories() map[component.Type]receiver.Factory { nginxplusreceiver.NewFactory(), tcplogreceiver.NewFactory(), filelogreceiver.NewFactory(), + certificatereceiver.NewFactory(), } receivers := make(map[component.Type]receiver.Factory) diff --git a/internal/collector/factories_test.go b/internal/collector/factories_test.go index cd2549e11..c3d5ca0d6 100644 --- a/internal/collector/factories_test.go +++ b/internal/collector/factories_test.go @@ -18,7 +18,7 @@ func TestOTelComponentFactoriesDefault(t *testing.T) { require.NoError(t, err, "OTelComponentFactories should not return an error") assert.NotNil(t, factories, "factories should not be nil") - assert.Len(t, factories.Receivers, 7) + assert.Len(t, factories.Receivers, 8) assert.Len(t, factories.Processors, 9) assert.Len(t, factories.Exporters, 4) assert.Len(t, factories.Extensions, 3) diff --git a/internal/collector/otel_collector_plugin.go b/internal/collector/otel_collector_plugin.go index 79818cc97..91fd7ea11 100644 --- a/internal/collector/otel_collector_plugin.go +++ b/internal/collector/otel_collector_plugin.go @@ -14,6 +14,7 @@ import ( "net/url" "os" "reflect" + "slices" "strings" "sync" "time" @@ -32,9 +33,10 @@ import ( ) const ( - maxTimeToWaitForShutdown = 30 * time.Second - defaultCollectionInterval = 1 * time.Minute - filePermission = 0o600 + maxTimeToWaitForShutdown = 30 * time.Second + defaultCollectionInterval = 1 * time.Minute + defaultCertCollectionInterval = 15 * time.Second + filePermission = 0o600 // To conform to the rfc3164 spec the timestamp in the logs need to be formatted correctly. // Here are some examples of what the timestamp conversions look like. // Notice how if the day begins with a zero that the zero is replaced with an empty space. @@ -542,6 +544,10 @@ func (oc *Collector) checkForNewReceivers(ctx context.Context, nginxConfigContex slog.DebugContext(ctx, "NAP logs feature disabled", "enabled_features", oc.config.Features) } + if oc.config.IsFeatureEnabled(pkgConfig.FeatureCertificates) { + reloadCollector = reloadCollector || oc.updateCertificateReceivers(nginxConfigContext) + } + return reloadCollector } @@ -763,6 +769,72 @@ func (oc *Collector) doesTcplogReceiverAlreadyExist(listenAddress string) bool { return false } +func (oc *Collector) updateCertificateReceivers(nginxConfigContext *model.NginxConfigContext) bool { + certFilePaths := certFilePathsFromFiles(nginxConfigContext.Files) + + for i, certReceiver := range oc.config.Collector.Receivers.CertificateReceivers { + if certReceiver.InstanceID == nginxConfigContext.InstanceID { + if len(certFilePaths) == 0 { + // No certs remaining — remove the receiver + oc.config.Collector.Receivers.CertificateReceivers = append( + oc.config.Collector.Receivers.CertificateReceivers[:i], + oc.config.Collector.Receivers.CertificateReceivers[i+1:]..., + ) + + return true + } + + // Update existing receiver's cert file paths if changed + if !certFilePathsEqual(certReceiver.CertFilePaths, certFilePaths) { + oc.config.Collector.Receivers.CertificateReceivers[i].CertFilePaths = certFilePaths + + return true + } + + return false + } + } + + if len(certFilePaths) == 0 { + return false + } + + oc.config.Collector.Receivers.CertificateReceivers = append( + oc.config.Collector.Receivers.CertificateReceivers, + config.CertificateReceiver{ + InstanceID: nginxConfigContext.InstanceID, + CollectionInterval: defaultCertCollectionInterval, + CertFilePaths: certFilePaths, + }, + ) + + return true +} + +func certFilePathsEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + + sortedA := slices.Clone(a) + sortedB := slices.Clone(b) + slices.Sort(sortedA) + slices.Sort(sortedB) + + return slices.Equal(sortedA, sortedB) +} + +func certFilePathsFromFiles(files []*v1.File) []string { + var paths []string + for _, f := range files { + if _, ok := f.GetFileMeta().GetFileType().(*v1.FileMeta_CertificateMeta); ok { + paths = append(paths, f.GetFileMeta().GetName()) + } + } + + return paths +} + func (oc *Collector) updateResourceAttributes( attributesToAdd []config.ResourceAttribute, ) (actionUpdated bool) { diff --git a/internal/collector/otelcol.tmpl b/internal/collector/otelcol.tmpl index 386fae5a9..c9ca017b4 100644 --- a/internal/collector/otelcol.tmpl +++ b/internal/collector/otelcol.tmpl @@ -119,6 +119,24 @@ receivers: {{- end }} {{- end }} +{{- range .Receivers.CertificateReceivers }} +{{- if gt (len $.Receivers.CertificateReceivers) 1 }} + certificate/{{- .InstanceID -}}: +{{- else }} + certificate: +{{- end}} + instance_id: "{{- .InstanceID -}}" + {{- if .CollectionInterval }} + collection_interval: {{ .CollectionInterval }} + {{- end }} + {{- if .CertFilePaths }} + cert_file_paths: + {{- range .CertFilePaths }} + - "{{- . -}}" + {{- end }} + {{- end }} +{{- end }} + {{- range $index, $tcplogReceiver := .Receivers.TcplogReceivers }} tcp_log/{{$index}}: listen_address: "{{- .ListenAddress -}}" @@ -271,7 +289,7 @@ service: pipelines: {{- range $pipelineName, $pipeline := .Pipelines.Metrics }} - {{- if or (ne $.Receivers.HostMetrics nil) (ne $.Receivers.ContainerMetrics nil) (gt (len $.Receivers.OtlpReceivers) 0) (gt (len $.Receivers.NginxReceivers) 0) (gt (len $.Receivers.NginxPlusReceivers) 0) }} + {{- if or (ne $.Receivers.HostMetrics nil) (ne $.Receivers.ContainerMetrics nil) (gt (len $.Receivers.OtlpReceivers) 0) (gt (len $.Receivers.NginxReceivers) 0) (gt (len $.Receivers.NginxPlusReceivers) 0) (gt (len $.Receivers.CertificateReceivers) 0) }} metrics/{{$pipelineName}}: receivers: {{- range $receiver := $pipeline.Receivers }} @@ -284,12 +302,19 @@ service: {{- end }} {{- end }} {{- range $.Receivers.NginxPlusReceivers }} - {{- if gt (len $.Receivers.NginxReceivers) 1 }} + {{- if gt (len $.Receivers.NginxPlusReceivers) 1 }} - nginxplus/{{- .InstanceID -}} {{- else }} - nginxplus {{- end }} {{- end }} + {{- range $.Receivers.CertificateReceivers }} + {{- if gt (len $.Receivers.CertificateReceivers) 1 }} + - certificate/{{- .InstanceID -}} + {{- else }} + - certificate + {{- end }} + {{- end }} {{- else }} - {{ $receiver }} {{- end }} diff --git a/internal/watcher/health/health_watcher_service_test.go b/internal/watcher/health/health_watcher_service_test.go index 65ceee351..3ac2dee87 100644 --- a/internal/watcher/health/health_watcher_service_test.go +++ b/internal/watcher/health/health_watcher_service_test.go @@ -6,9 +6,9 @@ package health import ( + "context" "errors" "fmt" - "reflect" "testing" mpi "github.com/nginx/agent/v3/api/grpc/mpi/v1" @@ -171,17 +171,24 @@ func TestHealthWatcherService_health(t *testing.T) { healthWatcher := NewHealthWatcherService(agentConfig) fakeHealthWatcher := healthfakes.FakeHealthWatcherOperator{} - fakeHealthWatcher.HealthReturnsOnCall(0, protos.HealthyInstanceHealth(), nil) - fakeHealthWatcher.HealthReturnsOnCall(1, protos.UnhealthyInstanceHealth(), nil) - fakeHealthWatcher.HealthReturnsOnCall(2, nil, errors.New("unable to determine health")) + fakeHealthWatcher.HealthStub = func(_ context.Context, instance *mpi.Instance) ( + *mpi.InstanceHealth, error, + ) { + switch instance.GetInstanceMeta().GetInstanceId() { + case ossInstance.GetInstanceMeta().GetInstanceId(): + return protos.HealthyInstanceHealth(), nil + case plusInstance.GetInstanceMeta().GetInstanceId(): + return protos.UnhealthyInstanceHealth(), nil + default: + return nil, errors.New("unable to determine health") + } + } healthWatcher.instances = test.instances healthWatcher.updateCache(test.cache) healthWatcher.watcher = &fakeHealthWatcher - updatedStatus, isHealthDiff := healthWatcher.health(t.Context()) + _, isHealthDiff := healthWatcher.health(t.Context()) assert.Equal(t, test.isHealthDiff, isHealthDiff) - - reflect.DeepEqual(test.updatedInstances, updatedStatus) }) } } diff --git a/test/load/benchmarks.json b/test/load/benchmarks.json new file mode 100644 index 000000000..f63bd7429 --- /dev/null +++ b/test/load/benchmarks.json @@ -0,0 +1,32 @@ +[ + { + "name": "cpu_percentage_avg", + "unit": "%", + "value": 0, + "extra": "Metric10kDPS/OTLP-darwin-build/nginx-agent - Cpu Percentage" + }, + { + "name": "cpu_percentage_max", + "unit": "%", + "value": 0, + "extra": "Metric10kDPS/OTLP-darwin-build/nginx-agent - Cpu Percentage" + }, + { + "name": "ram_mib_avg", + "unit": "MiB", + "value": 0, + "extra": "Metric10kDPS/OTLP-darwin-build/nginx-agent - RAM (MiB)" + }, + { + "name": "ram_mib_max", + "unit": "MiB", + "value": 0, + "extra": "Metric10kDPS/OTLP-darwin-build/nginx-agent - RAM (MiB)" + }, + { + "name": "dropped_span_count", + "unit": "spans", + "value": 0, + "extra": "Metric10kDPS/OTLP-darwin-build/nginx-agent - Dropped Span Count" + } +] \ No newline at end of file