Skip to content

Commit 393dab8

Browse files
committed
User Indexes collector and test
Signed-off-by: Felix Yuan <[email protected]>
1 parent dcf498e commit 393dab8

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed

collector/pg_stat_user_indexes.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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+
"database/sql"
18+
19+
"github.com/go-kit/log"
20+
"github.com/prometheus/client_golang/prometheus"
21+
)
22+
23+
func init() {
24+
registerCollector(statUserIndexesSubsystem, defaultDisabled, NewPGStatUserIndexesCollector)
25+
}
26+
27+
type PGStatUserIndexesCollector struct {
28+
log log.Logger
29+
}
30+
31+
const statUserIndexesSubsystem = "stat_user_indexes"
32+
33+
func NewPGStatUserIndexesCollector(config collectorConfig) (Collector, error) {
34+
return &PGStatUserIndexesCollector{log: config.logger}, nil
35+
}
36+
37+
var (
38+
statUserIndexesIdxScan = prometheus.NewDesc(
39+
prometheus.BuildFQName(namespace, statUserIndexesSubsystem, "idx_scans_total"),
40+
"Number of index scans initiated on this index",
41+
[]string{"schemaname", "relname", "indexrelname"},
42+
prometheus.Labels{},
43+
)
44+
statUserIndexesIdxTupRead = prometheus.NewDesc(
45+
prometheus.BuildFQName(namespace, statUserIndexesSubsystem, "idx_tup_reads_total"),
46+
"Number of index entries returned by scans on this index",
47+
[]string{"schemaname", "relname", "indexrelname"},
48+
prometheus.Labels{},
49+
)
50+
statUserIndexesIdxTupFetch = prometheus.NewDesc(
51+
prometheus.BuildFQName(namespace, statUserIndexesSubsystem, "idx_tup_fetches_total"),
52+
"Number of live table rows fetched by simple index scans using this index",
53+
[]string{"schemaname", "relname", "indexrelname"},
54+
prometheus.Labels{},
55+
)
56+
57+
statUserIndexesQuery = `
58+
SELECT
59+
schemaname,
60+
relname,
61+
indexrelname,
62+
idx_scan,
63+
idx_tup_read,
64+
idx_tup_fetch
65+
FROM pg_stat_user_indexes
66+
`
67+
)
68+
69+
func (c *PGStatUserIndexesCollector) Update(ctx context.Context, instance *instance, ch chan<- prometheus.Metric) error {
70+
db := instance.getDB()
71+
rows, err := db.QueryContext(ctx,
72+
statUserIndexesQuery)
73+
74+
if err != nil {
75+
return err
76+
}
77+
defer rows.Close()
78+
for rows.Next() {
79+
var schemaname, relname, indexrelname sql.NullString
80+
var idxScan, idxTupRead, idxTupFetch sql.NullFloat64
81+
82+
if err := rows.Scan(&schemaname, &relname, &indexrelname, &idxScan, &idxTupRead, &idxTupFetch); err != nil {
83+
return err
84+
}
85+
schemanameLabel := "unknown"
86+
if schemaname.Valid {
87+
schemanameLabel = schemaname.String
88+
}
89+
relnameLabel := "unknown"
90+
if relname.Valid {
91+
relnameLabel = relname.String
92+
}
93+
indexrelnameLabel := "unknown"
94+
if indexrelname.Valid {
95+
indexrelnameLabel = indexrelname.String
96+
}
97+
labels := []string{schemanameLabel, relnameLabel, indexrelnameLabel}
98+
99+
idxScanMetric := 0.0
100+
if idxScan.Valid {
101+
idxScanMetric = idxScan.Float64
102+
}
103+
ch <- prometheus.MustNewConstMetric(
104+
statUserIndexesIdxScan,
105+
prometheus.CounterValue,
106+
idxScanMetric,
107+
labels...,
108+
)
109+
110+
idxTupReadMetric := 0.0
111+
if idxTupRead.Valid {
112+
idxTupReadMetric = idxTupRead.Float64
113+
}
114+
ch <- prometheus.MustNewConstMetric(
115+
statUserIndexesIdxTupRead,
116+
prometheus.CounterValue,
117+
idxTupReadMetric,
118+
labels...,
119+
)
120+
121+
idxTupFetchMetric := 0.0
122+
if idxTupFetch.Valid {
123+
idxTupFetchMetric = idxTupFetch.Float64
124+
}
125+
ch <- prometheus.MustNewConstMetric(
126+
statUserIndexesIdxTupFetch,
127+
prometheus.CounterValue,
128+
idxTupFetchMetric,
129+
labels...,
130+
)
131+
}
132+
if err := rows.Err(); err != nil {
133+
return err
134+
}
135+
return nil
136+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 TestPgStatUserIndexesCollector(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+
inst := &instance{db: db}
32+
columns := []string{
33+
"schemaname",
34+
"relname",
35+
"indexrelname",
36+
"idx_scan",
37+
"idx_tup_read",
38+
"idx_tup_fetch",
39+
}
40+
rows := sqlmock.NewRows(columns).
41+
AddRow("public", "pgbench_accounts", "pgbench_accounts_pkey", 5, 6, 7)
42+
43+
mock.ExpectQuery(sanitizeQuery(statUserIndexesQuery)).WillReturnRows(rows)
44+
45+
ch := make(chan prometheus.Metric)
46+
go func() {
47+
defer close(ch)
48+
c := PGStatUserIndexesCollector{}
49+
50+
if err := c.Update(context.Background(), inst, ch); err != nil {
51+
t.Errorf("Error calling PGStatUserIndexesCollector.Update: %s", err)
52+
}
53+
}()
54+
expected := []MetricResult{
55+
{labels: labelMap{"schemaname": "public", "relname": "pgbench_accounts", "indexrelname": "pgbench_accounts_pkey"}, value: 5, metricType: dto.MetricType_COUNTER},
56+
{labels: labelMap{"schemaname": "public", "relname": "pgbench_accounts", "indexrelname": "pgbench_accounts_pkey"}, value: 6, metricType: dto.MetricType_COUNTER},
57+
{labels: labelMap{"schemaname": "public", "relname": "pgbench_accounts", "indexrelname": "pgbench_accounts_pkey"}, value: 7, metricType: dto.MetricType_COUNTER},
58+
}
59+
convey.Convey("Metrics comparison", t, func() {
60+
for _, expect := range expected {
61+
m := readMetric(<-ch)
62+
convey.So(expect, convey.ShouldResemble, m)
63+
}
64+
})
65+
if err := mock.ExpectationsWereMet(); err != nil {
66+
t.Errorf("there were unfulfilled exceptions: %s", err)
67+
}
68+
}
69+
70+
func TestPgStatUserIndexesCollectorNull(t *testing.T) {
71+
db, mock, err := sqlmock.New()
72+
if err != nil {
73+
t.Fatalf("Error opening a stub db connection: %s", err)
74+
}
75+
defer db.Close()
76+
inst := &instance{db: db}
77+
columns := []string{
78+
"schemaname",
79+
"relname",
80+
"indexrelname",
81+
"idx_scan",
82+
"idx_tup_read",
83+
"idx_tup_fetch",
84+
}
85+
rows := sqlmock.NewRows(columns).
86+
AddRow(nil, nil, nil, nil, nil, nil)
87+
88+
mock.ExpectQuery(sanitizeQuery(statUserIndexesQuery)).WillReturnRows(rows)
89+
90+
ch := make(chan prometheus.Metric)
91+
go func() {
92+
defer close(ch)
93+
c := PGStatUserIndexesCollector{}
94+
95+
if err := c.Update(context.Background(), inst, ch); err != nil {
96+
t.Errorf("Error calling PGStatUserIndexesCollector.Update: %s", err)
97+
}
98+
}()
99+
expected := []MetricResult{
100+
{labels: labelMap{"schemaname": "unknown", "relname": "unknown", "indexrelname": "unknown"}, value: 0, metricType: dto.MetricType_COUNTER},
101+
{labels: labelMap{"schemaname": "unknown", "relname": "unknown", "indexrelname": "unknown"}, value: 0, metricType: dto.MetricType_COUNTER},
102+
{labels: labelMap{"schemaname": "unknown", "relname": "unknown", "indexrelname": "unknown"}, value: 0, metricType: dto.MetricType_COUNTER},
103+
}
104+
convey.Convey("Metrics comparison", t, func() {
105+
for _, expect := range expected {
106+
m := readMetric(<-ch)
107+
convey.So(expect, convey.ShouldResemble, m)
108+
}
109+
})
110+
if err := mock.ExpectationsWereMet(); err != nil {
111+
t.Errorf("there were unfulfilled exceptions: %s", err)
112+
}
113+
}

0 commit comments

Comments
 (0)