Skip to content

Commit c0be446

Browse files
committed
Add collector for database/sql#DBStats
Signed-off-by: Mitsuo Heijo <mitsuo.heijo@gmail.com>
1 parent b89620c commit c0be446

File tree

3 files changed

+337
-0
lines changed

3 files changed

+337
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
//go:build go1.15
15+
// +build go1.15
16+
17+
package collectors
18+
19+
import (
20+
"database/sql"
21+
22+
"github.com/prometheus/client_golang/prometheus"
23+
)
24+
25+
type dbStatsCollector struct {
26+
db *sql.DB
27+
28+
maxOpenConnections *prometheus.Desc
29+
30+
openConnections *prometheus.Desc
31+
inUseConnections *prometheus.Desc
32+
idleConnections *prometheus.Desc
33+
34+
waitCount *prometheus.Desc
35+
waitDuration *prometheus.Desc
36+
maxIdleClosed *prometheus.Desc
37+
maxIdleTimeClosed *prometheus.Desc
38+
maxLifetimeClosed *prometheus.Desc
39+
}
40+
41+
// DBStatsCollectorOpts defines the behavior of a db stats collector
42+
// created with NewDBStatsCollector.
43+
type DBStatsCollectorOpts struct {
44+
// DriverName holds the name of driver.
45+
// It will not used for empty strings.
46+
DriverName string
47+
}
48+
49+
// NewDBStatsCollector returns a collector that exports metrics about the given *sql.DB.
50+
// See https://golang.org/pkg/database/sql/#DBStats for more information on stats.
51+
func NewDBStatsCollector(db *sql.DB, opts DBStatsCollectorOpts) prometheus.Collector {
52+
var fqName func(name string) string
53+
if opts.DriverName == "" {
54+
fqName = func(name string) string {
55+
return "go_db_stats_" + name
56+
}
57+
} else {
58+
fqName = func(name string) string {
59+
return "go_" + opts.DriverName + "_db_stats_" + name
60+
}
61+
}
62+
return &dbStatsCollector{
63+
db: db,
64+
maxOpenConnections: prometheus.NewDesc(
65+
fqName("max_open_connections"),
66+
"Maximum number of open connections to the database.",
67+
nil, nil,
68+
),
69+
openConnections: prometheus.NewDesc(
70+
fqName("open_connections"),
71+
"The number of established connections both in use and idle.",
72+
nil, nil,
73+
),
74+
inUseConnections: prometheus.NewDesc(
75+
fqName("in_use_connections"),
76+
"The number of connections currently in use.",
77+
nil, nil,
78+
),
79+
idleConnections: prometheus.NewDesc(
80+
fqName("idle_connections"),
81+
"The number of idle connections.",
82+
nil, nil,
83+
),
84+
waitCount: prometheus.NewDesc(
85+
fqName("wait_count_total"),
86+
"The total number of connections waited for.",
87+
nil, nil,
88+
),
89+
waitDuration: prometheus.NewDesc(
90+
fqName("wait_duration_seconds_total"),
91+
"The total time blocked waiting for a new connection.",
92+
nil, nil,
93+
),
94+
maxIdleClosed: prometheus.NewDesc(
95+
fqName("max_idle_closed_total"),
96+
"The total number of connections closed due to SetMaxIdleConns.",
97+
nil, nil,
98+
),
99+
maxIdleTimeClosed: prometheus.NewDesc(
100+
fqName("max_idle_time_closed_total"),
101+
"The total number of connections closed due to SetConnMaxIdleTime.",
102+
nil, nil,
103+
),
104+
maxLifetimeClosed: prometheus.NewDesc(
105+
fqName("max_lifetime_closed_total"),
106+
"The total number of connections closed due to SetConnMaxLifetime.",
107+
nil, nil,
108+
),
109+
}
110+
}
111+
112+
// Describe implements Collector.
113+
func (c *dbStatsCollector) Describe(ch chan<- *prometheus.Desc) {
114+
ch <- c.maxOpenConnections
115+
ch <- c.openConnections
116+
ch <- c.inUseConnections
117+
ch <- c.idleConnections
118+
ch <- c.waitCount
119+
ch <- c.waitDuration
120+
ch <- c.maxIdleClosed
121+
ch <- c.maxIdleTimeClosed
122+
ch <- c.maxLifetimeClosed
123+
}
124+
125+
// Collect implements Collector.
126+
func (c *dbStatsCollector) Collect(ch chan<- prometheus.Metric) {
127+
stats := c.db.Stats()
128+
ch <- prometheus.MustNewConstMetric(c.maxOpenConnections, prometheus.GaugeValue, float64(stats.MaxOpenConnections))
129+
ch <- prometheus.MustNewConstMetric(c.openConnections, prometheus.GaugeValue, float64(stats.OpenConnections))
130+
ch <- prometheus.MustNewConstMetric(c.inUseConnections, prometheus.GaugeValue, float64(stats.InUse))
131+
ch <- prometheus.MustNewConstMetric(c.idleConnections, prometheus.GaugeValue, float64(stats.Idle))
132+
ch <- prometheus.MustNewConstMetric(c.waitCount, prometheus.CounterValue, float64(stats.WaitCount))
133+
ch <- prometheus.MustNewConstMetric(c.waitDuration, prometheus.CounterValue, stats.WaitDuration.Seconds())
134+
ch <- prometheus.MustNewConstMetric(c.maxIdleClosed, prometheus.CounterValue, float64(stats.MaxIdleClosed))
135+
ch <- prometheus.MustNewConstMetric(c.maxIdleTimeClosed, prometheus.CounterValue, float64(stats.MaxIdleTimeClosed))
136+
ch <- prometheus.MustNewConstMetric(c.maxLifetimeClosed, prometheus.CounterValue, float64(stats.MaxLifetimeClosed))
137+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
//go:build !go1.15
15+
// +build !go1.15
16+
17+
package collectors
18+
19+
import (
20+
"database/sql"
21+
22+
"github.com/prometheus/client_golang/prometheus"
23+
)
24+
25+
type dbStatsCollector struct {
26+
db *sql.DB
27+
28+
maxOpenConnections *prometheus.Desc
29+
30+
openConnections *prometheus.Desc
31+
inUseConnections *prometheus.Desc
32+
idleConnections *prometheus.Desc
33+
34+
waitCount *prometheus.Desc
35+
waitDuration *prometheus.Desc
36+
maxIdleClosed *prometheus.Desc
37+
maxLifetimeClosed *prometheus.Desc
38+
}
39+
40+
// DBStatsCollectorOpts defines the behavior of a db stats collector
41+
// created with NewDBStatsCollector.
42+
type DBStatsCollectorOpts struct {
43+
// DriverName holds the name of driver.
44+
// It will not used for empty strings.
45+
DriverName string
46+
}
47+
48+
// NewDBStatsCollector returns a collector that exports metrics about the given *sql.DB.
49+
// See https://golang.org/pkg/database/sql/#DBStats for more information on stats.
50+
func NewDBStatsCollector(db *sql.DB, opts DBStatsCollectorOpts) prometheus.Collector {
51+
var fqName func(name string) string
52+
if opts.DriverName == "" {
53+
fqName = func(name string) string {
54+
return "go_db_stats_" + name
55+
}
56+
} else {
57+
fqName = func(name string) string {
58+
return "go_" + opts.DriverName + "_db_stats_" + name
59+
}
60+
}
61+
return &dbStatsCollector{
62+
db: db,
63+
maxOpenConnections: prometheus.NewDesc(
64+
fqName("max_open_connections"),
65+
"Maximum number of open connections to the database.",
66+
nil, nil,
67+
),
68+
openConnections: prometheus.NewDesc(
69+
fqName("open_connections"),
70+
"The number of established connections both in use and idle.",
71+
nil, nil,
72+
),
73+
inUseConnections: prometheus.NewDesc(
74+
fqName("in_use_connections"),
75+
"The number of connections currently in use.",
76+
nil, nil,
77+
),
78+
idleConnections: prometheus.NewDesc(
79+
fqName("idle_connections"),
80+
"The number of idle connections.",
81+
nil, nil,
82+
),
83+
waitCount: prometheus.NewDesc(
84+
fqName("wait_count_total"),
85+
"The total number of connections waited for.",
86+
nil, nil,
87+
),
88+
waitDuration: prometheus.NewDesc(
89+
fqName("wait_duration_seconds_total"),
90+
"The total time blocked waiting for a new connection.",
91+
nil, nil,
92+
),
93+
maxIdleClosed: prometheus.NewDesc(
94+
fqName("max_idle_closed_total"),
95+
"The total number of connections closed due to SetMaxIdleConns.",
96+
nil, nil,
97+
),
98+
maxLifetimeClosed: prometheus.NewDesc(
99+
fqName("max_lifetime_closed_total"),
100+
"The total number of connections closed due to SetConnMaxLifetime.",
101+
nil, nil,
102+
),
103+
}
104+
}
105+
106+
// Describe implements Collector.
107+
func (c *dbStatsCollector) Describe(ch chan<- *prometheus.Desc) {
108+
ch <- c.maxOpenConnections
109+
ch <- c.openConnections
110+
ch <- c.inUseConnections
111+
ch <- c.idleConnections
112+
ch <- c.waitCount
113+
ch <- c.waitDuration
114+
ch <- c.maxIdleClosed
115+
ch <- c.maxLifetimeClosed
116+
}
117+
118+
// Collect implements Collector.
119+
func (c *dbStatsCollector) Collect(ch chan<- prometheus.Metric) {
120+
stats := c.db.Stats()
121+
ch <- prometheus.MustNewConstMetric(c.maxOpenConnections, prometheus.GaugeValue, float64(stats.MaxOpenConnections))
122+
ch <- prometheus.MustNewConstMetric(c.openConnections, prometheus.GaugeValue, float64(stats.OpenConnections))
123+
ch <- prometheus.MustNewConstMetric(c.inUseConnections, prometheus.GaugeValue, float64(stats.InUse))
124+
ch <- prometheus.MustNewConstMetric(c.idleConnections, prometheus.GaugeValue, float64(stats.Idle))
125+
ch <- prometheus.MustNewConstMetric(c.waitCount, prometheus.CounterValue, float64(stats.WaitCount))
126+
ch <- prometheus.MustNewConstMetric(c.waitDuration, prometheus.CounterValue, stats.WaitDuration.Seconds())
127+
ch <- prometheus.MustNewConstMetric(c.maxIdleClosed, prometheus.CounterValue, float64(stats.MaxIdleClosed))
128+
ch <- prometheus.MustNewConstMetric(c.maxLifetimeClosed, prometheus.CounterValue, float64(stats.MaxLifetimeClosed))
129+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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 collectors
15+
16+
import (
17+
"database/sql"
18+
"runtime"
19+
"testing"
20+
21+
"github.com/prometheus/client_golang/prometheus"
22+
)
23+
24+
func TestDBStatsCollector(t *testing.T) {
25+
reg := prometheus.NewRegistry()
26+
db := new(sql.DB)
27+
opts := DBStatsCollectorOpts{DriverName: "test"}
28+
if err := reg.Register(NewDBStatsCollector(db, opts)); err != nil {
29+
t.Fatal(err)
30+
}
31+
32+
mfs, err := reg.Gather()
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
37+
names := []string{
38+
"go_test_db_stats_max_open_connections",
39+
"go_test_db_stats_open_connections",
40+
"go_test_db_stats_in_use_connections",
41+
"go_test_db_stats_idle_connections",
42+
"go_test_db_stats_wait_count_total",
43+
"go_test_db_stats_wait_duration_seconds_total",
44+
"go_test_db_stats_max_idle_closed_total",
45+
"go_test_db_stats_max_lifetime_closed_total",
46+
}
47+
if runtime.Version() >= "go1.15" {
48+
names = append(names, "go_test_db_stats_max_idle_time_closed_total")
49+
}
50+
type result struct {
51+
found bool
52+
}
53+
results := make(map[string]result)
54+
for _, name := range names {
55+
results[name] = result{found: false}
56+
}
57+
for _, mf := range mfs {
58+
for _, name := range names {
59+
if name == mf.GetName() {
60+
results[name] = result{found: true}
61+
break
62+
}
63+
}
64+
}
65+
66+
for name, result := range results {
67+
if !result.found {
68+
t.Errorf("%s not found", name)
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)