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:
- The `slowms` and `sampleRate` options are not used for profiling.
- The profiler only captures operations that match the filter.
+|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)
+ }
+}