Skip to content

Commit fe960c6

Browse files
authored
Move queries from queries.yaml to collectors (#801)
Signed-off-by: Ben Kochie <[email protected]>
1 parent 16430fc commit fe960c6

22 files changed

+1690
-279
lines changed

collector/collector.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ const (
3939
// Namespace for all metrics.
4040
namespace = "pg"
4141

42-
defaultEnabled = true
43-
// defaultDisabled = false
42+
defaultEnabled = true
43+
defaultDisabled = false
4444
)
4545

4646
var (

collector/collector_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
package collector
14+
15+
import (
16+
"strings"
17+
18+
"github.com/prometheus/client_golang/prometheus"
19+
dto "github.com/prometheus/client_model/go"
20+
)
21+
22+
type labelMap map[string]string
23+
24+
type MetricResult struct {
25+
labels labelMap
26+
value float64
27+
metricType dto.MetricType
28+
}
29+
30+
func readMetric(m prometheus.Metric) MetricResult {
31+
pb := &dto.Metric{}
32+
m.Write(pb)
33+
labels := make(labelMap, len(pb.Label))
34+
for _, v := range pb.Label {
35+
labels[v.GetName()] = v.GetValue()
36+
}
37+
if pb.Gauge != nil {
38+
return MetricResult{labels: labels, value: pb.GetGauge().GetValue(), metricType: dto.MetricType_GAUGE}
39+
}
40+
if pb.Counter != nil {
41+
return MetricResult{labels: labels, value: pb.GetCounter().GetValue(), metricType: dto.MetricType_COUNTER}
42+
}
43+
if pb.Untyped != nil {
44+
return MetricResult{labels: labels, value: pb.GetUntyped().GetValue(), metricType: dto.MetricType_UNTYPED}
45+
}
46+
panic("Unsupported metric type")
47+
}
48+
49+
func sanitizeQuery(q string) string {
50+
q = strings.Join(strings.Fields(q), " ")
51+
q = strings.Replace(q, "(", "\\(", -1)
52+
q = strings.Replace(q, ")", "\\)", -1)
53+
q = strings.Replace(q, "*", "\\*", -1)
54+
q = strings.Replace(q, "$", "\\$", -1)
55+
return q
56+
}

collector/pg_database.go

+11-8
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,15 @@ func NewPGDatabaseCollector(config collectorConfig) (Collector, error) {
4141
}, nil
4242
}
4343

44-
var pgDatabaseSizeDesc = prometheus.NewDesc(
45-
"pg_database_size_bytes",
46-
"Disk space used by the database",
47-
[]string{"datname"}, nil,
44+
var (
45+
pgDatabaseSizeDesc = prometheus.NewDesc(
46+
"pg_database_size_bytes",
47+
"Disk space used by the database",
48+
[]string{"datname"}, nil,
49+
)
50+
51+
pgDatabaseQuery = "SELECT pg_database.datname FROM pg_database;"
52+
pgDatabaseSizeQuery = "SELECT pg_database_size($1)"
4853
)
4954

5055
// Update implements Collector and exposes database size.
@@ -58,9 +63,7 @@ var pgDatabaseSizeDesc = prometheus.NewDesc(
5863
func (c PGDatabaseCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
5964
// Query the list of databases
6065
rows, err := db.QueryContext(ctx,
61-
`SELECT pg_database.datname
62-
FROM pg_database;
63-
`,
66+
pgDatabaseQuery,
6467
)
6568
if err != nil {
6669
return err
@@ -88,7 +91,7 @@ func (c PGDatabaseCollector) Update(ctx context.Context, db *sql.DB, ch chan<- p
8891
// Query the size of the databases
8992
for _, datname := range databases {
9093
var size int64
91-
err = db.QueryRowContext(ctx, "SELECT pg_database_size($1)", datname).Scan(&size)
94+
err = db.QueryRowContext(ctx, pgDatabaseSizeQuery, datname).Scan(&size)
9295
if err != nil {
9396
return err
9497
}

collector/pg_database_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
package collector
14+
15+
import (
16+
"context"
17+
"testing"
18+
19+
"github.com/DATA-DOG/go-sqlmock"
20+
"github.com/prometheus/client_golang/prometheus"
21+
dto "github.com/prometheus/client_model/go"
22+
"github.com/smartystreets/goconvey/convey"
23+
)
24+
25+
func TestPGDatabaseCollector(t *testing.T) {
26+
db, mock, err := sqlmock.New()
27+
if err != nil {
28+
t.Fatalf("Error opening a stub db connection: %s", err)
29+
}
30+
defer db.Close()
31+
32+
mock.ExpectQuery(sanitizeQuery(pgDatabaseQuery)).WillReturnRows(sqlmock.NewRows([]string{"datname"}).
33+
AddRow("postgres"))
34+
35+
mock.ExpectQuery(sanitizeQuery(pgDatabaseSizeQuery)).WithArgs("postgres").WillReturnRows(sqlmock.NewRows([]string{"pg_database_size"}).
36+
AddRow(1024))
37+
38+
ch := make(chan prometheus.Metric)
39+
go func() {
40+
defer close(ch)
41+
c := PGDatabaseCollector{}
42+
if err := c.Update(context.Background(), db, ch); err != nil {
43+
t.Errorf("Error calling PGDatabaseCollector.Update: %s", err)
44+
}
45+
}()
46+
47+
expected := []MetricResult{
48+
{labels: labelMap{"datname": "postgres"}, value: 1024, metricType: dto.MetricType_GAUGE},
49+
}
50+
convey.Convey("Metrics comparison", t, func() {
51+
for _, expect := range expected {
52+
m := readMetric(<-ch)
53+
convey.So(expect, convey.ShouldResemble, m)
54+
}
55+
})
56+
if err := mock.ExpectationsWereMet(); err != nil {
57+
t.Errorf("there were unfulfilled exceptions: %s", err)
58+
}
59+
}

collector/pg_postmaster.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package collector
15+
16+
import (
17+
"context"
18+
"database/sql"
19+
20+
"github.com/prometheus/client_golang/prometheus"
21+
)
22+
23+
func init() {
24+
registerCollector("postmaster", defaultEnabled, NewPGPostmasterCollector)
25+
}
26+
27+
type PGPostmasterCollector struct {
28+
}
29+
30+
func NewPGPostmasterCollector(collectorConfig) (Collector, error) {
31+
return &PGPostmasterCollector{}, nil
32+
}
33+
34+
var (
35+
pgPostMasterStartTimeSeconds = prometheus.NewDesc(
36+
"pg_postmaster_start_time_seconds",
37+
"Time at which postmaster started",
38+
[]string{}, nil,
39+
)
40+
41+
pgPostmasterQuery = "SELECT pg_postmaster_start_time from pg_postmaster_start_time();"
42+
)
43+
44+
func (c *PGPostmasterCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
45+
row := db.QueryRowContext(ctx,
46+
pgPostmasterQuery)
47+
48+
var startTimeSeconds float64
49+
err := row.Scan(&startTimeSeconds)
50+
if err != nil {
51+
return err
52+
}
53+
ch <- prometheus.MustNewConstMetric(
54+
pgPostMasterStartTimeSeconds,
55+
prometheus.GaugeValue, startTimeSeconds,
56+
)
57+
return nil
58+
}

collector/pg_postmaster_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
package collector
14+
15+
import (
16+
"context"
17+
"testing"
18+
19+
"github.com/DATA-DOG/go-sqlmock"
20+
"github.com/prometheus/client_golang/prometheus"
21+
dto "github.com/prometheus/client_model/go"
22+
"github.com/smartystreets/goconvey/convey"
23+
)
24+
25+
func TestPgPostmasterCollector(t *testing.T) {
26+
db, mock, err := sqlmock.New()
27+
if err != nil {
28+
t.Fatalf("Error opening a stub db connection: %s", err)
29+
}
30+
defer db.Close()
31+
32+
mock.ExpectQuery(sanitizeQuery(pgPostmasterQuery)).WillReturnRows(sqlmock.NewRows([]string{"pg_postmaster_start_time"}).
33+
AddRow(1685739904))
34+
35+
ch := make(chan prometheus.Metric)
36+
go func() {
37+
defer close(ch)
38+
c := PGPostmasterCollector{}
39+
40+
if err := c.Update(context.Background(), db, ch); err != nil {
41+
t.Errorf("Error calling PGPostmasterCollector.Update: %s", err)
42+
}
43+
}()
44+
45+
expected := []MetricResult{
46+
{labels: labelMap{}, value: 1685739904, metricType: dto.MetricType_GAUGE},
47+
}
48+
convey.Convey("Metrics comparison", t, func() {
49+
for _, expect := range expected {
50+
m := readMetric(<-ch)
51+
convey.So(expect, convey.ShouldResemble, m)
52+
}
53+
})
54+
if err := mock.ExpectationsWereMet(); err != nil {
55+
t.Errorf("there were unfulfilled exceptions: %s", err)
56+
}
57+
}

collector/pg_process_idle.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package collector
15+
16+
import (
17+
"context"
18+
"database/sql"
19+
20+
"github.com/go-kit/log"
21+
"github.com/prometheus/client_golang/prometheus"
22+
)
23+
24+
func init() {
25+
registerCollector("statements", defaultEnabled, NewPGProcessIdleCollector)
26+
}
27+
28+
type PGProcessIdleCollector struct {
29+
log log.Logger
30+
}
31+
32+
const processIdleSubsystem = "process_idle"
33+
34+
func NewPGProcessIdleCollector(config collectorConfig) (Collector, error) {
35+
return &PGProcessIdleCollector{log: config.logger}, nil
36+
}
37+
38+
var pgProcessIdleSeconds = prometheus.NewDesc(
39+
prometheus.BuildFQName(namespace, processIdleSubsystem, "seconds"),
40+
"Idle time of server processes",
41+
[]string{"application_name"},
42+
prometheus.Labels{},
43+
)
44+
45+
func (PGProcessIdleCollector) Update(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error {
46+
row := db.QueryRowContext(ctx,
47+
`WITH
48+
metrics AS (
49+
SELECT
50+
application_name,
51+
SUM(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - state_change))::bigint)::float AS process_idle_seconds_sum,
52+
COUNT(*) AS process_idle_seconds_count
53+
FROM pg_stat_activity
54+
WHERE state = 'idle'
55+
GROUP BY application_name
56+
),
57+
buckets AS (
58+
SELECT
59+
application_name,
60+
le,
61+
SUM(
62+
CASE WHEN EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - state_change)) <= le
63+
THEN 1
64+
ELSE 0
65+
END
66+
)::bigint AS bucket
67+
FROM
68+
pg_stat_activity,
69+
UNNEST(ARRAY[1, 2, 5, 15, 30, 60, 90, 120, 300]) AS le
70+
GROUP BY application_name, le
71+
ORDER BY application_name, le
72+
)
73+
SELECT
74+
application_name,
75+
process_idle_seconds_sum as seconds_sum,
76+
process_idle_seconds_count as seconds_count,
77+
ARRAY_AGG(le) AS seconds,
78+
ARRAY_AGG(bucket) AS seconds_bucket
79+
FROM metrics JOIN buckets USING (application_name)
80+
GROUP BY 1, 2, 3;`)
81+
82+
var applicationName string
83+
var secondsSum int64
84+
var secondsCount uint64
85+
var seconds []int64
86+
var secondsBucket []uint64
87+
88+
err := row.Scan(&applicationName, &secondsSum, &secondsCount, &seconds, &secondsBucket)
89+
90+
var buckets = make(map[float64]uint64, len(seconds))
91+
for i, second := range seconds {
92+
if i >= len(secondsBucket) {
93+
break
94+
}
95+
buckets[float64(second)] = secondsBucket[i]
96+
}
97+
if err != nil {
98+
return err
99+
}
100+
ch <- prometheus.MustNewConstHistogram(
101+
pgProcessIdleSeconds,
102+
secondsCount, float64(secondsSum), buckets,
103+
applicationName,
104+
)
105+
return nil
106+
}

0 commit comments

Comments
 (0)