diff --git a/REFERENCE.md b/REFERENCE.md index a78f7edc7..e7fc501dd 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -17,6 +17,7 @@ |--log.level|Only log messages with the given severity or above. Valid levels: [debug, info, warn, error, fatal]|--log.level="error"| |--collector.diagnosticdata|Enable collecting metrics from getDiagnosticData| |--collector.replicasetstatus|Enable collecting metrics from replSetGetStatus| +|--collector.serverstatus|Enable collecting metrics from serverStatus| |--collector.dbstats|Enable collecting metrics from dbStats|| |--collector.topmetrics|Enable collecting metrics from top admin command| |--collector.currentopmetrics|Enable collecting metrics from currentop admin command| diff --git a/exporter/exporter.go b/exporter/exporter.go index 4787abe7a..b03888e43 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -66,6 +66,7 @@ type Opts struct { EnableDBStatsFreeStorage bool EnableDiagnosticData bool EnableReplicasetStatus bool + EnableServerStatus bool EnableCurrentopMetrics bool EnableTopMetrics bool EnableIndexStats bool @@ -172,6 +173,7 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol e.opts.EnableCollStats = true e.opts.EnableTopMetrics = true e.opts.EnableReplicasetStatus = true + e.opts.EnableServerStatus = true e.opts.EnableIndexStats = true e.opts.EnableCurrentopMetrics = true e.opts.EnableProfile = true @@ -205,7 +207,9 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol registry.MustRegister(ic) } - if e.opts.EnableDiagnosticData && requestOpts.EnableDiagnosticData { + // getDiagnosticData does not return any data on mongos. + collectDiagnosticData := e.opts.EnableDiagnosticData && nodeType != typeMongos && requestOpts.EnableDiagnosticData + if collectDiagnosticData { ddc := newDiagnosticDataCollector(ctx, client, e.opts.Logger, e.opts.CompatibleMode, topologyInfo) registry.MustRegister(ddc) @@ -235,11 +239,20 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol registry.MustRegister(tc) } - // replSetGetStatus is not supported through mongos. - if e.opts.EnableReplicasetStatus && nodeType != typeMongos && requestOpts.EnableReplicasetStatus { - rsgsc := newReplicationSetStatusCollector(ctx, client, e.opts.Logger, - e.opts.CompatibleMode, topologyInfo) - registry.MustRegister(rsgsc) + // Only collect replica set and server status separately if we're not already fetching via diagnostic data + if !collectDiagnosticData { + // replSetGetStatus is not supported through mongos. + if e.opts.EnableReplicasetStatus && nodeType != typeMongos && requestOpts.EnableReplicasetStatus { + rsgsc := newReplicationSetStatusCollector(ctx, client, e.opts.Logger, + e.opts.CompatibleMode, topologyInfo) + registry.MustRegister(rsgsc) + } + + if e.opts.EnableServerStatus && requestOpts.EnableServerStatus { + ssc := newServerStatusCollector(ctx, client, e.opts.Logger, + e.opts.CompatibleMode, topologyInfo) + registry.MustRegister(ssc) + } } return registry @@ -303,6 +316,8 @@ func (e *Exporter) Handler() http.Handler { requestOpts.EnableDiagnosticData = true case "replicasetstatus": requestOpts.EnableReplicasetStatus = true + case "serverstatus": + requestOpts.EnableServerStatus = true case "dbstats": requestOpts.EnableDBStats = true case "topmetrics": diff --git a/exporter/replset_status_collector.go b/exporter/replset_status_collector.go index 65e9ceb7e..69664e76f 100644 --- a/exporter/replset_status_collector.go +++ b/exporter/replset_status_collector.go @@ -81,7 +81,7 @@ func (d *replSetGetStatusCollector) collect(ch chan<- prometheus.Metric) { logger.Debug("replSetGetStatus result:") debugResult(logger, m) - for _, metric := range makeMetrics("", m, d.topologyInfo.baseLabels(), d.compatibleMode) { + for _, metric := range makeMetrics("", bson.M{"replSetGetStatus": m}, d.topologyInfo.baseLabels(), d.compatibleMode) { ch <- metric } } diff --git a/exporter/replset_status_collector_test.go b/exporter/replset_status_collector_test.go index 12a5cbcb0..49c02239d 100644 --- a/exporter/replset_status_collector_test.go +++ b/exporter/replset_status_collector_test.go @@ -40,19 +40,19 @@ func TestReplsetStatusCollector(t *testing.T) { // The last \n at the end of this string is important expected := strings.NewReader(` - # HELP mongodb_myState myState - # TYPE mongodb_myState untyped - mongodb_myState 1 - # HELP mongodb_ok ok - # TYPE mongodb_ok untyped - mongodb_ok 1` + "\n") + # HELP mongodb_rs_myState replSetGetStatus. + # TYPE mongodb_rs_myState untyped + mongodb_rs_myState 1 + # HELP mongodb_rs_ok replSetGetStatus. + # TYPE mongodb_rs_ok untyped + mongodb_rs_ok 1` + "\n") // Filter metrics for 2 reasons: // 1. The result is huge // 2. We need to check against know values. Don't use metrics that return counters like uptime // or counters like the number of transactions because they won't return a known value to compare filter := []string{ - "mongodb_myState", - "mongodb_ok", + "mongodb_rs_myState", + "mongodb_rs_ok", } err := testutil.CollectAndCompare(c, expected, filter...) assert.NoError(t, err) diff --git a/exporter/serverstatus_collector.go b/exporter/serverstatus_collector.go new file mode 100644 index 000000000..b5375f15e --- /dev/null +++ b/exporter/serverstatus_collector.go @@ -0,0 +1,85 @@ +// mongodb_exporter +// Copyright (C) 2017 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package exporter + +import ( + "context" + + "github.com/prometheus/client_golang/prometheus" + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +type serverStatusCollector struct { + ctx context.Context + base *baseCollector + + compatibleMode bool + topologyInfo labelsGetter +} + +// newServerStatusCollector creates a collector for statistics on server status. +func newServerStatusCollector(ctx context.Context, client *mongo.Client, logger *logrus.Logger, compatible bool, topology labelsGetter) *serverStatusCollector { + return &serverStatusCollector{ + ctx: ctx, + base: newBaseCollector(client, logger), + compatibleMode: compatible, + topologyInfo: topology, + } +} + +func (d *serverStatusCollector) Describe(ch chan<- *prometheus.Desc) { + d.base.Describe(d.ctx, ch, d.collect) +} + +func (d *serverStatusCollector) Collect(ch chan<- prometheus.Metric) { + d.base.Collect(ch) +} + +func (d *serverStatusCollector) collect(ch chan<- prometheus.Metric) { + defer measureCollectTime(ch, "mongodb", "serverstatus")() + + logger := d.base.logger + client := d.base.client + + cmd := bson.D{ + { + Key: "serverStatus", Value: "1", + }, + { + Key: "metrics", Value: bson.M{ + // TODO: PMM-9568 : Add support to handle histogram metrics + "query": bson.M{"multiPlanner": bson.M{"histograms": false}}, + }, + }, + } + res := client.Database("admin").RunCommand(d.ctx, cmd) + + var m bson.M + if err := res.Decode(&m); err != nil { + ch <- prometheus.NewInvalidMetric(prometheus.NewInvalidDesc(err), err) + return + } + + logger.Debug("serverStatus result:") + debugResult(logger, m) + + for _, metric := range makeMetrics("", bson.M{"serverStatus": m}, d.topologyInfo.baseLabels(), d.compatibleMode) { + ch <- metric + } +} diff --git a/exporter/serverstatus_collector_test.go b/exporter/serverstatus_collector_test.go new file mode 100644 index 000000000..5a513c9dc --- /dev/null +++ b/exporter/serverstatus_collector_test.go @@ -0,0 +1,60 @@ +// mongodb_exporter +// Copyright (C) 2017 Percona LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package exporter + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + + "github.com/percona/mongodb_exporter/internal/tu" +) + +func TestServerStatusDataCollector(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + client := tu.DefaultTestClient(ctx, t) + + ti := labelsGetterMock{} + + c := newServerStatusCollector(ctx, client, logrus.New(), false, ti) + + // The last \n at the end of this string is important + expected := strings.NewReader(` + # HELP mongodb_ss_mem_bits serverStatus.mem. + # TYPE mongodb_ss_mem_bits untyped + mongodb_ss_mem_bits 64 + # HELP mongodb_ss_metrics_commands_connPoolSync_failed serverStatus.metrics.commands.connPoolSync. + # TYPE mongodb_ss_metrics_commands_connPoolSync_failed untyped + mongodb_ss_metrics_commands_connPoolSync_failed 0` + "\n") + // Filter metrics for 2 reasons: + // 1. The result is huge + // 2. We need to check against know values. Don't use metrics that return counters like uptime + // or counters like the number of transactions because they won't return a known value to compare + filter := []string{ + "mongodb_ss_mem_bits", + "mongodb_ss_metrics_commands_connPoolSync_failed", + } + err := testutil.CollectAndCompare(c, expected, filter...) + assert.NoError(t, err) +} diff --git a/main.go b/main.go index 1e5d13561..578b366fd 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,7 @@ type GlobalFlags struct { EnableDiagnosticData bool `name:"collector.diagnosticdata" help:"Enable collecting metrics from getDiagnosticData"` EnableReplicasetStatus bool `name:"collector.replicasetstatus" help:"Enable collecting metrics from replSetGetStatus"` + EnableServerStatus bool `name:"collector.serverstatus" help:"Enable collecting metrics from serverStatus"` EnableDBStats bool `name:"collector.dbstats" help:"Enable collecting metrics from dbStats"` EnableDBStatsFreeStorage bool `name:"collector.dbstatsfreestorage" help:"Enable collecting free space metrics from dbStats"` EnableTopMetrics bool `name:"collector.topmetrics" help:"Enable collecting metrics from top admin command"` @@ -143,6 +144,7 @@ func buildExporter(opts GlobalFlags) *exporter.Exporter { EnableDiagnosticData: opts.EnableDiagnosticData, EnableReplicasetStatus: opts.EnableReplicasetStatus, + EnableServerStatus: opts.EnableServerStatus, EnableCurrentopMetrics: opts.EnableCurrentopMetrics, EnableTopMetrics: opts.EnableTopMetrics, EnableDBStats: opts.EnableDBStats,