diff --git a/.chloggen/splunkenterprisereceiver-splunkd-info.yaml b/.chloggen/splunkenterprisereceiver-splunkd-info.yaml new file mode 100644 index 0000000000000..2895ce4a1b237 --- /dev/null +++ b/.chloggen/splunkenterprisereceiver-splunkd-info.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: splunkenterprisereceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add splunkd build info metrics + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [user] diff --git a/receiver/splunkenterprisereceiver/documentation.md b/receiver/splunkenterprisereceiver/documentation.md index e866054e56278..00c5477069539 100644 --- a/receiver/splunkenterprisereceiver/documentation.md +++ b/receiver/splunkenterprisereceiver/documentation.md @@ -448,6 +448,22 @@ This is the overall status of the kvstore for the given deployment. | splunk.kvstore.external | Value denoting if the KV store is using an external service. | Any Str | | splunk.kvstore.status.value | The string value of the status returned when reporting on KV store using the introspection endpoint | Any Str | +### splunk.server.info.build + +Gauge tracking splunkd build information. + +| Unit | Metric Type | Value Type | +| ---- | ----------- | ---------- | +| {splunkd_build} | Gauge | Int | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| splunk.host | The name of the splunk host | Any Str | +| splunk.build_info | The build of Splunk. | Any Str | +| splunk.version | The version of Splunk. | Any Str | + ### splunk.server.introspection.queues.current Gauge tracking current length of queue. *Note:** Must be pointed at specific indexer `endpoint` and gathers metrics from only that indexer. diff --git a/receiver/splunkenterprisereceiver/internal/metadata/generated_config.go b/receiver/splunkenterprisereceiver/internal/metadata/generated_config.go index 9daf22b30999d..1f3f3ec78e971 100644 --- a/receiver/splunkenterprisereceiver/internal/metadata/generated_config.go +++ b/receiver/splunkenterprisereceiver/internal/metadata/generated_config.go @@ -56,6 +56,7 @@ type MetricsConfig struct { SplunkSchedulerAvgExecutionLatency MetricConfig `mapstructure:"splunk.scheduler.avg.execution.latency"` SplunkSchedulerAvgRunTime MetricConfig `mapstructure:"splunk.scheduler.avg.run.time"` SplunkSchedulerCompletionRatio MetricConfig `mapstructure:"splunk.scheduler.completion.ratio"` + SplunkServerInfoBuild MetricConfig `mapstructure:"splunk.server.info.build"` SplunkServerIntrospectionQueuesCurrent MetricConfig `mapstructure:"splunk.server.introspection.queues.current"` SplunkServerIntrospectionQueuesCurrentBytes MetricConfig `mapstructure:"splunk.server.introspection.queues.current.bytes"` SplunkServerSearchartifactsAdhoc MetricConfig `mapstructure:"splunk.server.searchartifacts.adhoc"` @@ -156,6 +157,9 @@ func DefaultMetricsConfig() MetricsConfig { SplunkSchedulerCompletionRatio: MetricConfig{ Enabled: true, }, + SplunkServerInfoBuild: MetricConfig{ + Enabled: false, + }, SplunkServerIntrospectionQueuesCurrent: MetricConfig{ Enabled: false, }, diff --git a/receiver/splunkenterprisereceiver/internal/metadata/generated_config_test.go b/receiver/splunkenterprisereceiver/internal/metadata/generated_config_test.go index 9b1dc2b53099f..af6e6a53ba5cb 100644 --- a/receiver/splunkenterprisereceiver/internal/metadata/generated_config_test.go +++ b/receiver/splunkenterprisereceiver/internal/metadata/generated_config_test.go @@ -54,6 +54,7 @@ func TestMetricsBuilderConfig(t *testing.T) { SplunkSchedulerAvgExecutionLatency: MetricConfig{Enabled: true}, SplunkSchedulerAvgRunTime: MetricConfig{Enabled: true}, SplunkSchedulerCompletionRatio: MetricConfig{Enabled: true}, + SplunkServerInfoBuild: MetricConfig{Enabled: true}, SplunkServerIntrospectionQueuesCurrent: MetricConfig{Enabled: true}, SplunkServerIntrospectionQueuesCurrentBytes: MetricConfig{Enabled: true}, SplunkServerSearchartifactsAdhoc: MetricConfig{Enabled: true}, @@ -99,6 +100,7 @@ func TestMetricsBuilderConfig(t *testing.T) { SplunkSchedulerAvgExecutionLatency: MetricConfig{Enabled: false}, SplunkSchedulerAvgRunTime: MetricConfig{Enabled: false}, SplunkSchedulerCompletionRatio: MetricConfig{Enabled: false}, + SplunkServerInfoBuild: MetricConfig{Enabled: false}, SplunkServerIntrospectionQueuesCurrent: MetricConfig{Enabled: false}, SplunkServerIntrospectionQueuesCurrentBytes: MetricConfig{Enabled: false}, SplunkServerSearchartifactsAdhoc: MetricConfig{Enabled: false}, diff --git a/receiver/splunkenterprisereceiver/internal/metadata/generated_metrics.go b/receiver/splunkenterprisereceiver/internal/metadata/generated_metrics.go index 968f9b26bed2b..c95dc1a01682a 100644 --- a/receiver/splunkenterprisereceiver/internal/metadata/generated_metrics.go +++ b/receiver/splunkenterprisereceiver/internal/metadata/generated_metrics.go @@ -1496,6 +1496,59 @@ func newMetricSplunkSchedulerCompletionRatio(cfg MetricConfig) metricSplunkSched return m } +type metricSplunkServerInfoBuild 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 splunk.server.info.build metric with initial data. +func (m *metricSplunkServerInfoBuild) init() { + m.data.SetName("splunk.server.info.build") + m.data.SetDescription("Gauge tracking splunkd build information.") + m.data.SetUnit("{splunkd_build}") + m.data.SetEmptyGauge() + m.data.Gauge().DataPoints().EnsureCapacity(m.capacity) +} + +func (m *metricSplunkServerInfoBuild) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64, splunkHostAttributeValue string, splunkBuildInfoAttributeValue string, splunkVersionAttributeValue string) { + if !m.config.Enabled { + return + } + dp := m.data.Gauge().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) + dp.Attributes().PutStr("splunk.host", splunkHostAttributeValue) + dp.Attributes().PutStr("splunk.build_info", splunkBuildInfoAttributeValue) + dp.Attributes().PutStr("splunk.version", splunkVersionAttributeValue) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricSplunkServerInfoBuild) 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 *metricSplunkServerInfoBuild) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricSplunkServerInfoBuild(cfg MetricConfig) metricSplunkServerInfoBuild { + m := metricSplunkServerInfoBuild{config: cfg} + if cfg.Enabled { + m.data = pmetric.NewMetric() + m.init() + } + return m +} + type metricSplunkServerIntrospectionQueuesCurrent struct { data pmetric.Metric // data buffer for generated metric. config MetricConfig // metric config provided by user. @@ -1992,6 +2045,7 @@ type MetricsBuilder struct { metricSplunkSchedulerAvgExecutionLatency metricSplunkSchedulerAvgExecutionLatency metricSplunkSchedulerAvgRunTime metricSplunkSchedulerAvgRunTime metricSplunkSchedulerCompletionRatio metricSplunkSchedulerCompletionRatio + metricSplunkServerInfoBuild metricSplunkServerInfoBuild metricSplunkServerIntrospectionQueuesCurrent metricSplunkServerIntrospectionQueuesCurrent metricSplunkServerIntrospectionQueuesCurrentBytes metricSplunkServerIntrospectionQueuesCurrentBytes metricSplunkServerSearchartifactsAdhoc metricSplunkServerSearchartifactsAdhoc @@ -2056,6 +2110,7 @@ func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.Settings, opt metricSplunkSchedulerAvgExecutionLatency: newMetricSplunkSchedulerAvgExecutionLatency(mbc.Metrics.SplunkSchedulerAvgExecutionLatency), metricSplunkSchedulerAvgRunTime: newMetricSplunkSchedulerAvgRunTime(mbc.Metrics.SplunkSchedulerAvgRunTime), metricSplunkSchedulerCompletionRatio: newMetricSplunkSchedulerCompletionRatio(mbc.Metrics.SplunkSchedulerCompletionRatio), + metricSplunkServerInfoBuild: newMetricSplunkServerInfoBuild(mbc.Metrics.SplunkServerInfoBuild), metricSplunkServerIntrospectionQueuesCurrent: newMetricSplunkServerIntrospectionQueuesCurrent(mbc.Metrics.SplunkServerIntrospectionQueuesCurrent), metricSplunkServerIntrospectionQueuesCurrentBytes: newMetricSplunkServerIntrospectionQueuesCurrentBytes(mbc.Metrics.SplunkServerIntrospectionQueuesCurrentBytes), metricSplunkServerSearchartifactsAdhoc: newMetricSplunkServerSearchartifactsAdhoc(mbc.Metrics.SplunkServerSearchartifactsAdhoc), @@ -2159,6 +2214,7 @@ func (mb *MetricsBuilder) EmitForResource(options ...ResourceMetricsOption) { mb.metricSplunkSchedulerAvgExecutionLatency.emit(ils.Metrics()) mb.metricSplunkSchedulerAvgRunTime.emit(ils.Metrics()) mb.metricSplunkSchedulerCompletionRatio.emit(ils.Metrics()) + mb.metricSplunkServerInfoBuild.emit(ils.Metrics()) mb.metricSplunkServerIntrospectionQueuesCurrent.emit(ils.Metrics()) mb.metricSplunkServerIntrospectionQueuesCurrentBytes.emit(ils.Metrics()) mb.metricSplunkServerSearchartifactsAdhoc.emit(ils.Metrics()) @@ -2334,6 +2390,11 @@ func (mb *MetricsBuilder) RecordSplunkSchedulerCompletionRatioDataPoint(ts pcomm mb.metricSplunkSchedulerCompletionRatio.recordDataPoint(mb.startTime, ts, val, splunkHostAttributeValue) } +// RecordSplunkServerInfoBuildDataPoint adds a data point to splunk.server.info.build metric. +func (mb *MetricsBuilder) RecordSplunkServerInfoBuildDataPoint(ts pcommon.Timestamp, val int64, splunkHostAttributeValue string, splunkBuildInfoAttributeValue string, splunkVersionAttributeValue string) { + mb.metricSplunkServerInfoBuild.recordDataPoint(mb.startTime, ts, val, splunkHostAttributeValue, splunkBuildInfoAttributeValue, splunkVersionAttributeValue) +} + // RecordSplunkServerIntrospectionQueuesCurrentDataPoint adds a data point to splunk.server.introspection.queues.current metric. func (mb *MetricsBuilder) RecordSplunkServerIntrospectionQueuesCurrentDataPoint(ts pcommon.Timestamp, val int64, splunkQueueNameAttributeValue string) { mb.metricSplunkServerIntrospectionQueuesCurrent.recordDataPoint(mb.startTime, ts, val, splunkQueueNameAttributeValue) diff --git a/receiver/splunkenterprisereceiver/internal/metadata/generated_metrics_test.go b/receiver/splunkenterprisereceiver/internal/metadata/generated_metrics_test.go index b2c7ede854dce..692a202eca4ae 100644 --- a/receiver/splunkenterprisereceiver/internal/metadata/generated_metrics_test.go +++ b/receiver/splunkenterprisereceiver/internal/metadata/generated_metrics_test.go @@ -164,6 +164,9 @@ func TestMetricsBuilder(t *testing.T) { allMetricsCount++ mb.RecordSplunkSchedulerCompletionRatioDataPoint(ts, 1, "splunk.host-val") + allMetricsCount++ + mb.RecordSplunkServerInfoBuildDataPoint(ts, 1, "splunk.host-val", "splunk.build_info-val", "splunk.version-val") + allMetricsCount++ mb.RecordSplunkServerIntrospectionQueuesCurrentDataPoint(ts, 1, "splunk.queue.name-val") @@ -667,6 +670,27 @@ func TestMetricsBuilder(t *testing.T) { attrVal, ok := dp.Attributes().Get("splunk.host") assert.True(t, ok) assert.EqualValues(t, "splunk.host-val", attrVal.Str()) + case "splunk.server.info.build": + assert.False(t, validatedMetrics["splunk.server.info.build"], "Found a duplicate in the metrics slice: splunk.server.info.build") + validatedMetrics["splunk.server.info.build"] = true + assert.Equal(t, pmetric.MetricTypeGauge, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Gauge().DataPoints().Len()) + assert.Equal(t, "Gauge tracking splunkd build information.", ms.At(i).Description()) + assert.Equal(t, "{splunkd_build}", 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("splunk.host") + assert.True(t, ok) + assert.EqualValues(t, "splunk.host-val", attrVal.Str()) + attrVal, ok = dp.Attributes().Get("splunk.build_info") + assert.True(t, ok) + assert.EqualValues(t, "splunk.build_info-val", attrVal.Str()) + attrVal, ok = dp.Attributes().Get("splunk.version") + assert.True(t, ok) + assert.EqualValues(t, "splunk.version-val", attrVal.Str()) case "splunk.server.introspection.queues.current": assert.False(t, validatedMetrics["splunk.server.introspection.queues.current"], "Found a duplicate in the metrics slice: splunk.server.introspection.queues.current") validatedMetrics["splunk.server.introspection.queues.current"] = true diff --git a/receiver/splunkenterprisereceiver/internal/metadata/testdata/config.yaml b/receiver/splunkenterprisereceiver/internal/metadata/testdata/config.yaml index 449cd89b22555..3a63e723aa1f3 100644 --- a/receiver/splunkenterprisereceiver/internal/metadata/testdata/config.yaml +++ b/receiver/splunkenterprisereceiver/internal/metadata/testdata/config.yaml @@ -59,6 +59,8 @@ all_set: enabled: true splunk.scheduler.completion.ratio: enabled: true + splunk.server.info.build: + enabled: true splunk.server.introspection.queues.current: enabled: true splunk.server.introspection.queues.current.bytes: @@ -137,6 +139,8 @@ none_set: enabled: false splunk.scheduler.completion.ratio: enabled: false + splunk.server.info.build: + enabled: false splunk.server.introspection.queues.current: enabled: false splunk.server.introspection.queues.current.bytes: diff --git a/receiver/splunkenterprisereceiver/metadata.yaml b/receiver/splunkenterprisereceiver/metadata.yaml index e6329f500e0d0..be8fcddaaba2f 100644 --- a/receiver/splunkenterprisereceiver/metadata.yaml +++ b/receiver/splunkenterprisereceiver/metadata.yaml @@ -36,6 +36,12 @@ attributes: splunk.kvstore.storage.engine: description: The backend storage used by the KV store. type: string + splunk.build_info: + description: The build of Splunk. + type: string + splunk.version: + description: The version of Splunk. + type: string metrics: splunk.license.index.usage: @@ -324,6 +330,15 @@ metrics: aggregation_temporality: cumulative value_type: int attributes: [splunk.host] + splunk.server.info.build: + enabled: false + description: Gauge tracking splunkd build information. + unit: "{splunkd_build}" + gauge: + monotonic: false + aggregation_temporality: cumulative + value_type: int + attributes: [splunk.host, splunk.build_info, splunk.version] tests: config: diff --git a/receiver/splunkenterprisereceiver/scraper.go b/receiver/splunkenterprisereceiver/scraper.go index 7d55727e71e4c..e9d4dbb600983 100644 --- a/receiver/splunkenterprisereceiver/scraper.go +++ b/receiver/splunkenterprisereceiver/scraper.go @@ -1666,7 +1666,7 @@ func (s *splunkScraper) scrapeSearchArtifacts(ctx context.Context, now pcommon.T for _, f := range da.Entries { - if !s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsAdhoc.Enabled { + if s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsAdhoc.Enabled { adhocCount, err := strconv.ParseInt(f.Content.AdhocCount, 10, 64) if err != nil { errs <- err @@ -1674,7 +1674,7 @@ func (s *splunkScraper) scrapeSearchArtifacts(ctx context.Context, now pcommon.T s.mb.RecordSplunkServerSearchartifactsAdhocDataPoint(now, adhocCount, s.conf.SHEndpoint.Endpoint) } - if !s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsScheduled.Enabled { + if s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsScheduled.Enabled { scheduledCount, err := strconv.ParseInt(f.Content.ScheduledCount, 10, 64) if err != nil { errs <- err @@ -1682,7 +1682,7 @@ func (s *splunkScraper) scrapeSearchArtifacts(ctx context.Context, now pcommon.T s.mb.RecordSplunkServerSearchartifactsScheduledDataPoint(now, scheduledCount, s.conf.SHEndpoint.Endpoint) } - if !s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsCompleted.Enabled { + if s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsCompleted.Enabled { completedCount, err := strconv.ParseInt(f.Content.CompletedCount, 10, 64) if err != nil { errs <- err @@ -1690,7 +1690,7 @@ func (s *splunkScraper) scrapeSearchArtifacts(ctx context.Context, now pcommon.T s.mb.RecordSplunkServerSearchartifactsCompletedDataPoint(now, completedCount, s.conf.SHEndpoint.Endpoint) } - if !s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsIncomplete.Enabled { + if s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsIncomplete.Enabled { incompleCount, err := strconv.ParseInt(f.Content.IncompleCount, 10, 64) if err != nil { errs <- err @@ -1698,7 +1698,7 @@ func (s *splunkScraper) scrapeSearchArtifacts(ctx context.Context, now pcommon.T s.mb.RecordSplunkServerSearchartifactsIncompleteDataPoint(now, incompleCount, s.conf.SHEndpoint.Endpoint) } - if !s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsInvalid.Enabled { + if s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsInvalid.Enabled { invalidCount, err := strconv.ParseInt(f.Content.InvalidCount, 10, 64) if err != nil { errs <- err @@ -1706,7 +1706,7 @@ func (s *splunkScraper) scrapeSearchArtifacts(ctx context.Context, now pcommon.T s.mb.RecordSplunkServerSearchartifactsInvalidDataPoint(now, invalidCount, s.conf.SHEndpoint.Endpoint) } - if !s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsSavedsearches.Enabled { + if s.conf.MetricsBuilderConfig.Metrics.SplunkServerSearchartifactsSavedsearches.Enabled { savedSearchesCount, err := strconv.ParseInt(f.Content.SsCount, 10, 64) if err != nil { errs <- err @@ -1715,3 +1715,43 @@ func (s *splunkScraper) scrapeSearchArtifacts(ctx context.Context, now pcommon.T } } } + +// Scrape info endpoints +func (s *splunkScraper) scrapeInfo(ctx context.Context, now pcommon.Timestamp, errs chan error) { + + ctx = context.WithValue(ctx) + var info Info + + ept := apiDict[`SplunkInfo`] + + req, err := s.splunkClient.createAPIRequest(ctx, ept) + if err != nil { + errs <- err + return + } + + res, err := s.splunkClient.makeRequest(req) + if err != nil { + errs <- err + return + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + errs <- err + return + } + err = json.Unmarshal(body, &da) + if err != nil { + errs <- err + return + } + + for _, f := range info.Entries { + + if s.conf.MetricsBuilderConfig.Metrics.SplunkServerInfo.Enabled { + s.mb.RecordSplunkServerInfoDataPoint(now, 1, info.Host, f.Content.Build, f.Content.Version) + } + } +} diff --git a/receiver/splunkenterprisereceiver/search_result.go b/receiver/splunkenterprisereceiver/search_result.go index 69212c4de08d3..ff1fb7ddfeff9 100644 --- a/receiver/splunkenterprisereceiver/search_result.go +++ b/receiver/splunkenterprisereceiver/search_result.go @@ -20,6 +20,7 @@ var searchDict = map[string]string{ } var apiDict = map[string]string{ + `SplunkInfo`: `/services/server/info?output_mode=json`, `SplunkIndexerThroughput`: `/services/server/introspection/indexer?output_mode=json`, `SplunkDataIndexesExtended`: `/services/data/indexes-extended?output_mode=json&count=-1`, `SplunkIntrospectionQueues`: `/services/server/introspection/queues?output_mode=json&count=-1`, @@ -153,3 +154,18 @@ type DispatchArtifactContent struct { IncompleCount string `json:"incomple_count"` InvalidCount string `json:"invalid_count"` } + +// '/services/server/info' +type Info struct { + Host string `json:"origin"` + Entries []InfoEntry `json:"entry"` +} + +type InfoEntry struct { + Content InfoContent `json:"content"` +} + +type InfoContent struct { + Build string `json:"build"` + Version string `json:"version"` +}