Skip to content

Commit d330282

Browse files
committed
Add collector for database/sql#DBStats
MaxIdleTimeClosed is not available below go1.15. Therefore it is TODO. Signed-off-by: Mitsuo Heijo <[email protected]>
1 parent 0400fc4 commit d330282

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed

prometheus/dbstats_collector.go

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright 2021 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 prometheus
15+
16+
import "database/sql"
17+
18+
type dbStatsCollector struct {
19+
db *sql.DB
20+
21+
maxOpenConnections *Desc
22+
23+
openConnections *Desc
24+
inUseConnections *Desc
25+
idleConnections *Desc
26+
27+
waitCount *Desc
28+
waitDuration *Desc
29+
maxIdleClosed *Desc
30+
// maxIdleTimeClosed *Desc // TODO: for 1.15 or later
31+
maxLifetimeClosed *Desc
32+
}
33+
34+
// DBStatsCollectorOpts defines the behavior of a db stats collector
35+
// created with NewDBStatsCollector.
36+
type DBStatsCollectorOpts struct {
37+
// DriverName holds the name of driver.
38+
// It will not used for empty strings.
39+
DriverName string
40+
}
41+
42+
// NewDBStatsCollector returns a collector that exports metrics about the given *sql.DB.
43+
// See https://golang.org/pkg/database/sql/#DBStats for more information on stats.
44+
func NewDBStatsCollector(db *sql.DB, opts DBStatsCollectorOpts) Collector {
45+
var fqName func(name string) string
46+
if opts.DriverName == "" {
47+
fqName = func(name string) string {
48+
return "go_db_stats_" + name
49+
}
50+
} else {
51+
fqName = func(name string) string {
52+
return "go_" + opts.DriverName + "_db_stats_" + name
53+
}
54+
}
55+
return &dbStatsCollector{
56+
db: db,
57+
maxOpenConnections: NewDesc(
58+
fqName("max_open_connections"),
59+
"Maximum number of open connections to the database.",
60+
nil, nil,
61+
),
62+
openConnections: NewDesc(
63+
fqName("open_connections"),
64+
"The number of established connections both in use and idle.",
65+
nil, nil,
66+
),
67+
inUseConnections: NewDesc(
68+
fqName("in_use_connections"),
69+
"The number of connections currently in use.",
70+
nil, nil,
71+
),
72+
idleConnections: NewDesc(
73+
fqName("idle_connections"),
74+
"The number of idle connections.",
75+
nil, nil,
76+
),
77+
waitCount: NewDesc(
78+
fqName("wait_count_total"),
79+
"The total number of connections waited for.",
80+
nil, nil,
81+
),
82+
waitDuration: NewDesc(
83+
fqName("wait_duration_seconds_total"),
84+
"The total time blocked waiting for a new connection.",
85+
nil, nil,
86+
),
87+
maxIdleClosed: NewDesc(
88+
fqName("max_idle_closed_total"),
89+
"The total number of connections closed due to SetMaxIdleConns.",
90+
nil, nil,
91+
),
92+
maxLifetimeClosed: NewDesc(
93+
fqName("max_lifetime_closed_toal"),
94+
"The total number of connections closed due to SetConnMaxLifetime.",
95+
nil, nil,
96+
),
97+
}
98+
}
99+
100+
// Describe implements Collector.
101+
func (c *dbStatsCollector) Describe(ch chan<- *Desc) {
102+
ch <- c.maxOpenConnections
103+
ch <- c.openConnections
104+
ch <- c.inUseConnections
105+
ch <- c.idleConnections
106+
ch <- c.waitCount
107+
ch <- c.waitDuration
108+
ch <- c.maxIdleClosed
109+
ch <- c.maxLifetimeClosed
110+
}
111+
112+
// Collect implements Collector.
113+
func (c *dbStatsCollector) Collect(ch chan<- Metric) {
114+
stats := c.db.Stats()
115+
ch <- MustNewConstMetric(c.maxOpenConnections, GaugeValue, float64(stats.MaxOpenConnections))
116+
ch <- MustNewConstMetric(c.openConnections, GaugeValue, float64(stats.OpenConnections))
117+
ch <- MustNewConstMetric(c.inUseConnections, GaugeValue, float64(stats.InUse))
118+
ch <- MustNewConstMetric(c.idleConnections, GaugeValue, float64(stats.Idle))
119+
ch <- MustNewConstMetric(c.waitCount, CounterValue, float64(stats.WaitCount))
120+
ch <- MustNewConstMetric(c.waitDuration, CounterValue, stats.WaitDuration.Seconds())
121+
ch <- MustNewConstMetric(c.maxIdleClosed, CounterValue, float64(stats.MaxIdleClosed))
122+
ch <- MustNewConstMetric(c.maxLifetimeClosed, CounterValue, float64(stats.MaxLifetimeClosed))
123+
}

prometheus/dbstats_collector_test.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2021 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 prometheus
15+
16+
import (
17+
"database/sql"
18+
"testing"
19+
)
20+
21+
func TestDBStatsCollector(t *testing.T) {
22+
reg := NewRegistry()
23+
db := new(sql.DB)
24+
opts := DBStatsCollectorOpts{DriverName: "test"}
25+
if err := reg.Register(NewDBStatsCollector(db, opts)); err != nil {
26+
t.Fatal(err)
27+
}
28+
29+
mfs, err := reg.Gather()
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
names := []string{
35+
"go_test_db_stats_max_open_connections",
36+
"go_test_db_stats_open_connections",
37+
"go_test_db_stats_in_use",
38+
"go_test_db_stats_idle",
39+
"go_test_db_stats_wait_duration",
40+
"go_test_db_stats_max_idle_closed",
41+
"go_test_db_stats_max_lifetime_closed",
42+
}
43+
type result struct {
44+
found bool
45+
}
46+
results := make(map[string]result)
47+
for _, name := range names {
48+
results[name] = result{found: false}
49+
}
50+
for _, mf := range mfs {
51+
for _, name := range names {
52+
if name == mf.GetName() {
53+
results[name] = result{found: true}
54+
break
55+
}
56+
}
57+
}
58+
59+
for name, result := range results {
60+
if !result.found {
61+
t.Errorf("%s not found", name)
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)