From 9e7bf831665da9735bdb01c63dcd7d2be339d081 Mon Sep 17 00:00:00 2001 From: Nurlan Moldomurov Date: Mon, 11 Nov 2024 17:14:31 +0300 Subject: [PATCH] PMM-9870 fix collstats indexSizes metrics. --- exporter/collstats_collector.go | 13 +--- exporter/common.go | 21 +++--- exporter/metrics.go | 111 ++++++++++++++++++++++++-------- main.go | 12 +++- 4 files changed, 109 insertions(+), 48 deletions(-) diff --git a/exporter/collstats_collector.go b/exporter/collstats_collector.go index 65030610f..882260765 100644 --- a/exporter/collstats_collector.go +++ b/exporter/collstats_collector.go @@ -99,23 +99,16 @@ func (d *collstatsCollector) collect(ch chan<- prometheus.Metric) { aggregation := bson.D{ { - Key: "$collStats", Value: bson.M{ + Key: "$collStats", + Value: bson.M{ // TODO: PMM-9568 : Add support to handle histogram metrics "latencyStats": bson.M{"histograms": false}, "storageStats": bson.M{"scale": 1}, }, }, } - project := bson.D{ - { - Key: "$project", Value: bson.M{ - "storageStats.wiredTiger": 0, - "storageStats.indexDetails": 0, - }, - }, - } - cursor, err := client.Database(database).Collection(collection).Aggregate(d.ctx, mongo.Pipeline{aggregation, project}) + cursor, err := client.Database(database).Collection(collection).Aggregate(d.ctx, mongo.Pipeline{aggregation}) if err != nil { logger.Errorf("cannot get $collstats cursor for collection %s.%s: %s", database, collection, err) diff --git a/exporter/common.go b/exporter/common.go index 026453a11..4512d1272 100644 --- a/exporter/common.go +++ b/exporter/common.go @@ -79,7 +79,10 @@ func listCollections(ctx context.Context, client *mongo.Client, database string, // // - exclude: List of databases to be excluded. Useful to ignore system databases. func databases(ctx context.Context, client *mongo.Client, filterInNamespaces []string, exclude []string) ([]string, error) { - opts := &options.ListDatabasesOptions{NameOnly: pointer.ToBool(true), AuthorizedDatabases: pointer.ToBool(true)} + opts := &options.ListDatabasesOptions{ + NameOnly: pointer.ToBool(true), + AuthorizedDatabases: pointer.ToBool(true), + } filter := bson.D{} @@ -100,17 +103,16 @@ func databases(ctx context.Context, client *mongo.Client, filterInNamespaces []s } func makeExcludeFilter(exclude []string) *primitive.E { - filterExpressions := []bson.D{} + if len(exclude) == 0 { + return nil + } + var filterExpressions []bson.D for _, dbname := range exclude { filterExpressions = append(filterExpressions, bson.D{{Key: "name", Value: bson.D{{Key: "$ne", Value: dbname}}}}, ) } - if len(filterExpressions) == 0 { - return nil - } - return &primitive.E{Key: "$and", Value: filterExpressions} } @@ -118,6 +120,9 @@ func makeDBsFilter(filterInNamespaces []string) *primitive.E { filterExpressions := []bson.D{} nss := removeEmptyStrings(filterInNamespaces) + if len(nss) == 0 { + return nil + } for _, namespace := range nss { parts := strings.Split(namespace, ".") filterExpressions = append(filterExpressions, @@ -125,10 +130,6 @@ func makeDBsFilter(filterInNamespaces []string) *primitive.E { ) } - if len(filterExpressions) == 0 { - return nil - } - return &primitive.E{Key: "$or", Value: filterExpressions} } diff --git a/exporter/metrics.go b/exporter/metrics.go index edbd115b7..523da9658 100644 --- a/exporter/metrics.go +++ b/exporter/metrics.go @@ -93,25 +93,69 @@ var ( // mongodb_ss_opcounters{legacy_op_type="command"} 67923 // nodeToPDMetrics = map[string]string{ - "collStats.storageStats.indexDetails.": "index_name", - "globalLock.activeQueue.": "count_type", - "globalLock.locks.": "lock_type", - "serverStatus.asserts.": "assert_type", - "serverStatus.connections.": "conn_type", - "serverStatus.globalLock.currentQueue.": "count_type", - "serverStatus.metrics.commands.": "cmd_name", - "serverStatus.metrics.cursor.open.": "csr_type", - "serverStatus.metrics.document.": "doc_op_type", - "serverStatus.opLatencies.": "op_type", - "serverStatus.opReadConcernCounters.": "concern_type", - "serverStatus.opcounters.": "legacy_op_type", - "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", - "collstats.storageStats.indexSizes.": "index_name", + "collStats.storageStats.indexDetails.": "index_name", + "globalLock.activeQueue.": "count_type", + "globalLock.locks.": "lock_type", + "serverStatus.asserts.": "assert_type", + "serverStatus.connections.": "conn_type", + "serverStatus.globalLock.currentQueue.": "count_type", + "serverStatus.metrics.commands.": "cmd_name", + "serverStatus.metrics.cursor.open.": "csr_type", + "serverStatus.metrics.document.": "doc_op_type", + "serverStatus.opLatencies.": "op_type", + "serverStatus.opReadConcernCounters.": "concern_type", + "serverStatus.opcounters.": "legacy_op_type", + "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", + "collstats.storageStats.indexSizes.": "index_name", + "config.transactions.stats.storageStats.indexSizes.": "index_name", + "config.image_collection.stats.storageStats.indexSizes.": "index_name", + } + + // This map is used to add labels to some specific metrics. + // The difference from the case above that it works with middle nodes in the structure. + // For example, the fields under the storageStats.indexDetails. structure have this + // signature: + // + // "storageStats": primitive.M{ + // "indexDetails": primitive.M{ + // "_id_": primitive.M{ + // "LSM": primitive.M{ + // "bloom filter false positives": int32(0), + // "bloom filter hits": int32(0), + // "bloom filter misses": int32(0), + // ... + // }, + // "block-manager": primitive.M{ + // "allocations requiring file extension": int32(0), + // ... + // }, + // ... + // }, + // "name_1": primitive.M{ + // ... + // }, + // ... + // }, + // }, + // + // Applying the renaming rules, storageStats will become storageStats but instead of having metrics + // with the form storageStats.indexDetails.. where index_name is each one of + // the fields inside the structure (_id_, name_1, etc), those keys will become labels for the same + // metric name. The label name is defined as the value for each metric name in the map and the value + // the label will have is the field name in the structure. Example. + // + // mongodb_storageStats_indexDetails_index_name_LSM_bloom_filter_false_positives{index_name="_id_"} 0 + keyNodesToLabels = map[string]string{ + "storageStats.indexDetails.": "index_name", + "config.image_collection.stats.storageStats.indexDetails.": "index_name", + "config.transactions.stats.storageStats.indexDetails.": "index_name", + "config.image_collection.stats.storageStats.indexSizes.": "index_name", + "collstats.storageStats.indexDetails.": "index_name", } // Regular expressions used to make the metric name Prometheus-compatible @@ -237,9 +281,12 @@ func rawToPrometheusMetric(rm *rawMetric) (prometheus.Metric, error) { // by prometheus. For first level metrics, there is no prefix so we should use the metric name or // the help would be empty. func metricHelp(prefix, name string) string { - if prefix != "" { + if _, ok := nodeToPDMetrics[prefix]; ok { return prefix } + if prefix != "" { + return prefix + name + } return name } @@ -252,17 +299,29 @@ func makeMetrics(prefix string, m bson.M, labels map[string]string, compatibleMo } for k, val := range m { + nextPrefix := prefix + k + + var l = make(map[string]string) + if label, ok := keyNodesToLabels[prefix]; ok { + for k, v := range labels { + l[k] = v + } + l[label] = k + nextPrefix = prefix + label + } else { + l = labels + } switch v := val.(type) { case bson.M: - res = append(res, makeMetrics(prefix+k, v, labels, compatibleMode)...) + res = append(res, makeMetrics(nextPrefix, v, l, compatibleMode)...) case map[string]interface{}: - res = append(res, makeMetrics(prefix+k, v, labels, compatibleMode)...) + res = append(res, makeMetrics(nextPrefix, v, l, compatibleMode)...) case primitive.A: - res = append(res, processSlice(prefix, k, v, labels, compatibleMode)...) + res = append(res, processSlice(nextPrefix, v, l, compatibleMode)...) case []interface{}: continue default: - rm, err := makeRawMetric(prefix, k, v, labels) + rm, err := makeRawMetric(prefix, k, v, l) if err != nil { invalidMetric := prometheus.NewInvalidMetric(prometheus.NewInvalidDesc(err), err) res = append(res, invalidMetric) @@ -303,7 +362,7 @@ func makeMetrics(prefix string, m bson.M, labels map[string]string, compatibleMo // Extract maps from arrays. Only some structures like replicasets have arrays of members // and each member is represented by a map[string]interface{}. -func processSlice(prefix, k string, v []interface{}, commonLabels map[string]string, compatibleMode bool) []prometheus.Metric { +func processSlice(prefix string, v []interface{}, commonLabels map[string]string, compatibleMode bool) []prometheus.Metric { metrics := make([]prometheus.Metric, 0) labels := make(map[string]string) for name, value := range commonLabels { @@ -330,7 +389,7 @@ func processSlice(prefix, k string, v []interface{}, commonLabels map[string]str labels["member_state"] = state } - metrics = append(metrics, makeMetrics(prefix+k, s, labels, compatibleMode)...) + metrics = append(metrics, makeMetrics(prefix, s, labels, compatibleMode)...) } return metrics diff --git a/main.go b/main.go index ec2c64d55..83e8254ff 100644 --- a/main.go +++ b/main.go @@ -150,11 +150,19 @@ func buildExporter(opts GlobalFlags, uri string, log *logrus.Logger) *exporter.E nodeName = uriParsed.Host } + collStatsNamespaces := []string{} + if opts.CollStatsNamespaces != "" { + collStatsNamespaces = strings.Split(opts.CollStatsNamespaces, ",") + } + indexStatsCollections := []string{} + if opts.IndexStatsCollections != "" { + indexStatsCollections = strings.Split(opts.IndexStatsCollections, ",") + } exporterOpts := &exporter.Opts{ - CollStatsNamespaces: strings.Split(opts.CollStatsNamespaces, ","), + CollStatsNamespaces: collStatsNamespaces, CompatibleMode: opts.CompatibleMode, DiscoveringMode: opts.DiscoveringMode, - IndexStatsCollections: strings.Split(opts.IndexStatsCollections, ","), + IndexStatsCollections: indexStatsCollections, Logger: log, URI: uri, NodeName: nodeName,