Skip to content

Commit d4d1c28

Browse files
author
Anton Kucherov
committed
PMM-4131 Added replset_conf metrics.
1 parent af193c7 commit d4d1c28

File tree

6 files changed

+211
-11
lines changed

6 files changed

+211
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Changed
1010

1111
### Added
12+
- [PMM-4131](https://jira.percona.com/browse/PMM-4131): Added some features from [dcu/mongodb_exporter](https://github.com/dcu/mongodb_exporter). See list below.
13+
- New metrics:
14+
- `mongodb_mongod_replset_member_*`
15+
1216

1317
### Fixed
1418

collector/mongod/replset_conf.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package mongod
2+
3+
import (
4+
"context"
5+
6+
"go.mongodb.org/mongo-driver/bson"
7+
"go.mongodb.org/mongo-driver/mongo"
8+
9+
"github.com/prometheus/client_golang/prometheus"
10+
"github.com/prometheus/common/log"
11+
)
12+
13+
var (
14+
memberHidden = prometheus.NewGaugeVec(prometheus.GaugeOpts{
15+
Namespace: Namespace,
16+
Subsystem: subsystem,
17+
Name: "member_hidden",
18+
Help: "This field conveys if the member is hidden (1) or not-hidden (0).",
19+
}, []string{"id", "host"})
20+
memberArbiter = prometheus.NewGaugeVec(prometheus.GaugeOpts{
21+
Namespace: Namespace,
22+
Subsystem: subsystem,
23+
Name: "member_arbiter",
24+
Help: "This field conveys if the member is an arbiter (1) or not (0).",
25+
}, []string{"id", "host"})
26+
memberBuildIndexes = prometheus.NewGaugeVec(prometheus.GaugeOpts{
27+
Namespace: Namespace,
28+
Subsystem: subsystem,
29+
Name: "member_build_indexes",
30+
Help: "This field conveys if the member is builds indexes (1) or not (0).",
31+
}, []string{"id", "host"})
32+
memberPriority = prometheus.NewGaugeVec(prometheus.GaugeOpts{
33+
Namespace: Namespace,
34+
Subsystem: subsystem,
35+
Name: "member_priority",
36+
Help: "This field conveys the priority of a given member",
37+
}, []string{"id", "host"})
38+
memberVotes = prometheus.NewGaugeVec(prometheus.GaugeOpts{
39+
Namespace: Namespace,
40+
Subsystem: subsystem,
41+
Name: "member_votes",
42+
Help: "This field conveys the number of votes of a given member",
43+
}, []string{"id", "host"})
44+
)
45+
46+
// Although the docs say that it returns a map with id etc. it *actually* returns
47+
// that wrapped in a map
48+
type OuterReplSetConf struct {
49+
Config ReplSetConf `bson:"config"`
50+
}
51+
52+
// ReplSetConf keeps the data returned by the GetReplSetConf method
53+
type ReplSetConf struct {
54+
Id string `bson:"_id"`
55+
Version int `bson:"version"`
56+
Members []MemberConf `bson:"members"`
57+
}
58+
59+
type ReplSetConfSettings struct{}
60+
61+
// Member represents an array element of ReplSetConf.Members
62+
type MemberConf struct {
63+
Id int32 `bson:"_id"`
64+
Host string `bson:"host"`
65+
ArbiterOnly bool `bson:"arbiterOnly"`
66+
BuildIndexes bool `bson:"buildIndexes"`
67+
Hidden bool `bson:"hidden"`
68+
Priority int32 `bson:"priority"`
69+
70+
Tags map[string]string `bson:"tags"`
71+
SlaveDelay float64 `bson:"saveDelay"`
72+
Votes int32 `bson:"votes"`
73+
}
74+
75+
// Export exports the replSetGetStatus stati to be consumed by prometheus
76+
func (replConf *ReplSetConf) Export(ch chan<- prometheus.Metric) {
77+
for _, member := range replConf.Members {
78+
ls := prometheus.Labels{
79+
"id": replConf.Id,
80+
"host": member.Host,
81+
}
82+
if member.Hidden {
83+
memberHidden.With(ls).Set(1)
84+
} else {
85+
memberHidden.With(ls).Set(0)
86+
}
87+
88+
if member.ArbiterOnly {
89+
memberArbiter.With(ls).Set(1)
90+
} else {
91+
memberArbiter.With(ls).Set(0)
92+
}
93+
94+
if member.BuildIndexes {
95+
memberBuildIndexes.With(ls).Set(1)
96+
} else {
97+
memberBuildIndexes.With(ls).Set(0)
98+
}
99+
100+
memberPriority.With(ls).Set(float64(member.Priority))
101+
memberVotes.With(ls).Set(float64(member.Votes))
102+
}
103+
// collect metrics
104+
memberHidden.Collect(ch)
105+
memberArbiter.Collect(ch)
106+
memberBuildIndexes.Collect(ch)
107+
memberPriority.Collect(ch)
108+
memberVotes.Collect(ch)
109+
}
110+
111+
// Describe describes the replSetGetStatus metrics for prometheus
112+
func (replConf *ReplSetConf) Describe(ch chan<- *prometheus.Desc) {
113+
memberHidden.Describe(ch)
114+
memberArbiter.Describe(ch)
115+
memberBuildIndexes.Describe(ch)
116+
memberPriority.Describe(ch)
117+
memberVotes.Describe(ch)
118+
}
119+
120+
// GetReplSetConf returns the replica status info
121+
func GetReplSetConf(client *mongo.Client) *ReplSetConf {
122+
result := &OuterReplSetConf{}
123+
err := client.Database("admin").RunCommand(context.TODO(), bson.D{{"replSetGetConfig", 1}}).Decode(result)
124+
if err != nil {
125+
log.Error("Failed to get replSet config.")
126+
return nil
127+
}
128+
return &result.Config
129+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2017 Percona LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package mongod
16+
17+
import (
18+
"context"
19+
"testing"
20+
"time"
21+
22+
"github.com/stretchr/testify/assert"
23+
24+
"github.com/percona/mongodb_exporter/testutils"
25+
)
26+
27+
func TestGetReplSetConfDecodesFine(t *testing.T) {
28+
// setup
29+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
30+
defer cancel()
31+
client := testutils.MustGetConnectedReplSetClient(t, ctx)
32+
defer client.Disconnect(ctx)
33+
34+
// run
35+
status := GetReplSetConf(client)
36+
37+
// test
38+
assert.NotNil(t, status)
39+
}

collector/mongod/replset_status_test.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,20 @@ import (
2020
"time"
2121

2222
"github.com/stretchr/testify/assert"
23-
"go.mongodb.org/mongo-driver/mongo"
24-
"go.mongodb.org/mongo-driver/mongo/options"
23+
24+
"github.com/percona/mongodb_exporter/testutils"
2525
)
2626

2727
func TestGetReplSetStatusDecodesFine(t *testing.T) {
28+
// setup
2829
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
2930
defer cancel()
30-
opts := options.Client().
31-
ApplyURI("mongodb://127.0.0.1:27019/admin").
32-
SetReplicaSet("rs0").
33-
SetDirect(true).SetServerSelectionTimeout(time.Second)
34-
client, err := mongo.Connect(ctx, opts)
35-
if err != nil {
36-
t.Fatal(err)
37-
}
38-
defer client.Disconnect(context.Background())
31+
client := testutils.MustGetConnectedReplSetClient(t, ctx)
32+
defer client.Disconnect(ctx)
3933

34+
// run
4035
status := GetReplSetStatus(client)
36+
37+
// test
4138
assert.NotNil(t, status)
4239
}

collector/mongodb_collector.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,12 @@ func (exporter *MongodbCollector) collectMongod(client *mongo.Client, ch chan<-
309309
func (exporter *MongodbCollector) collectMongodReplSet(client *mongo.Client, ch chan<- prometheus.Metric) {
310310
exporter.collectMongod(client, ch)
311311

312+
log.Debug("Collecting ReplSetConf Metrics")
313+
replSetConf := mongod.GetReplSetConf(client)
314+
if replSetConf != nil {
315+
replSetConf.Export(ch)
316+
}
317+
312318
log.Debug("Collecting Replset Status")
313319
replSetStatus := mongod.GetReplSetStatus(client)
314320
if replSetStatus != nil {

testutils/mongo_client.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package testutils
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/pkg/errors"
9+
"go.mongodb.org/mongo-driver/mongo"
10+
"go.mongodb.org/mongo-driver/mongo/options"
11+
)
12+
13+
// MustGetConnectedReplSetClient return mongo.Client instance connected to server started in replicaSet mode.
14+
func MustGetConnectedReplSetClient(t *testing.T, ctx context.Context) *mongo.Client {
15+
opts := options.Client().
16+
ApplyURI("mongodb://127.0.0.1:27019/admin").
17+
SetReplicaSet("rs0").
18+
SetDirect(true).SetServerSelectionTimeout(time.Second)
19+
client, err := mongo.Connect(ctx, opts)
20+
if err != nil {
21+
t.Fatal(errors.Wrap(err, "Couldn't connect to MongoDB instance"))
22+
}
23+
24+
return client
25+
}

0 commit comments

Comments
 (0)