Skip to content

Commit a1fd7ce

Browse files
PMM-7424 Add dbStatsCollector (#329)
* https://jira.percona.com/browse/PMM-7424 Add dbStatsCollector * Run make format * Add dbstats_ prefix * Update Changelog Co-authored-by: JiriCtvrtka <[email protected]>
1 parent 15d2f1d commit a1fd7ce

File tree

6 files changed

+203
-0
lines changed

6 files changed

+203
-0
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ Release 0.20.7 released 2021-08-10
22

33
Improvement : Upgraded dependencies
44
Fixed error PMM-6877: replsetGetStatus error on mongos
5+
Improvement PMM-7424: Add dbStats metric
56

67
Release 0.20.6 released 2021-06-26
78

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Currently, these metric sources are implemented:
4040
|\-\-log.level|Only log messages with the given severity or above. Valid levels: [debug, info, warn, error]|\-\-log.level="error"|
4141
|\-\-disable.diagnosticdata|Disable collecting metrics from getDiagnosticData||
4242
|\-\-disable.replicasetstatus|Disable collecting metrics from replSetGetStatus||
43+
|\-\-disable.dbstats|Disable collecting metrics from dbStats||
4344
|--version|Show version and exit|
4445

4546
### Build the exporter

exporter/dbstats_collector.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// mongodb_exporter
2+
// Copyright (C) 2017 Percona LLC
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package exporter
18+
19+
import (
20+
"context"
21+
22+
"github.com/prometheus/client_golang/prometheus"
23+
"github.com/sirupsen/logrus"
24+
"go.mongodb.org/mongo-driver/bson"
25+
"go.mongodb.org/mongo-driver/mongo"
26+
)
27+
28+
type dbstatsCollector struct {
29+
ctx context.Context
30+
client *mongo.Client
31+
compatibleMode bool
32+
logger *logrus.Logger
33+
topologyInfo labelsGetter
34+
}
35+
36+
func (d *dbstatsCollector) Describe(ch chan<- *prometheus.Desc) {
37+
prometheus.DescribeByCollect(d, ch)
38+
}
39+
40+
func (d *dbstatsCollector) Collect(ch chan<- prometheus.Metric) {
41+
// List all databases names
42+
dbNames, err := d.client.ListDatabaseNames(d.ctx, bson.M{})
43+
if err != nil {
44+
d.logger.Errorf("Failed to get database names: %s", err)
45+
return
46+
}
47+
d.logger.Debugf("getting stats for databases: %v", dbNames)
48+
for _, db := range dbNames {
49+
var dbStats bson.M
50+
cmd := bson.D{{Key: "dbStats", Value: 1}, {Key: "scale", Value: 1}}
51+
r := d.client.Database(db).RunCommand(d.ctx, cmd)
52+
err := r.Decode(&dbStats)
53+
if err != nil {
54+
d.logger.Errorf("Failed to get $dbstats for database %s: %s", db, err)
55+
continue
56+
}
57+
58+
d.logger.Debugf("$dbStats metrics for %s", db)
59+
debugResult(d.logger, dbStats)
60+
61+
// Since all dbstats will have the same fields, we need to use a metric prefix (db)
62+
// to differentiate metrics between different databases. Labels are being set only to make it easier
63+
// to filter
64+
prefix := "dbstats_" + db
65+
66+
labels := d.topologyInfo.baseLabels()
67+
labels["database"] = db
68+
69+
for _, metric := range makeMetrics(prefix, dbStats, d.topologyInfo.baseLabels(), d.compatibleMode) {
70+
ch <- metric
71+
}
72+
}
73+
}
74+
75+
var _ prometheus.Collector = (*dbstatsCollector)(nil)

exporter/dbstats_collector_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// mongodb_exporter
2+
// Copyright (C) 2017 Percona LLC
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package exporter
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
"testing"
24+
"time"
25+
26+
"github.com/prometheus/client_golang/prometheus/testutil"
27+
"github.com/sirupsen/logrus"
28+
"github.com/stretchr/testify/assert"
29+
"go.mongodb.org/mongo-driver/bson"
30+
31+
"github.com/percona/mongodb_exporter/internal/tu"
32+
)
33+
34+
func TestDBStatsCollector(t *testing.T) {
35+
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
36+
defer cancel()
37+
38+
client := tu.DefaultTestClient(ctx, t)
39+
40+
database := client.Database("testdb")
41+
database.Drop(ctx) //nolint
42+
43+
defer func() {
44+
err := database.Drop(ctx)
45+
assert.NoError(t, err)
46+
}()
47+
48+
for i := 0; i < 3; i++ {
49+
coll := fmt.Sprintf("testcol_%02d", i)
50+
for j := 0; j < 10; j++ {
51+
_, err := database.Collection(coll).InsertOne(ctx, bson.M{"f1": j, "f2": "2"})
52+
assert.NoError(t, err)
53+
}
54+
}
55+
56+
ti := labelsGetterMock{}
57+
58+
c := &dbstatsCollector{
59+
client: client,
60+
logger: logrus.New(),
61+
topologyInfo: ti,
62+
}
63+
64+
// The last \n at the end of this string is important
65+
expected := strings.NewReader(`
66+
# HELP mongodb_dbstats_testdb_avgObjSize dbstats_testdb.
67+
# TYPE mongodb_dbstats_testdb_avgObjSize untyped
68+
mongodb_dbstats_testdb_avgObjSize 40
69+
# HELP mongodb_dbstats_testdb_collections dbstats_testdb.
70+
# TYPE mongodb_dbstats_testdb_collections untyped
71+
mongodb_dbstats_testdb_collections 3
72+
# HELP mongodb_dbstats_testdb_dataSize dbstats_testdb.
73+
# TYPE mongodb_dbstats_testdb_dataSize untyped
74+
mongodb_dbstats_testdb_dataSize 1200
75+
# HELP mongodb_dbstats_testdb_indexSize dbstats_testdb.
76+
# TYPE mongodb_dbstats_testdb_indexSize untyped
77+
mongodb_dbstats_testdb_indexSize 12288
78+
# HELP mongodb_dbstats_testdb_indexes dbstats_testdb.
79+
# TYPE mongodb_dbstats_testdb_indexes untyped
80+
mongodb_dbstats_testdb_indexes 3
81+
# HELP mongodb_dbstats_testdb_objects dbstats_testdb.
82+
# TYPE mongodb_dbstats_testdb_objects untyped
83+
mongodb_dbstats_testdb_objects 30
84+
# HELP mongodb_dbstats_testdb_ok dbstats_testdb.
85+
# TYPE mongodb_dbstats_testdb_ok untyped
86+
mongodb_dbstats_testdb_ok 1
87+
# HELP mongodb_dbstats_testdb_storageSize dbstats_testdb.
88+
# TYPE mongodb_dbstats_testdb_storageSize untyped
89+
mongodb_dbstats_testdb_storageSize 12288
90+
# HELP mongodb_dbstats_testdb_views dbstats_testdb.
91+
# TYPE mongodb_dbstats_testdb_views untyped
92+
mongodb_dbstats_testdb_views 0` +
93+
"\n")
94+
95+
// Filter metrics for 2 reasons:
96+
// 1. The result is huge
97+
// 2. We need to check against know values. Don't use metrics that return counters like uptime
98+
// or counters like the number of transactions because they won't return a known value to compare
99+
filter := []string{
100+
"mongodb_dbstats_testdb_avgObjSize",
101+
"mongodb_dbstats_testdb_collections",
102+
"mongodb_dbstats_testdb_dataSize",
103+
"mongodb_dbstats_testdb_indexSize",
104+
"mongodb_dbstats_testdb_indexes",
105+
"mongodb_dbstats_testdb_objects",
106+
"mongodb_dbstats_testdb_views",
107+
"mongodb_dbstats_testdb_storageSize",
108+
"mongodb_dbstats_testdb_ok",
109+
}
110+
err := testutil.CollectAndCompare(c, expected, filter...)
111+
assert.NoError(t, err)
112+
}

