diff --git a/collector/mongodb_collector.go b/collector/mongodb_collector.go index e7f90ad13..79f1f726b 100644 --- a/collector/mongodb_collector.go +++ b/collector/mongodb_collector.go @@ -15,8 +15,8 @@ package collector import ( - "errors" "fmt" + "sync" "time" "github.com/prometheus/client_golang/prometheus" @@ -38,6 +38,7 @@ type MongodbCollectorOpts struct { TLSPrivateKeyFile string TLSCaFile string TLSHostnameValidation bool + DbPoolLimit int } func (in MongodbCollectorOpts) toSessionOps() shared.MongoSessionOpts { @@ -48,6 +49,7 @@ func (in MongodbCollectorOpts) toSessionOps() shared.MongoSessionOpts { TLSPrivateKeyFile: in.TLSPrivateKeyFile, TLSCaFile: in.TLSCaFile, TLSHostnameValidation: in.TLSHostnameValidation, + PoolLimit: in.DbPoolLimit, } } @@ -57,6 +59,8 @@ type MongodbCollector struct { scrapesTotal prometheus.Counter lastScrapeError prometheus.Gauge lastScrapeDurationSeconds prometheus.Gauge + mongoSess *mgo.Session + mongoSessLock sync.Mutex } // NewMongodbCollector returns a new instance of a MongodbCollector. @@ -87,6 +91,31 @@ func NewMongodbCollector(opts MongodbCollectorOpts) *MongodbCollector { return exporter } +// getSession returns the cached *mgo.Session or creates a new session and returns it. +// Use sync.Mutex to avoid race condition around session creation. +func (exporter *MongodbCollector) getSession() *mgo.Session { + exporter.mongoSessLock.Lock() + defer exporter.mongoSessLock.Unlock() + + if exporter.mongoSess != nil { + return exporter.mongoSess.Copy() + } + exporter.mongoSess = shared.MongoSession(exporter.Opts.toSessionOps()) + if exporter.mongoSess != nil { + return exporter.mongoSess.Copy() + } + return nil +} + +// Close cleanly closes the mongo session if it exists. +func (exporter *MongodbCollector) Close() { + if exporter.mongoSess != nil { + exporter.mongoSessLock.Lock() + exporter.mongoSess.Close() + exporter.mongoSessLock.Unlock() + } +} + // Describe sends the super-set of all possible descriptors of metrics collected by this Collector // to the provided channel and returns once the last descriptor has been sent. // Part of prometheus.Collector interface. @@ -139,9 +168,9 @@ func (exporter *MongodbCollector) scrape(ch chan<- prometheus.Metric) { } }(time.Now()) - mongoSess := shared.MongoSession(exporter.Opts.toSessionOps()) + mongoSess := exporter.getSession() if mongoSess == nil { - err = errors.New("can't create mongo session") + log.Errorf("can't create mongo session to %s", exporter.Opts.URI) return } defer mongoSess.Close() diff --git a/mongodb_exporter.go b/mongodb_exporter.go index 018b32603..ea1f0e240 100644 --- a/mongodb_exporter.go +++ b/mongodb_exporter.go @@ -63,6 +63,8 @@ var ( " \tIf not provided: System default CAs are used.") tlsDisableHostnameValidationF = flag.Bool("mongodb.tls-disable-hostname-validation", false, "Do hostname validation for server connection.") + dbPoolLimit = flag.Int("mongodb.max-connections", 1, "Max number of pooled connections to the database.") + // FIXME currently ignored enabledGroupsFlag = flag.String("groups.enabled", "asserts,durability,background_flushing,connections,extra_info,global_lock,index_counters,network,op_counters,op_counters_repl,memory,locks,metrics", "Comma-separated list of groups to use, for more info see: docs.mongodb.org/manual/reference/command/serverStatus/") ) @@ -154,8 +156,8 @@ func startWebServer() { } handler := prometheusHandler() - - registerCollector() + collector := registerCollector() + defer collector.Close() if (*sslCertFileF == "") != (*sslKeyFileF == "") { log.Fatal("One of the flags -web.ssl-cert-file or -web.ssl-key-file is missing to enable HTTPS/TLS") @@ -210,7 +212,7 @@ func startWebServer() { } } -func registerCollector() { +func registerCollector() *collector.MongodbCollector { mongodbCollector := collector.NewMongodbCollector(collector.MongodbCollectorOpts{ URI: *uriF, TLSConnection: *tlsF, @@ -218,8 +220,10 @@ func registerCollector() { TLSPrivateKeyFile: *tlsPrivateKeyF, TLSCaFile: *tlsCAF, TLSHostnameValidation: !(*tlsDisableHostnameValidationF), + DbPoolLimit: *dbPoolLimit, }) prometheus.MustRegister(mongodbCollector) + return mongodbCollector } func main() { diff --git a/shared/connection.go b/shared/connection.go index 1aaa5acde..ccb8fd96d 100644 --- a/shared/connection.go +++ b/shared/connection.go @@ -52,6 +52,7 @@ type MongoSessionOpts struct { TLSPrivateKeyFile string TLSCaFile string TLSHostnameValidation bool + PoolLimit int } func MongoSession(opts MongoSessionOpts) *mgo.Session { @@ -76,8 +77,10 @@ func MongoSession(opts MongoSessionOpts) *mgo.Session { return nil } session.SetMode(mgo.Eventual, true) + session.SetPoolLimit(opts.PoolLimit) + session.SetPrefetch(0.00) session.SetSyncTimeout(syncMongodbTimeout) - session.SetSocketTimeout(0) + session.SetSocketTimeout(dialMongodbTimeout) return session }