Skip to content

Commit d53996f

Browse files
committed
Add cloud_provider block to database_observability.postgres
Copy over functionality from `database_observability.mysql` to populate cloud provider info in `connection_info` collector for Postgres.
1 parent e1322d9 commit d53996f

File tree

5 files changed

+115
-27
lines changed

5 files changed

+115
-27
lines changed

docs/sources/reference/components/database_observability/database_observability.postgres.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,39 @@ You can use the following blocks with `database_observability.postgres`:
4848

4949
| Block | Description | Required |
5050
|------------------------------------|---------------------------------------------------|----------|
51+
| [`cloud_provider`][cloud_provider] | Provide Cloud Provider information. | no |
52+
| `cloud_provider` > [`aws`][aws] | Provide AWS database host information. | no |
5153
| [`query_details`][query_details] | Configure the queries collector. | no |
5254
| [`query_samples`][query_samples] | Configure the query samples collector. | no |
5355
| [`schema_details`][schema_details] | Configure the schema and table details collector. | no |
5456
| [`explain_plans`][explain_plans] | Configure the explain plans collector. | no |
5557

58+
The > symbol indicates deeper levels of nesting.
59+
For example, `cloud_provider` > `aws` refers to a `aws` block defined inside an `cloud_provider` block.
60+
61+
[cloud_provider]: #cloud_provider
62+
[aws]: #aws
5663
[query_details]: #query_details
5764
[query_samples]: #query_samples
5865
[schema_details]: #schema_details
5966
[explain_plans]: #explain_plans
6067

68+
### `cloud_provider`
69+
70+
The `cloud_provider` block has no attributes.
71+
It contains zero or more [`aws`][aws] blocks.
72+
You use the `cloud_provider` block to provide information related to the cloud provider that hosts the database under observation.
73+
This information is appended as labels to the collected metrics.
74+
The labels make it easier for you to filter and group your metrics.
75+
76+
### `aws`
77+
78+
The `aws` block supplies the [ARN](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html) identifier for the database being monitored.
79+
80+
| Name | Type | Description | Default | Required |
81+
|-------|----------|---------------------------------------------------------|---------|----------|
82+
| `arn` | `string` | The ARN associated with the database under observation. | | yes |
83+
6184
### `query_details`
6285

6386
| Name | Type | Description | Default | Required |
@@ -96,6 +119,12 @@ database_observability.postgres "orders_db" {
96119
data_source_name = "postgres://user:pass@localhost:5432/mydb"
97120
forward_to = [loki.write.logs_service.receiver]
98121
enable_collectors = ["query_details", "query_samples", "schema_details"]
122+
123+
cloud_provider {
124+
aws {
125+
arn = "your-rds-db-arn"
126+
}
127+
}
99128
}
100129
101130
prometheus.scrape "orders_db" {

internal/component/database_observability/mysql/collector/connection_info.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import (
77
"strings"
88

99
"github.com/go-sql-driver/mysql"
10+
"github.com/grafana/alloy/internal/component/database_observability"
1011
"github.com/prometheus/client_golang/prometheus"
1112
"go.uber.org/atomic"
12-
13-
"github.com/grafana/alloy/internal/component/database_observability"
1413
)
1514

1615
const ConnectionInfoName = "connection_info"

internal/component/database_observability/postgres/collector/connection_info.go

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"regexp"
66
"strings"
77

8+
"github.com/grafana/alloy/internal/component/database_observability"
89
"github.com/prometheus/client_golang/prometheus"
910
"go.uber.org/atomic"
1011
)
@@ -22,13 +23,15 @@ type ConnectionInfoArguments struct {
2223
DSN string
2324
Registry *prometheus.Registry
2425
EngineVersion string
26+
CloudProvider *database_observability.CloudProvider
2527
}
2628

2729
type ConnectionInfo struct {
2830
DSN string
2931
Registry *prometheus.Registry
3032
EngineVersion string
3133
InfoMetric *prometheus.GaugeVec
34+
CloudProvider *database_observability.CloudProvider
3235

3336
running *atomic.Bool
3437
}
@@ -38,7 +41,7 @@ func NewConnectionInfo(args ConnectionInfoArguments) (*ConnectionInfo, error) {
3841
Namespace: "database_observability",
3942
Name: "connection_info",
4043
Help: "Information about the connection",
41-
}, []string{"provider_name", "provider_region", "db_instance_identifier", "engine", "engine_version"})
44+
}, []string{"provider_name", "provider_region", "provider_account", "db_instance_identifier", "engine", "engine_version"})
4245

4346
args.Registry.MustRegister(infoMetric)
4447

@@ -47,6 +50,7 @@ func NewConnectionInfo(args ConnectionInfoArguments) (*ConnectionInfo, error) {
4750
Registry: args.Registry,
4851
EngineVersion: args.EngineVersion,
4952
InfoMetric: infoMetric,
53+
CloudProvider: args.CloudProvider,
5054
running: &atomic.Bool{},
5155
}, nil
5256
}
@@ -56,34 +60,45 @@ func (c *ConnectionInfo) Name() string {
5660
}
5761