exporter/exporter.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type Opts struct {
5454
Logger *logrus.Logger
5555
DisableDiagnosticData bool
5656
DisableReplicasetStatus bool
57+
DisableDBStats bool
5758
}
5859

5960
var (
@@ -147,6 +148,17 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol
147148
registry.MustRegister(&ddc)
148149
}
149150

151+
if !e.opts.DisableDBStats {
152+
cc := dbstatsCollector{
153+
ctx: ctx,
154+
client: client,
155+
compatibleMode: e.opts.CompatibleMode,
156+
logger: e.opts.Logger,
157+
topologyInfo: topologyInfo,
158+
}
159+
registry.MustRegister(&cc)
160+
}
161+
150162
// replSetGetStatus is not supported through mongos
151163
if !e.opts.DisableReplicasetStatus && nodeType != typeMongos {
152164
rsgsc := replSetGetStatusCollector{

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type GlobalFlags struct {
4747

4848
DisableDiagnosticData bool `name:"disable.diagnosticdata" help:"Disable collecting metrics from getDiagnosticData"`
4949
DisableReplicasetStatus bool `name:"disable.replicasetstatus" help:"Disable collecting metrics from replSetGetStatus"`
50+
DisableDBStats bool `name:"disable.dbstats" help:"Disable collecting metrics from dbStats"`
5051

5152
DiscoveringMode bool `name:"discovering-mode" help:"Enable autodiscover collections"`
5253
CompatibleMode bool `name:"compatible-mode" help:"Enable old mongodb-exporter compatible metrics"`
@@ -117,6 +118,7 @@ func buildExporter(opts GlobalFlags) (*exporter.Exporter, error) {
117118
DisableDiagnosticData: opts.DisableDiagnosticData,
118119
DisableReplicasetStatus: opts.DisableReplicasetStatus,
119120
DirectConnect: opts.DirectConnect,
121+
DisableDBStats: opts.DisableDBStats,
120122
}
121123

122124
e, err := exporter.New(exporterOpts)

0 commit comments

Comments
 (0)