From 7a7e7934f42b9ef3e7b50a3e92cf0ade2b44aef6 Mon Sep 17 00:00:00 2001 From: Keval Bhogayata <90185475+bhogayatakb@users.noreply.github.com> Date: Sat, 25 May 2024 14:34:35 +0530 Subject: [PATCH] Metric/postgresql connections (#62) --- receiver/postgresqlreceiver/client.go | 9 +++ receiver/postgresqlreceiver/documentation.md | 10 +++- .../internal/metadata/generated_config.go | 4 ++ .../metadata/generated_config_test.go | 2 + .../internal/metadata/generated_metrics.go | 57 +++++++++++++++++++ .../metadata/generated_metrics_test.go | 16 ++++++ .../internal/metadata/testdata/config.yaml | 4 ++ receiver/postgresqlreceiver/metadata.yaml | 11 +++- receiver/postgresqlreceiver/scraper.go | 19 ++++++- 9 files changed, 127 insertions(+), 5 deletions(-) diff --git a/receiver/postgresqlreceiver/client.go b/receiver/postgresqlreceiver/client.go index 7b0daa527789..d1fc5da3808f 100644 --- a/receiver/postgresqlreceiver/client.go +++ b/receiver/postgresqlreceiver/client.go @@ -45,6 +45,7 @@ type client interface { getLatestWalAgeSeconds(ctx context.Context) (int64, error) getMaxConnections(ctx context.Context) (int64, error) getIndexStats(ctx context.Context, database string) (map[indexIdentifer]indexStat, error) + getActiveConnections(ctx context.Context) (int64, error) listDatabases(ctx context.Context) ([]string, error) } @@ -442,6 +443,14 @@ func (c *postgreSQLClient) getMaxConnections(ctx context.Context) (int64, error) return maxConns, err } +func (c *postgreSQLClient) getActiveConnections(ctx context.Context) (int64, error) { + query := `SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'active';` + row := c.client.QueryRowContext(ctx, query) + var activeConns int64 + err := row.Scan(&activeConns) + return activeConns, err +} + type replicationStats struct { clientAddr string pendingBytes int64 diff --git a/receiver/postgresqlreceiver/documentation.md b/receiver/postgresqlreceiver/documentation.md index 24d8c2fef1fd..1d1106476412 100644 --- a/receiver/postgresqlreceiver/documentation.md +++ b/receiver/postgresqlreceiver/documentation.md @@ -100,6 +100,14 @@ The number of commits. | ---- | ----------- | ---------- | ----------------------- | --------- | | 1 | Sum | Int | Cumulative | true | +### postgresql.connection.count + +The number of active connections to this database. If DBM is enabled, this metric is tagged with state, app, db and user + +| Unit | Metric Type | Value Type | +| ---- | ----------- | ---------- | +| {connection} | Gauge | Int | + ### postgresql.connection.max Configured maximum number of client connections allowed @@ -229,7 +237,7 @@ This metric requires WAL to be enabled with at least one replica. Time between flushing recent WAL locally and receiving notification that the standby server has completed an operation with it. -This metric requires WAL to be enabled with at least one replica. +This metric requires WAL to be enabled with at least one replica. | Unit | Metric Type | Value Type | diff --git a/receiver/postgresqlreceiver/internal/metadata/generated_config.go b/receiver/postgresqlreceiver/internal/metadata/generated_config.go index 797b7d7f6eae..ae01cc2362e3 100644 --- a/receiver/postgresqlreceiver/internal/metadata/generated_config.go +++ b/receiver/postgresqlreceiver/internal/metadata/generated_config.go @@ -33,6 +33,7 @@ type MetricsConfig struct { PostgresqlBgwriterMaxwritten MetricConfig `mapstructure:"postgresql.bgwriter.maxwritten"` PostgresqlBlocksRead MetricConfig `mapstructure:"postgresql.blocks_read"` PostgresqlCommits MetricConfig `mapstructure:"postgresql.commits"` + PostgresqlConnectionCount MetricConfig `mapstructure:"postgresql.connection.count"` PostgresqlConnectionMax MetricConfig `mapstructure:"postgresql.connection.max"` PostgresqlDatabaseCount MetricConfig `mapstructure:"postgresql.database.count"` PostgresqlDbSize MetricConfig `mapstructure:"postgresql.db_size"` @@ -78,6 +79,9 @@ func DefaultMetricsConfig() MetricsConfig { PostgresqlCommits: MetricConfig{ Enabled: true, }, + PostgresqlConnectionCount: MetricConfig{ + Enabled: true, + }, PostgresqlConnectionMax: MetricConfig{ Enabled: true, }, diff --git a/receiver/postgresqlreceiver/internal/metadata/generated_config_test.go b/receiver/postgresqlreceiver/internal/metadata/generated_config_test.go index 670931ddf2ec..0b2131b1e0e4 100644 --- a/receiver/postgresqlreceiver/internal/metadata/generated_config_test.go +++ b/receiver/postgresqlreceiver/internal/metadata/generated_config_test.go @@ -34,6 +34,7 @@ func TestMetricsBuilderConfig(t *testing.T) { PostgresqlBgwriterMaxwritten: MetricConfig{Enabled: true}, PostgresqlBlocksRead: MetricConfig{Enabled: true}, PostgresqlCommits: MetricConfig{Enabled: true}, + PostgresqlConnectionCount: MetricConfig{Enabled: true}, PostgresqlConnectionMax: MetricConfig{Enabled: true}, PostgresqlDatabaseCount: MetricConfig{Enabled: true}, PostgresqlDbSize: MetricConfig{Enabled: true}, @@ -71,6 +72,7 @@ func TestMetricsBuilderConfig(t *testing.T) { PostgresqlBgwriterMaxwritten: MetricConfig{Enabled: false}, PostgresqlBlocksRead: MetricConfig{Enabled: false}, PostgresqlCommits: MetricConfig{Enabled: false}, + PostgresqlConnectionCount: MetricConfig{Enabled: false}, PostgresqlConnectionMax: MetricConfig{Enabled: false}, PostgresqlDatabaseCount: MetricConfig{Enabled: false}, PostgresqlDbSize: MetricConfig{Enabled: false}, diff --git a/receiver/postgresqlreceiver/internal/metadata/generated_metrics.go b/receiver/postgresqlreceiver/internal/metadata/generated_metrics.go index d2e265522c58..a47c3f1c8ab9 100644 --- a/receiver/postgresqlreceiver/internal/metadata/generated_metrics.go +++ b/receiver/postgresqlreceiver/internal/metadata/generated_metrics.go @@ -653,6 +653,55 @@ func newMetricPostgresqlCommits(cfg MetricConfig) metricPostgresqlCommits { return m } +type metricPostgresqlConnectionCount 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 postgresql.connection.count metric with initial data. +func (m *metricPostgresqlConnectionCount) init() { + m.data.SetName("postgresql.connection.count") + m.data.SetDescription("The number of active connections to this database. If DBM is enabled, this metric is tagged with state, app, db and user") + m.data.SetUnit("{connection}") + m.data.SetEmptyGauge() +} + +func (m *metricPostgresqlConnectionCount) recordDataPoint(start pcommon.Timestamp, ts pcommon.Timestamp, val int64) { + if !m.config.Enabled { + return + } + dp := m.data.Gauge().DataPoints().AppendEmpty() + dp.SetStartTimestamp(start) + dp.SetTimestamp(ts) + dp.SetIntValue(val) +} + +// updateCapacity saves max length of data point slices that will be used for the slice capacity. +func (m *metricPostgresqlConnectionCount) 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 *metricPostgresqlConnectionCount) emit(metrics pmetric.MetricSlice) { + if m.config.Enabled && m.data.Gauge().DataPoints().Len() > 0 { + m.updateCapacity() + m.data.MoveTo(metrics.AppendEmpty()) + m.init() + } +} + +func newMetricPostgresqlConnectionCount(cfg MetricConfig) metricPostgresqlConnectionCount { + m := metricPostgresqlConnectionCount{config: cfg} + if cfg.Enabled { + m.data = pmetric.NewMetric() + m.init() + } + return m +} + type metricPostgresqlConnectionMax struct { data pmetric.Metric // data buffer for generated metric. config MetricConfig // metric config provided by user. @@ -1535,6 +1584,7 @@ type MetricsBuilder struct { metricPostgresqlBgwriterMaxwritten metricPostgresqlBgwriterMaxwritten metricPostgresqlBlocksRead metricPostgresqlBlocksRead metricPostgresqlCommits metricPostgresqlCommits + metricPostgresqlConnectionCount metricPostgresqlConnectionCount metricPostgresqlConnectionMax metricPostgresqlConnectionMax metricPostgresqlDatabaseCount metricPostgresqlDatabaseCount metricPostgresqlDbSize metricPostgresqlDbSize @@ -1578,6 +1628,7 @@ func NewMetricsBuilder(mbc MetricsBuilderConfig, settings receiver.CreateSetting metricPostgresqlBgwriterMaxwritten: newMetricPostgresqlBgwriterMaxwritten(mbc.Metrics.PostgresqlBgwriterMaxwritten), metricPostgresqlBlocksRead: newMetricPostgresqlBlocksRead(mbc.Metrics.PostgresqlBlocksRead), metricPostgresqlCommits: newMetricPostgresqlCommits(mbc.Metrics.PostgresqlCommits), + metricPostgresqlConnectionCount: newMetricPostgresqlConnectionCount(mbc.Metrics.PostgresqlConnectionCount), metricPostgresqlConnectionMax: newMetricPostgresqlConnectionMax(mbc.Metrics.PostgresqlConnectionMax), metricPostgresqlDatabaseCount: newMetricPostgresqlDatabaseCount(mbc.Metrics.PostgresqlDatabaseCount), metricPostgresqlDbSize: newMetricPostgresqlDbSize(mbc.Metrics.PostgresqlDbSize), @@ -1664,6 +1715,7 @@ func (mb *MetricsBuilder) EmitForResource(rmo ...ResourceMetricsOption) { mb.metricPostgresqlBgwriterMaxwritten.emit(ils.Metrics()) mb.metricPostgresqlBlocksRead.emit(ils.Metrics()) mb.metricPostgresqlCommits.emit(ils.Metrics()) + mb.metricPostgresqlConnectionCount.emit(ils.Metrics()) mb.metricPostgresqlConnectionMax.emit(ils.Metrics()) mb.metricPostgresqlDatabaseCount.emit(ils.Metrics()) mb.metricPostgresqlDbSize.emit(ils.Metrics()) @@ -1741,6 +1793,11 @@ func (mb *MetricsBuilder) RecordPostgresqlCommitsDataPoint(ts pcommon.Timestamp, mb.metricPostgresqlCommits.recordDataPoint(mb.startTime, ts, val) } +// RecordPostgresqlConnectionCountDataPoint adds a data point to postgresql.connection.count metric. +func (mb *MetricsBuilder) RecordPostgresqlConnectionCountDataPoint(ts pcommon.Timestamp, val int64) { + mb.metricPostgresqlConnectionCount.recordDataPoint(mb.startTime, ts, val) +} + // RecordPostgresqlConnectionMaxDataPoint adds a data point to postgresql.connection.max metric. func (mb *MetricsBuilder) RecordPostgresqlConnectionMaxDataPoint(ts pcommon.Timestamp, val int64) { mb.metricPostgresqlConnectionMax.recordDataPoint(mb.startTime, ts, val) diff --git a/receiver/postgresqlreceiver/internal/metadata/generated_metrics_test.go b/receiver/postgresqlreceiver/internal/metadata/generated_metrics_test.go index ffbad5ec9a66..ce00d7833db0 100644 --- a/receiver/postgresqlreceiver/internal/metadata/generated_metrics_test.go +++ b/receiver/postgresqlreceiver/internal/metadata/generated_metrics_test.go @@ -86,6 +86,10 @@ func TestMetricsBuilder(t *testing.T) { allMetricsCount++ mb.RecordPostgresqlCommitsDataPoint(ts, 1) + defaultMetricsCount++ + allMetricsCount++ + mb.RecordPostgresqlConnectionCountDataPoint(ts, 1) + defaultMetricsCount++ allMetricsCount++ mb.RecordPostgresqlConnectionMaxDataPoint(ts, 1) @@ -301,6 +305,18 @@ func TestMetricsBuilder(t *testing.T) { assert.Equal(t, ts, dp.Timestamp()) assert.Equal(t, pmetric.NumberDataPointValueTypeInt, dp.ValueType()) assert.Equal(t, int64(1), dp.IntValue()) + case "postgresql.connection.count": + assert.False(t, validatedMetrics["postgresql.connection.count"], "Found a duplicate in the metrics slice: postgresql.connection.count") + validatedMetrics["postgresql.connection.count"] = true + assert.Equal(t, pmetric.MetricTypeGauge, ms.At(i).Type()) + assert.Equal(t, 1, ms.At(i).Gauge().DataPoints().Len()) + assert.Equal(t, "The number of active connections to this database. If DBM is enabled, this metric is tagged with state, app, db and user", ms.At(i).Description()) + assert.Equal(t, "{connection}", 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()) case "postgresql.connection.max": assert.False(t, validatedMetrics["postgresql.connection.max"], "Found a duplicate in the metrics slice: postgresql.connection.max") validatedMetrics["postgresql.connection.max"] = true diff --git a/receiver/postgresqlreceiver/internal/metadata/testdata/config.yaml b/receiver/postgresqlreceiver/internal/metadata/testdata/config.yaml index ba6335d13518..b65cf6e850cd 100644 --- a/receiver/postgresqlreceiver/internal/metadata/testdata/config.yaml +++ b/receiver/postgresqlreceiver/internal/metadata/testdata/config.yaml @@ -17,6 +17,8 @@ all_set: enabled: true postgresql.commits: enabled: true + postgresql.connection.count: + enabled: true postgresql.connection.max: enabled: true postgresql.database.count: @@ -76,6 +78,8 @@ none_set: enabled: false postgresql.commits: enabled: false + postgresql.connection.count: + enabled: false postgresql.connection.max: enabled: false postgresql.database.count: diff --git a/receiver/postgresqlreceiver/metadata.yaml b/receiver/postgresqlreceiver/metadata.yaml index 14381ce46999..44b9ff7b910a 100644 --- a/receiver/postgresqlreceiver/metadata.yaml +++ b/receiver/postgresqlreceiver/metadata.yaml @@ -11,7 +11,7 @@ status: resource_attributes: postgresql.database.name: description: The name of the database. - enabled: true + enabled: true type: string postgresql.table.name: description: The schema name followed by the table name. @@ -280,4 +280,11 @@ metrics: gauge: value_type: int extended_documentation: | - This metric requires WAL to be enabled with at least one replica. + This metric requires WAL to be enabled with at least one replica. + postgresql.connection.count: + enabled: true + description: The number of active connections to this database. If DBM is enabled, + this metric is tagged with state, app, db and user + unit: '{connection}' + gauge: + value_type: int \ No newline at end of file diff --git a/receiver/postgresqlreceiver/scraper.go b/receiver/postgresqlreceiver/scraper.go index 62f6fbf77025..8a0b8282c194 100644 --- a/receiver/postgresqlreceiver/scraper.go +++ b/receiver/postgresqlreceiver/scraper.go @@ -132,11 +132,12 @@ func (p *postgreSQLScraper) scrape(ctx context.Context) (pmetric.Metrics, error) p.collectWalAge(ctx, now, listClient, &errs) p.collectReplicationStats(ctx, now, listClient, &errs) p.collectMaxConnections(ctx, now, listClient, &errs) - + p.collectActiveConnections(ctx, now, listClient, &errs) + rb := p.mb.NewResourceBuilder() rb.SetPostgresqlDatabaseName("N/A") p.mb.EmitForResource(metadata.WithResource(rb.Emit())) - + return p.mb.Emit(), errs.combine() } @@ -284,6 +285,20 @@ func (p *postgreSQLScraper) collectMaxConnections( p.mb.RecordPostgresqlConnectionMaxDataPoint(now, mc) } +func (p *postgreSQLScraper) collectActiveConnections( + ctx context.Context, + now pcommon.Timestamp, + client client, + errs *errsMux, +) { + ac, err := client.getActiveConnections(ctx) + if err != nil { + errs.addPartial(err) + return + } + p.mb.RecordPostgresqlConnectionCountDataPoint(now, ac) +} + func (p *postgreSQLScraper) collectReplicationStats( ctx context.Context, now pcommon.Timestamp,