5862
func (c *ConnectionInfo) Start(ctx context.Context) error {
59-
c.running.Store(true)
60-
6163
var (
6264
providerName = "unknown"
6365
providerRegion = "unknown"
66+
providerAccount = "unknown"
6467
dbInstanceIdentifier = "unknown"
6568
engine = "postgres"
6669
engineVersion = "unknown"
6770
)
6871

69-
parts, err := ParseURL(c.DSN)
70-
if err != nil {
71-
return err
72-
}
73-
74-
if host, ok := parts["host"]; ok {
75-
if strings.HasSuffix(host, "rds.amazonaws.com") {
72+
if c.CloudProvider != nil {
73+
if c.CloudProvider.AWS != nil {
7674
providerName = "aws"
77-
matches := rdsRegex.FindStringSubmatch(host)
78-
if len(matches) > 3 {
79-
dbInstanceIdentifier = matches[1]
80-
providerRegion = matches[3]
75+
providerAccount = c.CloudProvider.AWS.ARN.AccountID
76+
providerRegion = c.CloudProvider.AWS.ARN.Region
77+
78+
// We only support RDS database for now. Resource types and ARN formats are documented at: https://docs.aws.amazon.com/service-authorization/latest/reference/list_amazonrds.html#amazonrds-resources-for-iam-policies
79+
if resource := c.CloudProvider.AWS.ARN.Resource; strings.HasPrefix(resource, "db:") {
80+
dbInstanceIdentifier = strings.TrimPrefix(resource, "db:")
8181
}
82-
} else if strings.HasSuffix(host, "postgres.database.azure.com") {
83-
providerName = "azure"
84-
matches := azureRegex.FindStringSubmatch(host)
85-
if len(matches) > 1 {
86-
dbInstanceIdentifier = matches[1]
82+
}
83+
} else {
84+
parts, err := ParseURL(c.DSN)
85+
if err != nil {
86+
return err
87+
}
88+
if host, ok := parts["host"]; ok {
89+
if strings.HasSuffix(host, "rds.amazonaws.com") {
90+
providerName = "aws"
91+
matches := rdsRegex.FindStringSubmatch(host)
92+
if len(matches) > 3 {
93+
dbInstanceIdentifier = matches[1]
94+
providerRegion = matches[3]
95+
}
96+
} else if strings.HasSuffix(host, "postgres.database.azure.com") {
97+
providerName = "azure"
98+
matches := azureRegex.FindStringSubmatch(host)
99+
if len(matches) > 1 {
100+
dbInstanceIdentifier = matches[1]
101+
}
87102
}
88103
}
89104
}
@@ -93,7 +108,9 @@ func (c *ConnectionInfo) Start(ctx context.Context) error {
93108
engineVersion = matches[1]
94109
}
95110

96-
c.InfoMetric.WithLabelValues(providerName, providerRegion, dbInstanceIdentifier, engine, engineVersion).Set(1)
111+
c.running.Store(true)
112+
113+
c.InfoMetric.WithLabelValues(providerName, providerRegion, providerAccount, dbInstanceIdentifier, engine, engineVersion).Set(1)
97114
return nil
98115
}
99116

internal/component/database_observability/postgres/collector/connection_info_test.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import (
55
"strings"
66
"testing"
77

8+
"github.com/aws/aws-sdk-go-v2/aws/arn"
89
"github.com/prometheus/client_golang/prometheus"
910
"github.com/prometheus/client_golang/prometheus/testutil"
1011
"github.com/stretchr/testify/require"
1112
"go.uber.org/goleak"
13+
14+
"github.com/grafana/alloy/internal/component/database_observability"
1215
)
1316

1417
func TestConnectionInfo(t *testing.T) {
@@ -17,32 +20,48 @@ func TestConnectionInfo(t *testing.T) {
1720
const baseExpectedMetrics = `
1821
# HELP database_observability_connection_info Information about the connection
1922
# TYPE database_observability_connection_info gauge
20-
database_observability_connection_info{db_instance_identifier="%s",engine="%s",engine_version="%s",provider_name="%s",provider_region="%s"} 1
23+
database_observability_connection_info{db_instance_identifier="%s",engine="%s",engine_version="%s",provider_account="%s",provider_name="%s",provider_region="%s"} 1
2124
`
2225

2326
testCases := []struct {
2427
name string
2528
dsn string
2629
engineVersion string
30+
cloudProvider *database_observability.CloudProvider
2731
expectedMetrics string
2832
}{
2933
{
3034
name: "generic dsn",
3135
dsn: "postgres://user:pass@localhost:5432/mydb",
3236
engineVersion: "15.4",
33-
expectedMetrics: fmt.Sprintf(baseExpectedMetrics, "unknown", "postgres", "15.4", "unknown", "unknown"),
37+
expectedMetrics: fmt.Sprintf(baseExpectedMetrics, "unknown", "postgres", "15.4", "unknown", "unknown", "unknown"),
3438
},
3539
{
3640
name: "AWS/RDS dsn",
3741
dsn: "postgres://user:[email protected]:5432/mydb",
3842
engineVersion: "15.4",
39-
expectedMetrics: fmt.Sprintf(baseExpectedMetrics, "products-db", "postgres", "15.4", "aws", "us-east-1"),
43+
expectedMetrics: fmt.Sprintf(baseExpectedMetrics, "products-db", "postgres", "15.4", "unknown", "aws", "us-east-1"),
44+
},
45+
{
46+
name: "AWS/RDS dsn with cloud provider info supplied",
47+
dsn: "postgres://user:[email protected]:5432/mydb",
48+
engineVersion: "15.4",
49+
cloudProvider: &database_observability.CloudProvider{
50+
AWS: &database_observability.AWSCloudProviderInfo{
51+
ARN: arn.ARN{
52+
Region: "us-east-1",
53+
AccountID: "some-account-123",
54+
Resource: "db:products-db",
55+
},
56+
},
57+
},
58+
expectedMetrics: fmt.Sprintf(baseExpectedMetrics, "products-db", "postgres", "15.4", "some-account-123", "aws", "us-east-1"),
4059
},
4160
{
4261
name: "Azure flexibleservers dsn",
4362
dsn: "postgres://user:[email protected]:5432/mydb",
4463
engineVersion: "15.4",
45-
expectedMetrics: fmt.Sprintf(baseExpectedMetrics, "products-db", "postgres", "15.4", "azure", "unknown"),
64+
expectedMetrics: fmt.Sprintf(baseExpectedMetrics, "products-db", "postgres", "15.4", "unknown", "azure", "unknown"),
4665
},
4766
}
4867

@@ -53,6 +72,7 @@ func TestConnectionInfo(t *testing.T) {
5372
DSN: tc.dsn,
5473
Registry: reg,
5574
EngineVersion: tc.engineVersion,
75+
CloudProvider: tc.cloudProvider,
5676
})
5777
require.NoError(t, err)
5878
require.NotNil(t, collector)

internal/component/database_observability/postgres/component.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"sync"
1212
"time"
1313

14+
"github.com/aws/aws-sdk-go-v2/aws/arn"
1415
"github.com/blang/semver/v4"
1516
"github.com/lib/pq"
1617
"github.com/prometheus/client_golang/prometheus"
@@ -67,11 +68,19 @@ type Arguments struct {
6768
EnableCollectors []string `alloy:"enable_collectors,attr,optional"`
6869
DisableCollectors []string `alloy:"disable_collectors,attr,optional"`
6970

71+
CloudProvider *CloudProvider `alloy:"cloud_provider,block,optional"`
7072
QuerySampleArguments QuerySampleArguments `alloy:"query_samples,block,optional"`
7173
QueryTablesArguments QueryTablesArguments `alloy:"query_details,block,optional"`
7274
SchemaDetailsArguments SchemaDetailsArguments `alloy:"schema_details,block,optional"`
75+
ExplainPlanArguments ExplainPlanArguments `alloy:"explain_plans,block,optional"`
76+
}
77+
78+
type CloudProvider struct {
79+
AWS *AWSCloudProviderInfo `alloy:"aws,block,optional"`
80+
}
7381

74-
ExplainPlanArguments ExplainPlanArguments `alloy:"explain_plans,block,optional"`
82+
type AWSCloudProviderInfo struct {
83+
ARN string `alloy:"arn,attr"`
7584
}
7685

7786
type QuerySampleArguments struct {
@@ -342,6 +351,19 @@ func (c *Component) startCollectors(systemID string, engineVersion string) error
342351
startErrors = append(startErrors, errorString)
343352
}
344353

354+
var cloudProviderInfo *database_observability.CloudProvider
355+
if c.args.CloudProvider != nil && c.args.CloudProvider.AWS != nil {
356+
arn, err := arn.Parse(c.args.CloudProvider.AWS.ARN)
357+
if err != nil {
358+
level.Error(c.opts.Logger).Log("msg", "failed to parse AWS cloud provider ARN", "err", err)
359+
}
360+
cloudProviderInfo = &database_observability.CloudProvider{
361+
AWS: &database_observability.AWSCloudProviderInfo{
362+
ARN: arn,
363+
},
364+
}
365+
}
366+
345367
entryHandler := addLokiLabels(loki.NewEntryHandler(c.handler.Chan(), func() {}), c.instanceKey, systemID)
346368

347369
collectors := enableOrDisableCollectors(c.args)
@@ -384,6 +406,7 @@ func (c *Component) startCollectors(systemID string, engineVersion string) error
384406
DSN: string(c.args.DataSourceName),
385407
Registry: c.registry,
386408
EngineVersion: engineVersion,
409+
CloudProvider: cloudProviderInfo,
387410
})
388411
if err != nil {
389412
logStartError(collector.ConnectionInfoName, "create", err)

0 commit comments

Comments
 (0)