diff --git a/README.md b/README.md index 00b09a6bf..f20cefb7a 100644 --- a/README.md +++ b/README.md @@ -71,14 +71,25 @@ Connecting user should have sufficient rights to query needed stats: More info about roles in MongoDB [documentation](https://docs.mongodb.com/manual/reference/built-in-roles/#mongodb-authrole-clusterMonitor). #### Example -``` +```sh mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 ``` +#### MongoDB Authentication +You can supply the mongodb user/password direct in the `--mongodb.uri=` like `--mongodb.uri=mongodb://user:pass@127.0.0.1:17001`, you can also supply the mongodb user/password with `--mongodb.user=`, `--mongodb.password=` +but the user and password info will be leaked via `ps` or `top` command, for security issue, you can use `MONGODB_USER` and `MONGODB_PASSWORD` env variable to set user/password for given uri +```sh +MONGODB_USER=XXX MONGODB_PASSWORD=YYY mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 --mongodb.collstats-colls=db1.c1,db2.c2 +# or +export MONGODB_USER=XXX +export MONGODB_PASSWORD=YYY +mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 --mongodb.collstats-colls=db1.c1,db2.c2 +``` + #### Enabling collstats metrics gathering `--mongodb.collstats-colls` receives a list of databases and collections to monitor using collstats. Usage example: `--mongodb.collstats-colls=database1.collection1,database2.collection2` -``` +```sh mongodb_exporter_linux_amd64/mongodb_exporter --mongodb.uri=mongodb://127.0.0.1:17001 --mongodb.collstats-colls=db1.c1,db2.c2 ``` #### Enabling compatibility mode. @@ -95,6 +106,16 @@ HELP mongodb_mongod_wiredtiger_log_bytes_total mongodb_mongod_wiredtiger_log_byt # TYPE mongodb_mongod_wiredtiger_log_bytes_total untyped mongodb_mongod_wiredtiger_log_bytes_total{type="unwritten"} 2.6208e+06 ``` +#### Enabling profile metrics gathering +`--collector.profile` +To collect metrics, you need to enable the profiler in [MongoDB](https://www.mongodb.com/docs/manual/tutorial/manage-the-database-profiler/): +Usage example: `db.setProfilingLevel(2)` + +|Level|Description| +|-----|-----------| +|0| The profiler is off and does not collect any data. This is the default profiler level.| +|1| The profiler collects data for operations that take longer than the value of `slowms` or that match a filter.
When a filter is set: +|2|The profiler collects data for all operations.| #### Cluster role labels The exporter sets some topology labels in all metrics. diff --git a/REFERENCE.md b/REFERENCE.md index bf36356e7..a78f7edc7 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -19,9 +19,12 @@ |--collector.replicasetstatus|Enable collecting metrics from replSetGetStatus| |--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| |--collector.indexstats|Enable collecting metrics from $indexStats| |--collector.collstats|Enable collecting metrics from $collStats| |--collect-all|Enable all collectors. Same as specifying all --collector.\| |--collector.collstats-limit=0|Disable collstats, dbstats, topmetrics and indexstats collector if there are more than \ collections. 0=No limit| +|--collector.profile-time-ts=30|Set time for scrape slow queries| This interval must be synchronized with the Prometheus scrape interval| +|--collector.profile|Enable collecting metrics from profile| |--metrics.overridedescendingindex| Enable descending index name override to replace -1 with _DESC || |--version|Show version and exit| \ No newline at end of file diff --git a/exporter/currentop_collector.go b/exporter/currentop_collector.go new file mode 100644 index 000000000..392f825ab --- /dev/null +++ b/exporter/currentop_collector.go @@ -0,0 +1,144 @@ +// mongodb_exporter +// Copyright (C) 2017 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporter + +import ( + "context" + "strconv" + + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +type currentopCollector struct { + ctx context.Context + base *baseCollector + compatibleMode bool + topologyInfo labelsGetter +} + +var ErrInvalidOrMissingInprogEntry = errors.New("invalid or missing inprog entry in currentop results") + +// newCurrentopCollector creates a collector for being processed queries. +func newCurrentopCollector(ctx context.Context, client *mongo.Client, logger *logrus.Logger, + compatible bool, topology labelsGetter, +) *currentopCollector { + return ¤topCollector{ + ctx: ctx, + base: newBaseCollector(client, logger), + compatibleMode: compatible, + topologyInfo: topology, + } +} + +func (d *currentopCollector) Describe(ch chan<- *prometheus.Desc) { + d.base.Describe(d.ctx, ch, d.collect) +} + +func (d *currentopCollector) Collect(ch chan<- prometheus.Metric) { + d.base.Collect(ch) +} + +func (d *currentopCollector) collect(ch chan<- prometheus.Metric) { + defer measureCollectTime(ch, "mongodb", "currentop")() + + logger := d.base.logger + client := d.base.client + + // Get all requests that are being processed except system requests (admin and local). + cmd := bson.D{ + {Key: "currentOp", Value: true}, + {Key: "active", Value: true}, + {Key: "microsecs_running", Value: bson.D{ + {Key: "$exists", Value: true}, + }}, + {Key: "op", Value: bson.D{{Key: "$ne", Value: ""}}}, + {Key: "ns", Value: bson.D{ + {Key: "$ne", Value: ""}, + {Key: "$not", Value: bson.D{{Key: "$regex", Value: "^admin.*|^local.*"}}}, + }}, + } + res := client.Database("admin").RunCommand(d.ctx, cmd) + + var r primitive.M + if err := res.Decode(&r); err != nil { + ch <- prometheus.NewInvalidMetric(prometheus.NewInvalidDesc(err), err) + return + } + + logger.Debug("currentop response from MongoDB:") + debugResult(logger, r) + + inprog, ok := r["inprog"].(primitive.A) + + if !ok { + ch <- prometheus.NewInvalidMetric(prometheus.NewInvalidDesc(ErrInvalidOrMissingInprogEntry), + ErrInvalidOrMissingInprogEntry) + } + + for _, bsonMap := range inprog { + + bsonMapElement, ok := bsonMap.(primitive.M) + if !ok { + logger.Errorf("Invalid type primitive.M assertion for bsonMap: %T", bsonMapElement) + continue + } + opid, ok := bsonMapElement["opid"].(int32) + if !ok { + logger.Errorf("Invalid type int32 assertion for 'opid': %T", bsonMapElement) + continue + } + namespace, ok := bsonMapElement["ns"].(string) + if !ok { + logger.Errorf("Invalid type string assertion for 'ns': %T", bsonMapElement) + continue + } + db, collection := splitNamespace(namespace) + op, ok := bsonMapElement["op"].(string) + if !ok { + logger.Errorf("Invalid type string assertion for 'op': %T", bsonMapElement) + continue + } + desc, ok := bsonMapElement["desc"].(string) + if !ok { + logger.Errorf("Invalid type string assertion for 'desc': %T", bsonMapElement) + continue + } + microsecs_running, ok := bsonMapElement["microsecs_running"].(int64) + if !ok { + logger.Errorf("Invalid type int64 assertion for 'microsecs_running': %T", bsonMapElement) + continue + } + + labels := d.topologyInfo.baseLabels() + labels["opid"] = strconv.Itoa(int(opid)) + labels["op"] = op + labels["desc"] = desc + labels["database"] = db + labels["collection"] = collection + labels["ns"] = namespace + + m := primitive.M{"uptime": microsecs_running} + + for _, metric := range makeMetrics("currentop_query", m, labels, d.compatibleMode) { + ch <- metric + } + } +} diff --git a/exporter/currentop_collector_test.go b/exporter/currentop_collector_test.go new file mode 100644 index 000000000..5e9d07917 --- /dev/null +++ b/exporter/currentop_collector_test.go @@ -0,0 +1,77 @@ +// mongodb_exporter +// Copyright (C) 2017 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporter + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/bson" + + "github.com/percona/mongodb_exporter/internal/tu" +) + +func TestCurrentopCollector(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var wg sync.WaitGroup + + client := tu.DefaultTestClient(ctx, t) + + database := client.Database("testdb") + database.Drop(ctx) + + defer func() { + err := database.Drop(ctx) + assert.NoError(t, err) + }() + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 3; i++ { + coll := fmt.Sprintf("testcol_%02d", i) + _, err := database.Collection(coll).InsertOne(ctx, bson.M{"f1": 1, "f2": "2"}) + assert.NoError(t, err) + } + }() + + ti := labelsGetterMock{} + + c := newCurrentopCollector(ctx, client, logrus.New(), false, ti) + + // Filter metrics by reason: + // 1. The result will be different on different hardware + // 2. Can't check labels like 'decs' and 'opid' because they don't return a known value for comparison + // It looks like: + // # HELP mongodb_currentop_query_uptime currentop_query. + // # TYPE mongodb_currentop_query_uptime untyped + // mongodb_currentop_query_uptime{collection="testcol_00",database="testdb",decs="conn6365",ns="testdb.testcol_00",op="insert",opid="448307"} 2524 + + filter := []string{ + "mongodb_currentop_query_uptime", + } + + count := testutil.CollectAndCount(c, filter...) + assert.True(t, count > 0) + wg.Wait() +} diff --git a/exporter/exporter.go b/exporter/exporter.go index 65af03158..4787abe7a 100644 --- a/exporter/exporter.go +++ b/exporter/exporter.go @@ -59,15 +59,18 @@ type Opts struct { DisableDefaultRegistry bool DiscoveringMode bool GlobalConnPool bool + ProfileTimeTS int CollectAll bool EnableDBStats bool EnableDBStatsFreeStorage bool EnableDiagnosticData bool EnableReplicasetStatus bool + EnableCurrentopMetrics bool EnableTopMetrics bool EnableIndexStats bool EnableCollStats bool + EnableProfile bool EnableOverrideDescendingIndex bool @@ -170,6 +173,8 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol e.opts.EnableTopMetrics = true e.opts.EnableReplicasetStatus = true e.opts.EnableIndexStats = true + e.opts.EnableCurrentopMetrics = true + e.opts.EnableProfile = true } // arbiter only have isMaster privileges @@ -180,6 +185,8 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol e.opts.EnableTopMetrics = false e.opts.EnableReplicasetStatus = false e.opts.EnableIndexStats = false + e.opts.EnableCurrentopMetrics = false + e.opts.EnableProfile = false } // If we manually set the collection names we want or auto discovery is set. @@ -210,6 +217,18 @@ func (e *Exporter) makeRegistry(ctx context.Context, client *mongo.Client, topol registry.MustRegister(cc) } + if e.opts.EnableCurrentopMetrics && nodeType != typeMongos && limitsOk && requestOpts.EnableCurrentopMetrics { + coc := newCurrentopCollector(ctx, client, e.opts.Logger, + e.opts.CompatibleMode, topologyInfo) + registry.MustRegister(coc) + } + + if e.opts.EnableProfile && nodeType != typeMongos && limitsOk && requestOpts.EnableProfile && e.opts.ProfileTimeTS != 0 { + pc := newProfileCollector(ctx, client, e.opts.Logger, + e.opts.CompatibleMode, topologyInfo, e.opts.ProfileTimeTS) + registry.MustRegister(pc) + } + if e.opts.EnableTopMetrics && nodeType != typeMongos && limitsOk && requestOpts.EnableTopMetrics { tc := newTopCollector(ctx, client, e.opts.Logger, e.opts.CompatibleMode, topologyInfo) @@ -288,10 +307,14 @@ func (e *Exporter) Handler() http.Handler { requestOpts.EnableDBStats = true case "topmetrics": requestOpts.EnableTopMetrics = true + case "currentopmetrics": + requestOpts.EnableCurrentopMetrics = true case "indexstats": requestOpts.EnableIndexStats = true case "collstats": requestOpts.EnableCollStats = true + case "profile": + requestOpts.EnableProfile = true } } diff --git a/exporter/metrics.go b/exporter/metrics.go index 844dab611..e1040d7d7 100644 --- a/exporter/metrics.go +++ b/exporter/metrics.go @@ -208,7 +208,9 @@ func asFloat64(value interface{}) (*float64, error) { f = v case primitive.DateTime: f = float64(v) - case primitive.A, primitive.ObjectID, primitive.Timestamp, primitive.Binary, string, []uint8, time.Time: + case primitive.Timestamp: + f = float64(v.T) + case primitive.A, primitive.ObjectID, primitive.Binary, string, []uint8, time.Time: return nil, nil default: return nil, errors.Wrapf(errCannotHandleType, "%T", v) diff --git a/exporter/metrics_test.go b/exporter/metrics_test.go index 77469dbac..da0a00743 100644 --- a/exporter/metrics_test.go +++ b/exporter/metrics_test.go @@ -141,7 +141,7 @@ func TestMakeRawMetric(t *testing.T) { {value: float32(1.23), wantVal: pointer.ToFloat64(float64(float32(1.23)))}, {value: float64(1.23), wantVal: pointer.ToFloat64(1.23)}, {value: primitive.A{}, wantVal: nil}, - {value: primitive.Timestamp{}, wantVal: nil}, + {value: primitive.Timestamp{T: 123, I: 456}, wantVal: pointer.ToFloat64(123)}, {value: "zapp", wantVal: nil}, {value: []byte{}, wantVal: nil}, {value: time.Date(2020, 6, 15, 0, 0, 0, 0, time.UTC), wantVal: nil}, diff --git a/exporter/profile_status_collector.go b/exporter/profile_status_collector.go new file mode 100644 index 000000000..910cd1559 --- /dev/null +++ b/exporter/profile_status_collector.go @@ -0,0 +1,96 @@ +// mongodb_exporter +// Copyright (C) 2017 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporter + +import ( + "context" + "time" + + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +type profileCollector struct { + ctx context.Context + base *baseCollector + compatibleMode bool + topologyInfo labelsGetter + profiletimets int +} + +// newProfileCollector creates a collector for being processed queries. +func newProfileCollector(ctx context.Context, client *mongo.Client, logger *logrus.Logger, + compatible bool, topology labelsGetter, profileTimeTS int, +) *profileCollector { + return &profileCollector{ + ctx: ctx, + base: newBaseCollector(client, logger), + compatibleMode: compatible, + topologyInfo: topology, + profiletimets: profileTimeTS, + } +} + +func (d *profileCollector) Describe(ch chan<- *prometheus.Desc) { + d.base.Describe(d.ctx, ch, d.collect) +} + +func (d *profileCollector) Collect(ch chan<- prometheus.Metric) { + d.base.Collect(ch) +} + +func (d *profileCollector) collect(ch chan<- prometheus.Metric) { + defer measureCollectTime(ch, "mongodb", "profile")() + + logger := d.base.logger + client := d.base.client + timeScrape := d.profiletimets + + databases, err := databases(d.ctx, client, nil, nil) + if err != nil { + errors.Wrap(err, "cannot get the database names list") + return + } + + // Now time + '--collector.profile-time-ts' + ts := primitive.NewDateTimeFromTime(time.Now().Add(-time.Duration(time.Second * time.Duration(timeScrape)))) + + labels := d.topologyInfo.baseLabels() + + // Get all slow queries from all databases + cmd := bson.M{"ts": bson.M{"$gte": ts}} + for _, db := range databases { + res, err := client.Database(db).Collection("system.profile").CountDocuments(d.ctx, cmd) + if err != nil { + errors.Wrapf(err, "cannot read system.profile") + break + } + labels["database"] = db + + m := primitive.M{"count": res} + + logger.Debug("profile response from MongoDB:") + debugResult(logger, primitive.M{db: m}) + + for _, metric := range makeMetrics("profile_slow_query", m, labels, d.compatibleMode) { + ch <- metric + } + } +} diff --git a/exporter/profile_status_collector_test.go b/exporter/profile_status_collector_test.go new file mode 100644 index 000000000..df47d5a34 --- /dev/null +++ b/exporter/profile_status_collector_test.go @@ -0,0 +1,69 @@ +// mongodb_exporter +// Copyright (C) 2017 Percona LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exporter + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/bson" + + "github.com/percona/mongodb_exporter/internal/tu" +) + +func TestProfileCollector(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + client := tu.DefaultTestClient(ctx, t) + + database := client.Database("testdb") + database.Drop(ctx) //nolint + + defer func() { + err := database.Drop(ctx) + assert.NoError(t, err) + }() + + // Enable database profiler https://www.mongodb.com/docs/manual/tutorial/manage-the-database-profiler/ + cmd := bson.M{"profile": 2} + _ = database.RunCommand(ctx, cmd) + + ti := labelsGetterMock{} + + c := newProfileCollector(ctx, client, logrus.New(), false, ti, 30) + + expected := strings.NewReader(` + # HELP mongodb_profile_slow_query_count profile_slow_query. + # TYPE mongodb_profile_slow_query_count counter + mongodb_profile_slow_query_count{database="admin"} 0 + mongodb_profile_slow_query_count{database="config"} 0 + mongodb_profile_slow_query_count{database="local"} 0 + mongodb_profile_slow_query_count{database="testdb"} 0` + + "\n") + + filter := []string{ + "mongodb_profile_slow_query_count", + } + + err := testutil.CollectAndCompare(c, expected, filter...) + assert.NoError(t, err) +} diff --git a/main.go b/main.go index 839031a80..1e5d13561 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,8 @@ var ( // GlobalFlags has command line flags to configure the exporter. type GlobalFlags struct { + User string `name:"mongodb.user" help:"monitor user, need clusterMonitor role in admin db and read role in local db" env:"MONGODB_USER" placeholder:"monitorUser"` + Password string `name:"mongodb.password" help:"monitor user password" env:"MONGODB_PASSWORD" placeholder:"monitorPassword"` CollStatsNamespaces string `name:"mongodb.collstats-colls" help:"List of comma separared databases.collections to get $collStats" placeholder:"db1,db2.col2"` IndexStatsCollections string `name:"mongodb.indexstats-colls" help:"List of comma separared databases.collections to get $indexStats" placeholder:"db1.col1,db2.col2"` URI string `name:"mongodb.uri" help:"MongoDB connection URI" env:"MONGODB_URI" placeholder:"mongodb://user:pass@127.0.0.1:27017/admin?ssl=true"` @@ -49,8 +51,10 @@ type GlobalFlags struct { 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"` + EnableCurrentopMetrics bool `name:"collector.currentopmetrics" help:"Enable collecting metrics currentop admin command"` EnableIndexStats bool `name:"collector.indexstats" help:"Enable collecting metrics from $indexStats"` EnableCollStats bool `name:"collector.collstats" help:"Enable collecting metrics from $collStats"` + EnableProfile bool `name:"collector.profile" help:"Enable collecting metrics from profile"` EnableOverrideDescendingIndex bool `name:"metrics.overridedescendingindex" help:"Enable descending index name override to replace -1 with _DESC"` @@ -58,6 +62,8 @@ type GlobalFlags struct { CollStatsLimit int `name:"collector.collstats-limit" help:"Disable collstats, dbstats, topmetrics and indexstats collector if there are more than collections. 0=No limit" default:"0"` + ProfileTimeTS int `name:"collector.profile-time-ts" help:"Set time for scrape slow queries." default:"30"` + DiscoveringMode bool `name:"discovering-mode" help:"Enable autodiscover collections" negatable:""` CompatibleMode bool `name:"compatible-mode" help:"Enable old mongodb-exporter compatible metrics" negatable:""` Version bool `name:"version" help:"Show version and exit"` @@ -89,6 +95,21 @@ func main() { e.Run() } +func buildURI(uri string, user string, password string) string { + // IF user@pass not contained in uri AND custom user and pass supplied in arguments + // DO concat a new uri with user and pass arguments value + if !strings.Contains(uri, "@") && user != "" && password != "" { + // trim mongodb:// prefix to handle user and pass logic + uri = strings.TrimPrefix(uri, "mongodb://") + // add user and pass to the uri + uri = fmt.Sprintf("%s:%s@%s", user, password, uri) + } + if !strings.HasPrefix(uri, "mongodb") { + uri = "mongodb://" + uri + } + return uri +} + func buildExporter(opts GlobalFlags) *exporter.Exporter { log := logrus.New() @@ -103,10 +124,7 @@ func buildExporter(opts GlobalFlags) *exporter.Exporter { log.Debugf("Compatible mode: %v", opts.CompatibleMode) - if !strings.HasPrefix(opts.URI, "mongodb") { - log.Debugf("Prepending mongodb:// to the URI") - opts.URI = "mongodb://" + opts.URI - } + opts.URI = buildURI(opts.URI, opts.User, opts.Password) log.Debugf("Connection URI: %s", opts.URI) @@ -125,16 +143,19 @@ func buildExporter(opts GlobalFlags) *exporter.Exporter { EnableDiagnosticData: opts.EnableDiagnosticData, EnableReplicasetStatus: opts.EnableReplicasetStatus, + EnableCurrentopMetrics: opts.EnableCurrentopMetrics, EnableTopMetrics: opts.EnableTopMetrics, EnableDBStats: opts.EnableDBStats, EnableDBStatsFreeStorage: opts.EnableDBStatsFreeStorage, EnableIndexStats: opts.EnableIndexStats, EnableCollStats: opts.EnableCollStats, + EnableProfile: opts.EnableProfile, EnableOverrideDescendingIndex: opts.EnableOverrideDescendingIndex, CollStatsLimit: opts.CollStatsLimit, CollectAll: opts.CollectAll, + ProfileTimeTS: opts.ProfileTimeTS, } e := exporter.New(exporterOpts) diff --git a/main_test.go b/main_test.go index 886eb6d0f..bf37e38a0 100644 --- a/main_test.go +++ b/main_test.go @@ -17,6 +17,8 @@ package main import ( "testing" + + "github.com/stretchr/testify/assert" ) func TestBuildExporter(t *testing.T) { @@ -37,3 +39,77 @@ func TestBuildExporter(t *testing.T) { buildExporter(opts) } + +func TestBuildURI(t *testing.T) { + tests := []struct { + situation string + origin string + newUser string + newPassword string + expect string + }{ + { + situation: "uri with prefix and auth, and auth supplied in opt.User/Password", + origin: "mongodb://usr:pwd@127.0.0.1", + newUser: "xxx", + newPassword: "yyy", + expect: "mongodb://usr:pwd@127.0.0.1", + }, + { + situation: "uri with prefix and auth, no auth supplied in opt.User/Password", + origin: "mongodb://usr:pwd@127.0.0.1", + newUser: "", + newPassword: "", + expect: "mongodb://usr:pwd@127.0.0.1", + }, + { + situation: "uri with no prefix and auth, and auth supplied in opt.User/Password", + origin: "usr:pwd@127.0.0.1", + newUser: "xxx", + newPassword: "yyy", + expect: "mongodb://usr:pwd@127.0.0.1", + }, + { + situation: "uri with no prefix and auth, no auth supplied in opt.User/Password", + origin: "usr:pwd@127.0.0.1", + newUser: "", + newPassword: "", + expect: "mongodb://usr:pwd@127.0.0.1", + }, + { + situation: "uri with prefix and no auth, and auth supplied in opt.User/Password", + origin: "mongodb://127.0.0.1", + newUser: "xxx", + newPassword: "yyy", + expect: "mongodb://xxx:yyy@127.0.0.1", + }, + { + situation: "uri with prefix and no auth, no auth supplied in opt.User/Password", + origin: "mongodb://127.0.0.1", + newUser: "", + newPassword: "", + expect: "mongodb://127.0.0.1", + }, + { + situation: "uri with no prefix and no auth, and auth supplied in opt.User/Password", + origin: "127.0.0.1", + newUser: "xxx", + newPassword: "yyy", + expect: "mongodb://xxx:yyy@127.0.0.1", + }, + { + situation: "uri with no prefix and no auth, no auth supplied in opt.User/Password", + origin: "127.0.0.1", + newUser: "", + newPassword: "", + expect: "mongodb://127.0.0.1", + }, + } + for _, tc := range tests { + newUri := buildURI(tc.origin, tc.newUser, tc.newPassword) + // t.Logf("Origin: %s", tc.origin) + // t.Logf("Expect: %s", tc.expect) + // t.Logf("Result: %s", newUri) + assert.Equal(t, newUri, tc.expect) + } +}