From 1e917bf2a65060e270dd5c51783e79c22f4e9089 Mon Sep 17 00:00:00 2001 From: Nurlan Moldomurov Date: Tue, 5 Nov 2024 12:35:08 +0300 Subject: [PATCH] PMM-13477 Support MongoDB 8.0 (#943) * PMM-13477 Support MongoDB 8.0 * PMM-13477 Support MongoDB 8.0 * PMM-13477 Support MongoDB 8.0 * PMM-13477 Fix tests. * PMM-13477 Fix imports and test matrix. * PMM-13477 A little bit of refactoring. * PMM-13477 Fix tests. * PMM-13477 Fix fcv tests. * PMM-13477 see logs on failure. * PMM-13477 fix tests. * PMM-13477 fix tests. * PMM-13477 fix imports. * PMM-13477 a bit of refactoring. * PMM-13477 fix test. * Update exporter/diagnostic_data_collector.go Co-authored-by: Alex Demidoff --------- Co-authored-by: Alex Demidoff --- .github/workflows/go.yml | 21 +++++ exporter/diagnostic_data_collector.go | 28 +++++-- exporter/diagnostic_data_collector_test.go | 63 ++++++++++----- exporter/encryption_info_test.go | 8 +- exporter/exporter.go | 20 +++-- exporter/exporter_test.go | 4 +- ...re_compatibility_version_collector_test.go | 8 ++ exporter/general_collector.go | 9 +-- exporter/metrics.go | 4 + exporter/secondary_lag_test.go | 3 +- exporter/server.go | 10 ++- exporter/topology_info.go | 8 +- exporter/topology_info_test.go | 3 +- exporter/v1_compatibility.go | 79 +++++++++++-------- 14 files changed, 184 insertions(+), 84 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d057866e9..9b9d81b4c 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -22,8 +22,15 @@ jobs: image: - mongo:4.4 - mongo:5.0 + - mongo:6.0 + - mongo:7.0 + - mongo:8.0 + - mongo:latest - percona/percona-server-mongodb:4.4 - percona/percona-server-mongodb:5.0 + - percona/percona-server-mongodb:6.0 + - percona/percona-server-mongodb:7.0 + - percona/percona-server-mongodb:latest runs-on: ubuntu-latest @@ -42,3 +49,17 @@ jobs: sleep 10 make test-race make test-cluster-clean + + - name: Run debug commands on failure + if: ${{ failure() }} + run: | + echo "--- Environment variables ---" + env | sort + echo "--- GO Environment ---" + go env | sort + echo "--- Git status ---" + git status + echo "--- Docker logs ---" + docker compose logs + echo "--- Docker ps ---" + docker compose ps -a diff --git a/exporter/diagnostic_data_collector.go b/exporter/diagnostic_data_collector.go index 3fb322bdc..57b4b7e96 100644 --- a/exporter/diagnostic_data_collector.go +++ b/exporter/diagnostic_data_collector.go @@ -35,12 +35,14 @@ type diagnosticDataCollector struct { ctx context.Context base *baseCollector + buildInfo buildInfo + compatibleMode bool topologyInfo labelsGetter } // newDiagnosticDataCollector creates a collector for diagnostic information. -func newDiagnosticDataCollector(ctx context.Context, client *mongo.Client, logger *logrus.Logger, compatible bool, topology labelsGetter) *diagnosticDataCollector { +func newDiagnosticDataCollector(ctx context.Context, client *mongo.Client, logger *logrus.Logger, compatible bool, topology labelsGetter, buildInfo buildInfo) *diagnosticDataCollector { nodeType, err := getNodeType(ctx, client) if err != nil { logger.WithFields(logrus.Fields{ @@ -57,6 +59,8 @@ func newDiagnosticDataCollector(ctx context.Context, client *mongo.Client, logge ctx: ctx, base: newBaseCollector(client, logger.WithFields(logrus.Fields{"collector": "diagnostic_data"})), + buildInfo: buildInfo, + compatibleMode: compatible, topologyInfo: topology, } @@ -112,6 +116,21 @@ func (d *diagnosticDataCollector) collect(ch chan<- prometheus.Metric) { logger.Debug("getDiagnosticData result") debugResult(logger, m) + // MongoDB 8.0 splits the diagnostic data into multiple blocks, so we need to merge them + if d.buildInfo.VersionArray[0] >= 8 { //nolint:gomnd + b := bson.M{} + for _, mv := range m { + block, ok := mv.(bson.M) + if !ok { + continue + } + for k, v := range block { + b[k] = v + } + } + m = b + } + metrics = makeMetrics("", m, d.topologyInfo.baseLabels(), d.compatibleMode) metrics = append(metrics, locksMetrics(logger, m)...) @@ -132,12 +151,7 @@ func (d *diagnosticDataCollector) collect(ch chan<- prometheus.Metric) { } if d.compatibleMode { - buildInfo, err := retrieveMongoDBBuildInfo(d.ctx, client, logger) - if err != nil { - logger.Errorf("cannot retrieve MongoDB buildInfo: %s", err) - } - - metrics = append(metrics, serverVersion(buildInfo)) + metrics = append(metrics, serverVersion(d.buildInfo)) if nodeType == typeArbiter { if hm := arbiterMetrics(d.ctx, client, logger); hm != nil { diff --git a/exporter/diagnostic_data_collector_test.go b/exporter/diagnostic_data_collector_test.go index 264701d6f..2f6f8a334 100644 --- a/exporter/diagnostic_data_collector_test.go +++ b/exporter/diagnostic_data_collector_test.go @@ -46,31 +46,36 @@ func TestDiagnosticDataCollector(t *testing.T) { logger := logrus.New() ti := labelsGetterMock{} - c := newDiagnosticDataCollector(ctx, client, logger, false, ti) + dbBuildInfo, err := retrieveMongoDBBuildInfo(ctx, client, logger.WithField("component", "test")) + require.NoError(t, err) + + c := newDiagnosticDataCollector(ctx, client, logger, false, ti, dbBuildInfo) + + prefix := "local.oplog.rs.stats.storageStats.wiredTiger" + if dbBuildInfo.VersionArray[0] < 7 { + prefix = "local.oplog.rs.stats.wiredTiger" + } // The last \n at the end of this string is important - expected := strings.NewReader(` - # HELP mongodb_oplog_stats_ok local.oplog.rs.stats. - # TYPE mongodb_oplog_stats_ok untyped - mongodb_oplog_stats_ok 1 - # HELP mongodb_oplog_stats_wt_btree_fixed_record_size local.oplog.rs.stats.wiredTiger.btree. + expectedString := fmt.Sprintf(` + # HELP mongodb_oplog_stats_wt_btree_fixed_record_size %s.btree. # TYPE mongodb_oplog_stats_wt_btree_fixed_record_size untyped mongodb_oplog_stats_wt_btree_fixed_record_size 0 - # HELP mongodb_oplog_stats_wt_transaction_update_conflicts local.oplog.rs.stats.wiredTiger.transaction. + # HELP mongodb_oplog_stats_wt_transaction_update_conflicts %s.transaction. # TYPE mongodb_oplog_stats_wt_transaction_update_conflicts untyped - mongodb_oplog_stats_wt_transaction_update_conflicts 0` + "\n") + mongodb_oplog_stats_wt_transaction_update_conflicts 0`, prefix, prefix) + expected := strings.NewReader(expectedString + "\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_oplog_stats_ok", "mongodb_oplog_stats_wt_btree_fixed_record_size", "mongodb_oplog_stats_wt_transaction_update_conflicts", } - err := testutil.CollectAndCompare(c, expected, filter...) + err = testutil.CollectAndCompare(c, expected, filter...) assert.NoError(t, err) } @@ -188,7 +193,10 @@ func TestCollectorWithCompatibleMode(t *testing.T) { logger := logrus.New() ti := labelsGetterMock{} - c := newDiagnosticDataCollector(ctx, client, logger, true, ti) + dbBuildInfo, err := retrieveMongoDBBuildInfo(ctx, client, logger.WithField("component", "test")) + require.NoError(t, err) + + c := newDiagnosticDataCollector(ctx, client, logger, true, ti, dbBuildInfo) err = testutil.CollectAndCompare(c, tt.expectedMetrics(), tt.metricsFilter...) assert.NoError(t, err) @@ -202,12 +210,16 @@ func TestAllDiagnosticDataCollectorMetrics(t *testing.T) { client := tu.DefaultTestClient(ctx, t) - ti := newTopologyInfo(ctx, client, logrus.New()) + logger := logrus.New() + ti := newTopologyInfo(ctx, client, logger) - c := newDiagnosticDataCollector(ctx, client, logrus.New(), true, ti) + dbBuildInfo, err := retrieveMongoDBBuildInfo(ctx, client, logger.WithField("component", "test")) + require.NoError(t, err) + + c := newDiagnosticDataCollector(ctx, client, logger, true, ti, dbBuildInfo) reg := prometheus.NewRegistry() - err := reg.Register(c) + err = reg.Register(c) require.NoError(t, err) metrics := helpers.CollectMetrics(c) actualMetrics := helpers.ReadMetrics(metrics) @@ -281,7 +293,11 @@ func TestDiagnosticDataErrors(t *testing.T) { logger, hook := logrustest.NewNullLogger() ti := newTopologyInfo(ctx, client, logger) - c := newDiagnosticDataCollector(ctx, client, logger, true, ti) + + dbBuildInfo, err := retrieveMongoDBBuildInfo(ctx, client, logger.WithField("component", "test")) + require.NoError(t, err) + + c := newDiagnosticDataCollector(ctx, client, logger, true, ti, dbBuildInfo) reg := prometheus.NewRegistry() err = reg.Register(c) @@ -318,11 +334,15 @@ func TestContextTimeout(t *testing.T) { client := tu.DefaultTestClient(ctx, t) - ti := newTopologyInfo(ctx, client, logrus.New()) + logger := logrus.New() + ti := newTopologyInfo(ctx, client, logger) + + dbBuildInfo, err := retrieveMongoDBBuildInfo(ctx, client, logger.WithField("component", "test")) + require.NoError(t, err) dbCount := 100 - err := addTestData(ctx, client, dbCount) + err = addTestData(ctx, client, dbCount) assert.NoError(t, err) defer cleanTestData(ctx, client, dbCount) //nolint:errcheck @@ -330,7 +350,7 @@ func TestContextTimeout(t *testing.T) { cctx, ccancel := context.WithCancel(context.Background()) ccancel() - c := newDiagnosticDataCollector(cctx, client, logrus.New(), true, ti) + c := newDiagnosticDataCollector(cctx, client, logger, true, ti, dbBuildInfo) // it should not panic helpers.CollectMetrics(c) } @@ -403,7 +423,7 @@ func cleanTestData(ctx context.Context, client *mongo.Client, count int) error { } func TestDisconnectedDiagnosticDataCollector(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() client := tu.DefaultTestClient(ctx, t) @@ -415,7 +435,10 @@ func TestDisconnectedDiagnosticDataCollector(t *testing.T) { ti := labelsGetterMock{} - c := newDiagnosticDataCollector(ctx, client, logger, true, ti) + dbBuildInfo, err := retrieveMongoDBBuildInfo(ctx, client, logger.WithField("component", "test")) + require.Error(t, err) + + c := newDiagnosticDataCollector(ctx, client, logger, true, ti, dbBuildInfo) // The last \n at the end of this string is important expected := strings.NewReader(` diff --git a/exporter/encryption_info_test.go b/exporter/encryption_info_test.go index de1b46923..c1565d06f 100644 --- a/exporter/encryption_info_test.go +++ b/exporter/encryption_info_test.go @@ -25,6 +25,7 @@ import ( "github.com/prometheus/client_golang/prometheus/testutil" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/percona/mongodb_exporter/internal/tu" ) @@ -45,7 +46,10 @@ func TestGetEncryptionInfo(t *testing.T) { ti := labelsGetterMock{} - c := newDiagnosticDataCollector(ctx, client, logger, true, ti) + dbBuildInfo, err := retrieveMongoDBBuildInfo(ctx, client, logger.WithField("component", "test")) + require.NoError(t, err) + + c := newDiagnosticDataCollector(ctx, client, logger, true, ti, dbBuildInfo) // The last \n at the end of this string is important expected := strings.NewReader(` @@ -61,6 +65,6 @@ func TestGetEncryptionInfo(t *testing.T) { "mongodb_version_info", } - err := testutil.CollectAndCompare(c, expected, filter...) + err = testutil.CollectAndCompare(c, expected, filter...) assert.NoError(t, err) } diff --git a/exporter/exporter.go b/exporter/exporter.go index 75a166061..52af296a9 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -134,16 +134,17 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol nodeType, err := getNodeType(ctx, client) if err != nil { - e.logger.Errorf("Registry - Cannot get node type to check if this is a mongos : %s", err) + e.logger.Errorf("Registry - Cannot get node type : %s", err) + } + + dbBuildInfo, err := retrieveMongoDBBuildInfo(ctx, client, e.logger.WithField("component", "buildInfo")) + if err != nil { + e.logger.Warnf("Registry - Cannot get MongoDB buildInfo: %s", err) } gc := newGeneralCollector(ctx, client, nodeType, e.opts.Logger) registry.MustRegister(gc) - if client == nil { - return registry - } - // Enable collectors like collstats and indexstats depending on the number of collections // present in the database. limitsOk := false @@ -203,7 +204,7 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol if e.opts.EnableDiagnosticData && requestOpts.EnableDiagnosticData { ddc := newDiagnosticDataCollector(ctx, client, e.opts.Logger, - e.opts.CompatibleMode, topologyInfo) + e.opts.CompatibleMode, topologyInfo, dbBuildInfo) registry.MustRegister(ddc) } @@ -335,13 +336,18 @@ func (e *Exporter) Handler() http.Handler { gatherers = append(gatherers, prometheus.DefaultGatherer) } + var registry *prometheus.Registry var ti *topologyInfo if client != nil { // Topology can change between requests, so we need to get it every time. ti = newTopologyInfo(ctx, client, e.logger) + registry = e.makeRegistry(ctx, client, ti, requestOpts) + } else { + registry = prometheus.NewRegistry() + gc := newGeneralCollector(ctx, client, "", e.opts.Logger) + registry.MustRegister(gc) } - registry := e.makeRegistry(ctx, client, ti, requestOpts) gatherers = append(gatherers, registry) // Delegate http serving to Prometheus client library, which will call collector.Collect. diff --git a/exporter/exporter_test.go b/exporter/exporter_test.go index 8f59c48e2..a00ca19d8 100644 --- a/exporter/exporter_test.go +++ b/exporter/exporter_test.go @@ -186,9 +186,7 @@ func TestMongoS(t *testing.T) { assert.NoError(t, err) e := New(exporterOpts) - - rsgsc := newReplicationSetStatusCollector(ctx, client, e.opts.Logger, - e.opts.CompatibleMode, new(labelsGetterMock)) + rsgsc := newReplicationSetStatusCollector(ctx, client, e.opts.Logger, e.opts.CompatibleMode, new(labelsGetterMock)) r := e.makeRegistry(ctx, client, new(labelsGetterMock), *e.opts) diff --git a/exporter/feature_compatibility_version_collector_test.go b/exporter/feature_compatibility_version_collector_test.go index 5196a8888..df2e9e264 100644 --- a/exporter/feature_compatibility_version_collector_test.go +++ b/exporter/feature_compatibility_version_collector_test.go @@ -55,6 +55,14 @@ func TestFCVCollector(t *testing.T) { mversion = "4.4" case mmv == "4.4": mversion = "4.2" + case mmv == "6.0": + mversion = "5.0" + case mmv == "7.0": + mversion = "6.0" + case mmv == "8.0": + mversion = "7.0" + default: + mversion = mmv } // The last \n at the end of this string is important diff --git a/exporter/general_collector.go b/exporter/general_collector.go index f7218f6e7..ebe9f3f4f 100644 --- a/exporter/general_collector.go +++ b/exporter/general_collector.go @@ -63,14 +63,11 @@ func mongodbUpMetric(ctx context.Context, client *mongo.Client, nodeType mongoDB } else { log.Errorf("error while checking mongodb connection: %s. mongo_up is set to 0", err.Error()) } - switch nodeType { //nolint:exhaustive - case typeMongos: - clusterRole = typeMongos - case typeArbiter: - clusterRole = typeArbiter - default: + case typeShardServer: clusterRole = typeMongod + default: + clusterRole = nodeType } } diff --git a/exporter/metrics.go b/exporter/metrics.go index ef06903b7..684509522 100644 --- a/exporter/metrics.go +++ b/exporter/metrics.go @@ -52,11 +52,14 @@ var ( prefixes = [][]string{ {"serverStatus.wiredTiger.transaction", "ss_wt_txn"}, {"serverStatus.wiredTiger", "ss_wt"}, + {"serverStatus.queues.execution", "ss_wt_concurrentTransactions"}, {"serverStatus", "ss"}, {"replSetGetStatus", "rs"}, {"systemMetrics", "sys"}, {"local.oplog.rs.stats.wiredTiger", "oplog_stats_wt"}, + {"local.oplog.rs.stats.storageStats.wiredTiger", "oplog_stats_wt"}, {"local.oplog.rs.stats", "oplog_stats"}, + {"local.oplog.rs.stats.storageStats", "oplog_stats"}, {"collstats_storage.wiredTiger", "collstats_storage_wt"}, {"collstats_storage.indexDetails", "collstats_storage_idx"}, {"collStats.storageStats", "collstats_storage"}, @@ -105,6 +108,7 @@ var ( "serverStatus.opcountersRepl.": "legacy_op_type", "serverStatus.transactions.commitTypes.": "commit_type", "serverStatus.wiredTiger.concurrentTransactions.": "txn_rw_type", + "serverStatus.queues.execution.": "txn_rw_type", "serverStatus.wiredTiger.perf.": "perf_bucket", "systemMetrics.disks.": "device_name", } diff --git a/exporter/secondary_lag_test.go b/exporter/secondary_lag_test.go index ce246f09b..bf12761ce 100644 --- a/exporter/secondary_lag_test.go +++ b/exporter/secondary_lag_test.go @@ -23,6 +23,7 @@ import ( "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/bson" @@ -126,7 +127,7 @@ func TestSecondaryLag(t *testing.T) { assert.NoError(t, err) m, _ = m["data"].(bson.M) - metrics := replSetMetrics(m) + metrics := replSetMetrics(m, logrus.WithField("component", "test")) var lag prometheus.Metric for _, m := range metrics { if strings.HasPrefix(m.Desc().String(), `Desc{fqName: "mongodb_mongod_replset_member_replication_lag"`) { diff --git a/exporter/server.go b/exporter/server.go index cb19b07eb..5c535c505 100644 --- a/exporter/server.go +++ b/exporter/server.go @@ -147,18 +147,24 @@ func OverallTargetsHandler(exporters []*Exporter, logger *logrus.Logger) http.Ha }() } + var registry *prometheus.Registry var ti *topologyInfo if client != nil { // Topology can change between requests, so we need to get it every time. ti = newTopologyInfo(ctx, client, e.logger) + registry = e.makeRegistry(ctx, client, ti, requestOpts) + } else { + registry = prometheus.NewRegistry() + gc := newGeneralCollector(ctx, client, "", e.opts.Logger) + registry.MustRegister(gc) } hostlabels := prometheus.Labels{ "instance": e.opts.NodeName, } - registry := NewGathererWrapper(e.makeRegistry(ctx, client, ti, requestOpts), hostlabels) - gatherers = append(gatherers, registry) + gw := NewGathererWrapper(registry, hostlabels) + gatherers = append(gatherers, gw) } // Delegate http serving to Prometheus client library, which will call collector.Collect. diff --git a/exporter/topology_info.go b/exporter/topology_info.go index 08d05d841..254f8795e 100644 --- a/exporter/topology_info.go +++ b/exporter/topology_info.go @@ -103,7 +103,7 @@ func (t *topologyInfo) loadLabels(ctx context.Context) error { t.labels = make(map[string]string) - role, err := getClusterRole(ctx, t.client) + role, err := getClusterRole(ctx, t.client, t.logger) if err != nil { return errors.Wrap(err, "cannot get node type for topology info") } @@ -157,13 +157,12 @@ func getNodeType(ctx context.Context, client *mongo.Client) (mongoDBNodeType, er return typeMongod, nil } -func getClusterRole(ctx context.Context, client *mongo.Client) (string, error) { +func getClusterRole(ctx context.Context, client *mongo.Client, logger *logrus.Entry) (string, error) { cmdOpts := primitive.M{} // Not always we can get this info. For example, we cannot get this for hidden hosts so // if there is an error, just ignore it res := client.Database("admin").RunCommand(ctx, primitive.D{ {Key: "getCmdLineOpts", Value: 1}, - {Key: "recordStats", Value: 1}, }) if res.Err() != nil { @@ -174,6 +173,9 @@ func getClusterRole(ctx context.Context, client *mongo.Client) (string, error) { return "", errors.Wrap(err, "cannot decode getCmdLineOpts response") } + logger.Debug("getCmdLineOpts response:") + debugResult(logger, cmdOpts) + if walkTo(cmdOpts, []string{"parsed", "sharding", "configDB"}) != nil { return "mongos", nil } diff --git a/exporter/topology_info_test.go b/exporter/topology_info_test.go index d1e4f364f..7d7d3ec1a 100644 --- a/exporter/topology_info_test.go +++ b/exporter/topology_info_test.go @@ -136,7 +136,8 @@ func TestGetClusterRole(t *testing.T) { require.NoError(t, err) client := tu.TestClient(ctx, port, t) - nodeType, err := getClusterRole(ctx, client) + logger := logrus.WithField("component", "test") + nodeType, err := getClusterRole(ctx, client, logger) assert.NoError(t, err) assert.Equal(t, tc.want, nodeType, fmt.Sprintf("container name: %s, port: %s", tc.containerName, port)) } diff --git a/exporter/v1_compatibility.go b/exporter/v1_compatibility.go index 3a3d49895..29fc2c7b5 100644 --- a/exporter/v1_compatibility.go +++ b/exporter/v1_compatibility.go @@ -473,10 +473,22 @@ func conversions() []conversion { oldName: "mongodb_mongod_wiredtiger_transactions_checkpoint_milliseconds_total", newName: "mongodb_ss_wt_txn_transaction_checkpoint_total_time_msecs", }, + { + oldName: "mongodb_mongod_wiredtiger_transactions_checkpoint_milliseconds_total", + newName: "mongodb_ss_wt_checkpoint_total_time_msecs", + }, { oldName: "mongodb_mongod_wiredtiger_transactions_running_checkpoints", newName: "mongodb_ss_wt_txn_transaction_checkpoint_currently_running", }, + { + oldName: "mongodb_mongod_wiredtiger_transactions_running_checkpoints", + newName: "mongodb_ss_wt_checkpoint_currently_running", + }, + { + oldName: "mongodb_ss_tcmalloc_tcmalloc_thread_cache_free_bytes", + newName: "mongodb_ss_tcmalloc_tcmalloc_thread_cache_free", + }, { oldName: "mongodb_mongod_wiredtiger_transactions_total", prefix: "mongodb_ss_wt_txn_transactions", @@ -577,6 +589,15 @@ func conversions() []conversion { "max_time_msecs": "max", }, }, + { + oldName: "mongodb_mongod_wiredtiger_transactions_checkpoint_milliseconds", + prefix: "mongodb_ss_wt_checkpoint", + suffixLabel: "type", + suffixMapping: map[string]string{ + "min_time_msecs": "min", + "max_time_msecs": "max", + }, + }, { oldName: "mongodb_mongod_global_lock_current_queue", prefix: "mongodb_mongod_global_lock_current_queue", @@ -812,8 +833,10 @@ func specialMetrics(ctx context.Context, client *mongo.Client, m bson.M, nodeTyp if nodeType != typeArbiter { metrics = append(metrics, myState(ctx, client)) - if rm := replSetMetrics(m); rm != nil { - metrics = append(metrics, rm...) + if replSetGetStatus, ok := m["replSetGetStatus"].(bson.M); ok { + if rm := replSetMetrics(replSetGetStatus, l); rm != nil { + metrics = append(metrics, rm...) + } } if nodeType != typeMongos { @@ -829,41 +852,32 @@ func specialMetrics(ctx context.Context, client *mongo.Client, m bson.M, nodeTyp } func retrieveMongoDBBuildInfo(ctx context.Context, client *mongo.Client, l *logrus.Entry) (buildInfo, error) { + if client == nil { + return buildInfo{}, errors.New("cannot get mongo build info: client is nil") + } buildInfoCmd := bson.D{bson.E{Key: "buildInfo", Value: 1}} res := client.Database("admin").RunCommand(ctx, buildInfoCmd) - var buildInfoDoc bson.M + var buildInfoDoc buildInfo err := res.Decode(&buildInfoDoc) if err != nil { return buildInfo{}, errors.Wrap(err, "Failed to run buildInfo command") } - modules, ok := buildInfoDoc["modules"].(bson.A) - if !ok { - return buildInfo{}, errors.Wrap(err, "Failed to cast module information variable") - } - - var bi buildInfo - if len(modules) > 0 && modules[0].(string) == "enterprise" { - bi.Edition = EnterpriseEdition + if len(buildInfoDoc.Modules) > 0 && buildInfoDoc.Modules[0] == "enterprise" { + buildInfoDoc.Edition = EnterpriseEdition } else { - bi.Edition = CommunityEdition + buildInfoDoc.Edition = CommunityEdition } - l.Debug("MongoDB edition: ", bi.Edition) + l.Debug("MongoDB edition: ", buildInfoDoc.Edition) - _, ok = buildInfoDoc["psmdbVersion"] - if ok { - bi.Vendor = PerconaVendor + if buildInfoDoc.PSMDBVersion != "" { + buildInfoDoc.Vendor = PerconaVendor } else { - bi.Vendor = MongoDBVendor + buildInfoDoc.Vendor = MongoDBVendor } - bi.Version, ok = buildInfoDoc["version"].(string) - if !ok { - return buildInfo{}, errors.Wrap(err, "Failed to cast version information variable") - } - - return bi, nil + return buildInfoDoc, nil } func storageEngine(m bson.M) (prometheus.Metric, error) { //nolint:ireturn @@ -982,17 +996,15 @@ func oplogStatus(ctx context.Context, client *mongo.Client) ([]prometheus.Metric return []prometheus.Metric{headMetric, tailMetric}, nil } -func replSetMetrics(d bson.M) []prometheus.Metric { - replSetGetStatus, ok := d["replSetGetStatus"].(bson.M) - if !ok { - return nil - } +func replSetMetrics(d bson.M, l *logrus.Entry) []prometheus.Metric { var repl proto.ReplicaSetStatus - b, err := bson.Marshal(replSetGetStatus) + b, err := bson.Marshal(d) if err != nil { + l.Warnf("cannot marshal replica set status: %s", err) return nil } if err := bson.Unmarshal(b, &repl); err != nil { + l.Warnf("cannot unmarshal replica set status: %s", err) return nil } @@ -1324,9 +1336,12 @@ type rawStatus struct { } type buildInfo struct { - Version string - Edition string - Vendor string + Version string `bson:"version"` + PSMDBVersion string `bson:"psmdbVersion"` + VersionArray []int `bson:"versionArray"` + Edition string + Vendor string + Modules []string `bson:"modules"` } func getDatabaseStatList(ctx context.Context, client *mongo.Client, l *logrus.Entry) *databaseStatList {