pax_global_header00006660000000000000000000000064130153560760014520gustar00rootroot0000000000000052 comment=dc3ffef6aac84e440519a3ad2e0425ede08ac8e6 prometheus-mongodb-exporter-1.0.0/000077500000000000000000000000001301535607600172025ustar00rootroot00000000000000prometheus-mongodb-exporter-1.0.0/.gitignore000066400000000000000000000004711301535607600211740ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so *.out # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof *.swp mongodb_exporter vendor/ release/ prometheus-mongodb-exporter-1.0.0/Dockerfile000066400000000000000000000006371301535607600212020ustar00rootroot00000000000000FROM alpine:latest MAINTAINER David Cuadrado EXPOSE 9001 ENV GOPATH /go ENV APPPATH $GOPATH/src/github.com/dcu/mongodb_exporter COPY . $APPPATH RUN apk add --update -t build-deps go git mercurial libc-dev gcc libgcc \ && cd $APPPATH && go get -d && go build -o /bin/mongodb_exporter \ && apk del --purge build-deps && rm -rf $GOPATH ENTRYPOINT [ "/bin/mongodb_exporter" ] prometheus-mongodb-exporter-1.0.0/LICENSE000066400000000000000000000020721301535607600202100ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 David Cuadrado Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prometheus-mongodb-exporter-1.0.0/Makefile000066400000000000000000000014131301535607600206410ustar00rootroot00000000000000package = github.com/dcu/mongodb_exporter TAG := $(shell git tag | sort -r | head -n 1) test: go test github.com/dcu/mongodb_exporter/collector -cover -coverprofile=collector_coverage.out -short go tool cover -func=collector_coverage.out go test github.com/dcu/mongodb_exporter/shared -cover -coverprofile=shared_coverage.out -short go tool cover -func=shared_coverage.out @rm *.out deps: glide install build: deps go build mongodb_exporter.go release: deps mkdir -p release perl -p -i -e 's/{{VERSION}}/$(TAG)/g' mongodb_exporter.go GOOS=darwin GOARCH=amd64 go build -o release/mongodb_exporter-darwin-amd64 $(package) GOOS=linux GOARCH=amd64 go build -o release/mongodb_exporter-linux-amd64 $(package) perl -p -i -e 's/$(TAG)/{{VERSION}}/g' mongodb_exporter.go prometheus-mongodb-exporter-1.0.0/README.md000066400000000000000000000077041301535607600204710ustar00rootroot00000000000000# Mongodb Exporter MongoDB exporter for prometheus.io, written in go. ![screenshot](https://raw.githubusercontent.com/dcu/mongodb_exporter/321189c90831d5ad5a8c6fb04925a335b37f51b8/screenshots/mongodb-dashboard-1.png) ## Installing Download a [release](https://github.com/dcu/mongodb_exporter/releases) ## Building Requires [glide](https://github.com/Masterminds/glide) for dependency management git clone git@github.com:dcu/mongodb_exporter.git $GOPATH/src/github.com/dcu/mongodb_exporter cd $GOPATH/src/github.com/dcu/mongodb_exporter make build ./mongodb_exporter -h The mongodb url can contain credentials which can be seen by other users on the system when passed in as command line flag. To pass in the mongodb url securely, you can set the MONGODB_URL environment variable instead. ## Available groups of data Name | Description ---------|------------ asserts | The asserts group reports the number of asserts on the database. While assert errors are typically uncommon, if there are non-zero values for the asserts, you should check the log file for the mongod process for more information. In many cases these errors are trivial, but are worth investigating. durability | The durability group contains data regarding the mongod's journaling-related operations and performance. mongod must be running with journaling for these data to appear in the output of "serverStatus". background_flushing | mongod periodically flushes writes to disk. In the default configuration, this happens every 60 seconds. The background_flushing group contains data regarding these operations. Consider these values if you have concerns about write performance and journaling. connections | The connections groups contains data regarding the current status of incoming connections and availability of the database server. Use these values to assess the current load and capacity requirements of the server. extra_info | The extra_info group holds data collected by the mongod instance about the underlying system. Your system may only report a subset of these fields. global_lock | The global_lock group contains information regarding the database’s current lock state, historical lock status, current operation queue, and the number of active clients. index_counters | The index_counters groupp reports information regarding the state and use of indexes in MongoDB. network | The network group contains data regarding MongoDB’s network use. op_counters | The op_counters group provides an overview of database operations by type and makes it possible to analyze the load on the database in more granular manner. These numbers will grow over time and in response to database use. Analyze these values over time to track database utilization. op_counters_repl | The op_counters_repl group, similar to the op_counters data structure, provides an overview of database replication operations by type and makes it possible to analyze the load on the replica in more granular manner. These values only appear when the current host has replication enabled. These values will differ from the opcounters values because of how MongoDB serializes operations during replication. These numbers will grow over time in response to database use. Analyze these values over time to track database utilization. memory | The memory group holds information regarding the target system architecture of mongod and current memory use locks | The locks group containsdata that provides a granular report on MongoDB database-level lock use metrics | The metrics group holds a number of statistics that reflect the current use and state of a running mongod instance. cursors | The cursors group contains data regarding cursor state and use. This group is disabled by default because it is deprecated in mongodb >= 2.6. For more information see [the official documentation.](http://docs.mongodb.org/manual/reference/command/serverStatus/) ## Roadmap - Collect data from http://docs.mongodb.org/manual/reference/command/replSetGetStatus/ prometheus-mongodb-exporter-1.0.0/collector/000077500000000000000000000000001301535607600211705ustar00rootroot00000000000000prometheus-mongodb-exporter-1.0.0/collector/asserts.go000066400000000000000000000025441301535607600232100ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( assertsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "asserts_total", Help: "The asserts document reports the number of asserts on the database. While assert errors are typically uncommon, if there are non-zero values for the asserts, you should check the log file for the mongod process for more information. In many cases these errors are trivial, but are worth investigating.", }, []string{"type"}) ) // AssertsStats has the assets metrics type AssertsStats struct { Regular float64 `bson:"regular"` Warning float64 `bson:"warning"` Msg float64 `bson:"msg"` User float64 `bson:"user"` Rollovers float64 `bson:"rollovers"` } // Export exports the metrics to prometheus. func (asserts *AssertsStats) Export(ch chan<- prometheus.Metric) { assertsTotal.WithLabelValues("regular").Set(asserts.Regular) assertsTotal.WithLabelValues("warning").Set(asserts.Warning) assertsTotal.WithLabelValues("msg").Set(asserts.Msg) assertsTotal.WithLabelValues("user").Set(asserts.User) assertsTotal.WithLabelValues("rollovers").Set(asserts.Rollovers) assertsTotal.Collect(ch) } // Describe describes the metrics for prometheus func (asserts *AssertsStats) Describe(ch chan<- *prometheus.Desc) { assertsTotal.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/background_flushing.go000066400000000000000000000071321301535607600255400ustar00rootroot00000000000000package collector import ( "time" "github.com/prometheus/client_golang/prometheus" ) var ( backgroundFlushingflushesTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "background_flushing", Name: "flushes_total", Help: "flushes is a counter that collects the number of times the database has flushed all writes to disk. This value will grow as database runs for longer periods of time", }) backgroundFlushingtotalMilliseconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "background_flushing", Name: "total_milliseconds", Help: "The total_ms value provides the total number of milliseconds (ms) that the mongod processes have spent writing (i.e. flushing) data to disk. Because this is an absolute value, consider the value offlushes and average_ms to provide better context for this datum", }) backgroundFlushingaverageMilliseconds = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "background_flushing", Name: "average_milliseconds", Help: `The average_ms value describes the relationship between the number of flushes and the total amount of time that the database has spent writing data to disk. The larger flushes is, the more likely this value is likely to represent a "normal," time; however, abnormal data can skew this value`, }) backgroundFlushinglastMilliseconds = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "background_flushing", Name: "last_milliseconds", Help: "The value of the last_ms field is the amount of time, in milliseconds, that the last flush operation took to complete. Use this value to verify that the current performance of the server and is in line with the historical data provided by average_ms and total_ms", }) backgroundFlushinglastFinishedTime = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "background_flushing", Name: "last_finished_time", Help: "The last_finished field provides a timestamp of the last completed flush operation in the ISODateformat. If this value is more than a few minutes old relative to your server's current time and accounting for differences in time zone, restarting the database may result in some data loss", }) ) // FlushStats is the flush stats metrics type FlushStats struct { Flushes float64 `bson:"flushes"` TotalMs float64 `bson:"total_ms"` AverageMs float64 `bson:"average_ms"` LastMs float64 `bson:"last_ms"` LastFinished time.Time `bson:"last_finished"` } // Export exports the metrics for prometheus. func (flushStats *FlushStats) Export(ch chan<- prometheus.Metric) { backgroundFlushingflushesTotal.Set(flushStats.Flushes) backgroundFlushingtotalMilliseconds.Set(flushStats.TotalMs) backgroundFlushingaverageMilliseconds.Set(flushStats.AverageMs) backgroundFlushinglastMilliseconds.Set(flushStats.LastMs) backgroundFlushinglastFinishedTime.Set(float64(flushStats.LastFinished.Unix())) backgroundFlushingflushesTotal.Collect(ch) backgroundFlushingtotalMilliseconds.Collect(ch) backgroundFlushingaverageMilliseconds.Collect(ch) backgroundFlushinglastMilliseconds.Collect(ch) backgroundFlushinglastFinishedTime.Collect(ch) } // Describe describes the metrics for prometheus func (flushStats *FlushStats) Describe(ch chan<- *prometheus.Desc) { backgroundFlushingflushesTotal.Describe(ch) backgroundFlushingtotalMilliseconds.Describe(ch) backgroundFlushingaverageMilliseconds.Describe(ch) backgroundFlushinglastMilliseconds.Describe(ch) backgroundFlushinglastFinishedTime.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/connections.go000066400000000000000000000030661301535607600240460ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( connections = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Name: "connections", Help: "The connections sub document data regarding the current status of incoming connections and availability of the database server. Use these values to assess the current load and capacity requirements of the server", }, []string{"state"}) ) var ( connectionsMetricsCreatedTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "connections_metrics", Name: "created_total", Help: "totalCreated provides a count of all incoming connections created to the server. This number includes connections that have since closed", }) ) // ConnectionStats are connections metrics type ConnectionStats struct { Current float64 `bson:"current"` Available float64 `bson:"available"` TotalCreated float64 `bson:"totalCreated"` } // Export exports the data to prometheus. func (connectionStats *ConnectionStats) Export(ch chan<- prometheus.Metric) { connections.WithLabelValues("current").Set(connectionStats.Current) connections.WithLabelValues("available").Set(connectionStats.Available) connections.Collect(ch) connectionsMetricsCreatedTotal.Set(connectionStats.TotalCreated) connectionsMetricsCreatedTotal.Collect(ch) } // Describe describes the metrics for prometheus func (connectionStats *ConnectionStats) Describe(ch chan<- *prometheus.Desc) { connections.Describe(ch) connectionsMetricsCreatedTotal.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/cursors.go000066400000000000000000000020751301535607600232230ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( cursorsGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Name: "cursors", Help: "The cursors data structure contains data regarding cursor state and use", }, []string{"state"}) ) // Cursors are the cursor metrics type Cursors struct { TotalOpen float64 `bson:"totalOpen"` TimeOut float64 `bson:"timedOut"` TotalNoTimeout float64 `bson:"totalNoTimeout"` Pinned float64 `bson:"pinned"` } // Export exports the data to prometheus. func (cursors *Cursors) Export(ch chan<- prometheus.Metric) { cursorsGauge.WithLabelValues("total_open").Set(cursors.TotalOpen) cursorsGauge.WithLabelValues("timed_out").Set(cursors.TimeOut) cursorsGauge.WithLabelValues("total_no_timeout").Set(cursors.TotalNoTimeout) cursorsGauge.WithLabelValues("pinned").Set(cursors.Pinned) cursorsGauge.Collect(ch) } // Describe describes the metrics for prometheus func (cursors *Cursors) Describe(ch chan<- *prometheus.Desc) { cursorsGauge.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/database_status.go000066400000000000000000000074431301535607600246760ustar00rootroot00000000000000package collector import ( "strings" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" mgo "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" ) var ( indexSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "db", Name: "index_size_bytes", Help: "The total size in bytes of all indexes created on this database", }, []string{"db", "shard"}) dataSize = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "db", Name: "data_size_bytes", Help: "The total size in bytes of the uncompressed data held in this database", }, []string{"db", "shard"}) collectionsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "db", Name: "collections_total", Help: "Contains a count of the number of collections in that database", }, []string{"db", "shard"}) indexesTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "db", Name: "indexes_total", Help: "Contains a count of the total number of indexes across all collections in the database", }, []string{"db", "shard"}) objectsTotal = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "db", Name: "objects_total", Help: "Contains a count of the number of objects (i.e. documents) in the database across all collections", }, []string{"db", "shard"}) ) // DatabaseStatus represents stats about a database type DatabaseStatus struct { RawStatus // embed to collect top-level attributes Shards map[string]*RawStatus `bson:"raw,omitempty"` } // RawStatus represents stats about a database type RawStatus struct { Name string `bson:"db,omitempty"` IndexSize int `bson:"indexSize,omitempty"` DataSize int `bson:"dataSize,omitempty"` Collections int `bson:"collections,omitempty"` Objects int `bson:"objects,omitempty"` Indexes int `bson:"indexes,omitempty"` } // Export exports database stats to prometheus func (dbStatus *DatabaseStatus) Export(ch chan<- prometheus.Metric) { if len(dbStatus.Shards) > 0 { for shard, stats := range dbStatus.Shards { shard = strings.Split(shard, "/")[0] indexSize.WithLabelValues(stats.Name, shard).Set(float64(stats.IndexSize)) dataSize.WithLabelValues(stats.Name, shard).Set(float64(stats.DataSize)) collectionsTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Collections)) indexesTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Indexes)) objectsTotal.WithLabelValues(stats.Name, shard).Set(float64(stats.Objects)) } } else { indexSize.WithLabelValues(dbStatus.Name).Set(float64(dbStatus.IndexSize)) dataSize.WithLabelValues(dbStatus.Name).Set(float64(dbStatus.DataSize)) collectionsTotal.WithLabelValues(dbStatus.Name).Set(float64(dbStatus.Collections)) indexesTotal.WithLabelValues(dbStatus.Name).Set(float64(dbStatus.Indexes)) objectsTotal.WithLabelValues(dbStatus.Name).Set(float64(dbStatus.Objects)) } indexSize.Collect(ch) dataSize.Collect(ch) collectionsTotal.Collect(ch) indexesTotal.Collect(ch) objectsTotal.Collect(ch) indexSize.Reset() dataSize.Reset() collectionsTotal.Reset() indexesTotal.Reset() objectsTotal.Reset() } // Describe describes database stats for prometheus func (dbStatus *DatabaseStatus) Describe(ch chan<- *prometheus.Desc) { indexSize.Describe(ch) dataSize.Describe(ch) collectionsTotal.Describe(ch) indexesTotal.Describe(ch) objectsTotal.Describe(ch) } // GetDatabaseStatus returns stats for a given database func GetDatabaseStatus(session *mgo.Session, db string) *DatabaseStatus { dbStatus := &DatabaseStatus{} err := session.DB(db).Run(bson.D{{"dbStats", 1}, {"scale", 1}}, &dbStatus) if err != nil { glog.Error(err) return nil } return dbStatus } prometheus-mongodb-exporter-1.0.0/collector/durability.go000066400000000000000000000107031301535607600236700ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( durabilityCommits = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Name: "durability_commits", Help: "Durability commits", }, []string{"state"}) ) var ( durabilityJournaledMegabytes = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "durability", Name: "journaled_megabytes", Help: "The journaledMB provides the amount of data in megabytes (MB) written to journal during the last journal group commit interval", }) durabilityWriteToDataFilesMegabytes = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "durability", Name: "write_to_data_files_megabytes", Help: "The writeToDataFilesMB provides the amount of data in megabytes (MB) written from journal to the data files during the last journal group commit interval", }) durabilityCompression = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "durability", Name: "compression", Help: "The compression represents the compression ratio of the data written to the journal: ( journaled_size_of_data / uncompressed_size_of_data )", }) durabilityEarlyCommits = prometheus.NewSummary(prometheus.SummaryOpts{ Namespace: Namespace, Subsystem: "durability", Name: "early_commits", Help: "The earlyCommits value reflects the number of times MongoDB requested a commit before the scheduled journal group commit interval. Use this value to ensure that your journal group commit interval is not too long for your deployment", }) ) var ( durabilityTimeMilliseconds = prometheus.NewSummaryVec(prometheus.SummaryOpts{ Namespace: Namespace, Name: "durability_time_milliseconds", Help: "Summary of times spent during the journaling process.", }, []string{"stage"}) ) // DurTiming is the information about durability returned from the server. type DurTiming struct { Dt float64 `bson:"dt"` PrepLogBuffer float64 `bson:"prepLogBuffer"` WriteToJournal float64 `bson:"writeToJournal"` WriteToDataFiles float64 `bson:"writeToDataFiles"` RemapPrivateView float64 `bson:"remapPrivateView"` } // Export exports the data for the prometheus server. func (durTiming *DurTiming) Export(ch chan<- prometheus.Metric) { durabilityTimeMilliseconds.WithLabelValues("dt").Observe(durTiming.Dt) durabilityTimeMilliseconds.WithLabelValues("prep_log_buffer").Observe(durTiming.PrepLogBuffer) durabilityTimeMilliseconds.WithLabelValues("write_to_journal").Observe(durTiming.WriteToJournal) durabilityTimeMilliseconds.WithLabelValues("write_to_data_files").Observe(durTiming.WriteToDataFiles) durabilityTimeMilliseconds.WithLabelValues("remap_private_view").Observe(durTiming.RemapPrivateView) durabilityTimeMilliseconds.Collect(ch) } // DurStats are the stats related to durability. type DurStats struct { Commits float64 `bson:"commits"` JournaledMB float64 `bson:"journaledMB"` WriteToDataFilesMB float64 `bson:"writeToDataFilesMB"` Compression float64 `bson:"compression"` CommitsInWriteLock float64 `bson:"commitsInWriteLock"` EarlyCommits float64 `bson:"earlyCommits"` TimeMs DurTiming `bson:"timeMs"` } // Export export the durability stats for the prometheus server. func (durStats *DurStats) Export(ch chan<- prometheus.Metric) { durabilityCommits.WithLabelValues("written").Set(durStats.Commits) durabilityCommits.WithLabelValues("in_write_lock").Set(durStats.CommitsInWriteLock) durabilityJournaledMegabytes.Set(durStats.JournaledMB) durabilityWriteToDataFilesMegabytes.Set(durStats.WriteToDataFilesMB) durabilityCompression.Set(durStats.Compression) durabilityEarlyCommits.Observe(durStats.EarlyCommits) durStats.TimeMs.Export(ch) durStats.Collect(ch) } // Collect collects the metrics for prometheus func (durStats *DurStats) Collect(ch chan<- prometheus.Metric) { durabilityCommits.Collect(ch) durabilityJournaledMegabytes.Collect(ch) durabilityWriteToDataFilesMegabytes.Collect(ch) durabilityCompression.Collect(ch) durabilityEarlyCommits.Collect(ch) } // Describe describes the metrics for prometheus func (durStats *DurStats) Describe(ch chan<- *prometheus.Desc) { durabilityCommits.Describe(ch) durabilityJournaledMegabytes.Describe(ch) durabilityWriteToDataFilesMegabytes.Describe(ch) durabilityCompression.Describe(ch) durabilityEarlyCommits.Describe(ch) durabilityTimeMilliseconds.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/extra_info.go000066400000000000000000000032061301535607600236560ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( extraInfopageFaultsTotal = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "extra_info", Name: "page_faults_total", Help: "The page_faults Reports the total number of page faults that require disk operations. Page faults refer to operations that require the database server to access data which isn't available in active memory. The page_faults counter may increase dramatically during moments of poor performance and may correlate with limited memory environments and larger data sets. Limited and sporadic page faults do not necessarily indicate an issue", }) extraInfoheapUsageBytes = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "extra_info", Name: "heap_usage_bytes", Help: "The heap_usage_bytes field is only available on Unix/Linux systems, and reports the total size in bytes of heap space used by the database process", }) ) // ExtraInfo has extra info metrics type ExtraInfo struct { HeapUsageBytes float64 `bson:"heap_usage_bytes"` PageFaults float64 `bson:"page_faults"` } // Export exports the metrics to prometheus. func (extraInfo *ExtraInfo) Export(ch chan<- prometheus.Metric) { extraInfoheapUsageBytes.Set(extraInfo.HeapUsageBytes) extraInfopageFaultsTotal.Set(extraInfo.PageFaults) extraInfoheapUsageBytes.Collect(ch) extraInfopageFaultsTotal.Collect(ch) } // Describe describes the metrics for prometheus func (extraInfo *ExtraInfo) Describe(ch chan<- *prometheus.Desc) { extraInfoheapUsageBytes.Describe(ch) extraInfopageFaultsTotal.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/fixtures/000077500000000000000000000000001301535607600230415ustar00rootroot00000000000000prometheus-mongodb-exporter-1.0.0/collector/fixtures/server_status.bson000066400000000000000000000051551301535607600266430ustar00rootroot00000000000000m host localhostversion2.6.7processmongodpid! uptimesuptimeMillisuptimeEstimate6localTime2015-02-07T01:29:27.458ZassertsAregularwarningmsguserrolloversbackgroundFlushingmflushestotal_msaverage_ms@last_mslast_finished2015-02-06T21:25:57.012Zconnections3currentavailable2totalCreated1cursorsnote&deprecated, use server status metricsclientCursors_sizetotalOpenpinnedtotalNoTimeouttimedOutdurcommits journaledMBwriteToDataFilesMBcompressioncommitsInWriteLockearlyCommitstimeMs`dts prepLogBufferwriteToJournalwriteToDataFilesremapPrivateViewextra_info8notefields vary by platformpage_faultsNglobalLocktotalTime 127859430000lockTimeJlcurrentQueue*totalreaderswritersactiveClients*totalreaderswritersindexCountersDaccesseshitsmissesresetsmissRatiolocksd.RtimeLockedMicrosR5WJltimeAcquiringMicrosR^WƘadminRtimeLockedMicrosr+wtimeAcquiringMicrosrѕwlocalRtimeLockedMicrosr=owtimeAcquiringMicrosr{wa-dbRtimeLockedMicrosrlw timeAcquiringMicrosr*wnetwork1bytesIn bytesOut^numRequestsHopcountersNinsertӳqueryiupdatedeletegetmorecommand# opcountersReplNinsertqueryupdatedeletegetmorecommandwriteBacksQueuedmemYbits@resident virtualsupportedmapped \mappedWithJournalmetrics'cursorDtimedOutopen+noTimeoutpinnedtotaldocument;deletedinsertedӳreturnedupdatedI,getLastError:wtimenumtotalMilliswtimeoutsoperation0fastmodidhackscanAndOrder queryExecutor&scannedscannedObjectsOrecordmovesrepl5apply6batchesnumtotalMillisopsbuffer1countmaxSizeBytessizeBytesnetworkVbytesgetmoresnumtotalMillisopsreadersCreatedpreloadRdocsnumtotalMillisindexesnumtotalMillisstorageQfreelistBsearch5bucketExhaustedrequestsWscannedttl'deletedDocumentspassesjokprometheus-mongodb-exporter-1.0.0/collector/fixtures/server_status.json000066400000000000000000000074411301535607600266530ustar00rootroot00000000000000{ "host" : "localhost", "version" : "2.6.7", "process" : "mongod", "pid" : 2593, "uptime" : 127859, "uptimeMillis" : 127859430, "uptimeEstimate" : 13850, "localTime" : "2015-02-07T01:29:27.458Z", "asserts" : { "regular" : 0, "warning" : 0, "msg" : 0, "user" : 3, "rollovers" : 0 }, "backgroundFlushing" : { "flushes" : 3, "total_ms" : 23, "average_ms" : 7.666666666666667, "last_ms" : 7, "last_finished" : "2015-02-06T21:25:57.012Z" }, "connections" : { "current" : 1, "available" : 818, "totalCreated" : 4145 }, "cursors" : { "note" : "deprecated, use server status metrics", "clientCursors_size" : 0, "totalOpen" : 0, "pinned" : 0, "totalNoTimeout" : 0, "timedOut" : 3 }, "dur" : { "commits" : 10, "journaledMB" : 0, "writeToDataFilesMB" : 0, "compression" : 0, "commitsInWriteLock" : 0, "earlyCommits" : 0, "timeMs" : { "dt" : 3187, "prepLogBuffer" : 0, "writeToJournal" : 0, "writeToDataFiles" : 0, "remapPrivateView" : 0 } }, "extra_info" : { "note" : "fields vary by platform", "page_faults" : 1724156 }, "globalLock" : { "totalTime" : "127859430000", "lockTime" : 7097013, "currentQueue" : { "total" : 0, "readers" : 0, "writers" : 0 }, "activeClients" : { "total" : 0, "readers" : 0, "writers" : 0 } }, "indexCounters" : { "accesses" : 2094239, "hits" : 2094239, "misses" : 0, "resets" : 0, "missRatio" : 0 }, "locks" : { "." : { "timeLockedMicros" : { "R" : 1979803, "W" : 7097013 }, "timeAcquiringMicros" : { "R" : 1466095, "W" : 2005190 } }, "admin" : { "timeLockedMicros" : { "r" : 1452829, "w" : 0 }, "timeAcquiringMicros" : { "r" : 38353, "w" : 0 } }, "local" : { "timeLockedMicros" : { "r" : 1863485, "w" : 537 }, "timeAcquiringMicros" : { "r" : 293787, "w" : 2 } }, "a-db" : { "timeLockedMicros" : { "r" : 986732, "w" : 525 }, "timeAcquiringMicros" : { "r" : 45866, "w" : 21 } } }, "network" : { "bytesIn" : 162962349, "bytesOut" : 399007475, "numRequests" : 1263839 }, "opcounters" : { "insert" : 46035, "query" : 460137, "update" : 51878, "delete" : 123287, "getmore" : 20, "command" : 642339 }, "opcountersRepl" : { "insert" : 0, "query" : 0, "update" : 0, "delete" : 0, "getmore" : 0, "command" : 0 }, "writeBacksQueued" : false, "mem" : { "bits" : 64, "resident" : 10, "virtual" : 49653, "supported" : true, "mapped" : 23561, "mappedWithJournal" : 47122 }, "metrics" : { "cursor" : { "timedOut" : 3, "open" : { "noTimeout" : 0, "pinned" : 0, "total" : 0 } }, "document" : { "deleted" : 45726, "inserted" : 46035, "returned" : 426416, "updated" : 76873 }, "getLastError" : { "wtime" : { "num" : 0, "totalMillis" : 0 }, "wtimeouts" : 0 }, "operation" : { "fastmod" : 46466, "idhack" : 4259, "scanAndOrder" : 11 }, "queryExecutor" : { "scanned" : 295888337, "scannedObjects" : 290443242 }, "record" : { "moves" : 5306 }, "repl" : { "apply" : { "batches" : { "num" : 0, "totalMillis" : 0 }, "ops" : 0 }, "buffer" : { "count" : 0, "maxSizeBytes" : 268435456, "sizeBytes" : 0 }, "network" : { "bytes" : 0, "getmores" : { "num" : 0, "totalMillis" : 0 }, "ops" : 0, "readersCreated" : 0 }, "preload" : { "docs" : { "num" : 0, "totalMillis" : 0 }, "indexes" : { "num" : 0, "totalMillis" : 0 } } }, "storage" : { "freelist" : { "search" : { "bucketExhausted" : 271, "requests" : 51031, "scanned" : 260603 } } }, "ttl" : { "deletedDocuments" : 0, "passes" : 1130 } }, "ok" : 1 } prometheus-mongodb-exporter-1.0.0/collector/global_lock.go000066400000000000000000000070401301535607600237700ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( globalLockRatio = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "global_lock", Name: "ratio", Help: "The value of ratio displays the relationship between lockTime and totalTime. Low values indicate that operations have held the globalLock frequently for shorter periods of time. High values indicate that operations have held globalLock infrequently for longer periods of time", }) globalLockTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "global_lock", Name: "total", Help: "The value of totalTime represents the time, in microseconds, since the database last started and creation of the globalLock. This is roughly equivalent to total server uptime", }) globalLockLockTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "global_lock", Name: "lock_total", Help: "The value of lockTime represents the time, in microseconds, since the database last started, that the globalLock has been held", }) ) var ( globalLockCurrentQueue = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Name: "global_lock_current_queue", Help: "The currentQueue data structure value provides more granular information concerning the number of operations queued because of a lock", }, []string{"type"}) ) var ( globalLockClient = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Name: "global_lock_client", Help: "The activeClients data structure provides more granular information about the number of connected clients and the operation types (e.g. read or write) performed by these clients", }, []string{"type"}) ) // ClientStats metrics for client stats type ClientStats struct { Total float64 `bson:"total"` Readers float64 `bson:"readers"` Writers float64 `bson:"writers"` } // Export exports the metrics to prometheus func (clientStats *ClientStats) Export(ch chan<- prometheus.Metric) { globalLockClient.WithLabelValues("reader").Set(clientStats.Readers) globalLockClient.WithLabelValues("writer").Set(clientStats.Writers) } // QueueStats queue stats type QueueStats struct { Total float64 `bson:"total"` Readers float64 `bson:"readers"` Writers float64 `bson:"writers"` } // Export exports the metrics to prometheus func (queueStats *QueueStats) Export(ch chan<- prometheus.Metric) { globalLockCurrentQueue.WithLabelValues("reader").Set(queueStats.Readers) globalLockCurrentQueue.WithLabelValues("writer").Set(queueStats.Writers) } // GlobalLockStats global lock stats type GlobalLockStats struct { TotalTime float64 `bson:"totalTime"` LockTime float64 `bson:"lockTime"` Ratio float64 `bson:"ratio"` CurrentQueue *QueueStats `bson:"currentQueue"` ActiveClients *ClientStats `bson:"activeClients"` } // Export exports the metrics to prometheus func (globalLock *GlobalLockStats) Export(ch chan<- prometheus.Metric) { globalLockTotal.Set(globalLock.LockTime) globalLockRatio.Set(globalLock.Ratio) globalLock.CurrentQueue.Export(ch) globalLock.ActiveClients.Export(ch) globalLockTotal.Collect(ch) globalLockRatio.Collect(ch) globalLockCurrentQueue.Collect(ch) globalLockClient.Collect(ch) } // Describe describes the metrics for prometheus func (globalLock *GlobalLockStats) Describe(ch chan<- *prometheus.Desc) { globalLockTotal.Describe(ch) globalLockRatio.Describe(ch) globalLockCurrentQueue.Describe(ch) globalLockClient.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/index_counters.go000066400000000000000000000030531301535607600245510ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( indexCountersMissRatio = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "index_counters", Name: "miss_ratio", Help: "The missRatio value is the ratio of hits to misses. This value is typically 0 or approaching 0", }) ) var ( indexCountersTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "index_counters_total", Help: "Total indexes by type", }, []string{"type"}) ) //IndexCounterStats index counter stats type IndexCounterStats struct { Accesses float64 `bson:"accesses"` Hits float64 `bson:"hits"` Misses float64 `bson:"misses"` Resets float64 `bson:"resets"` MissRatio float64 `bson:"missRatio"` } // Export exports the data to prometheus. func (indexCountersStats *IndexCounterStats) Export(ch chan<- prometheus.Metric) { indexCountersTotal.WithLabelValues("accesses").Set(indexCountersStats.Accesses) indexCountersTotal.WithLabelValues("hits").Set(indexCountersStats.Hits) indexCountersTotal.WithLabelValues("misses").Set(indexCountersStats.Misses) indexCountersTotal.WithLabelValues("resets").Set(indexCountersStats.Resets) indexCountersMissRatio.Set(indexCountersStats.MissRatio) indexCountersTotal.Collect(ch) indexCountersMissRatio.Collect(ch) } // Describe describes the metrics for prometheus func (indexCountersStats *IndexCounterStats) Describe(ch chan<- *prometheus.Desc) { indexCountersTotal.Describe(ch) indexCountersMissRatio.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/locks.go000066400000000000000000000053201301535607600226320ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( locksTimeLockedGlobalMicrosecondsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "locks_time_locked_global_microseconds_total", Help: "amount of time in microseconds that any database has held the global lock", }, []string{"type", "database"}) ) var ( locksTimeLockedLocalMicrosecondsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "locks_time_locked_local_microseconds_total", Help: "amount of time in microseconds that any database has held the local lock", }, []string{"type", "database"}) ) var ( locksTimeAcquiringGlobalMicrosecondsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "locks_time_acquiring_global_microseconds_total", Help: "amount of time in microseconds that any database has spent waiting for the global lock", }, []string{"type", "database"}) ) // LockStatsMap is a map of lock stats type LockStatsMap map[string]LockStats // ReadWriteLockTimes information about the lock type ReadWriteLockTimes struct { Read float64 `bson:"R"` Write float64 `bson:"W"` ReadLower float64 `bson:"r"` WriteLower float64 `bson:"w"` } // LockStats lock stats type LockStats struct { TimeLockedMicros ReadWriteLockTimes `bson:"timeLockedMicros"` TimeAcquiringMicros ReadWriteLockTimes `bson:"timeAcquiringMicros"` } // Export exports the data to prometheus. func (locks LockStatsMap) Export(ch chan<- prometheus.Metric) { for key, locks := range locks { if key == "." { key = "dot" } locksTimeLockedGlobalMicrosecondsTotal.WithLabelValues("read", key).Set(locks.TimeLockedMicros.Read) locksTimeLockedGlobalMicrosecondsTotal.WithLabelValues("write", key).Set(locks.TimeLockedMicros.Write) locksTimeLockedLocalMicrosecondsTotal.WithLabelValues("read", key).Set(locks.TimeLockedMicros.ReadLower) locksTimeLockedLocalMicrosecondsTotal.WithLabelValues("write", key).Set(locks.TimeLockedMicros.WriteLower) locksTimeAcquiringGlobalMicrosecondsTotal.WithLabelValues("read", key).Set(locks.TimeAcquiringMicros.ReadLower) locksTimeAcquiringGlobalMicrosecondsTotal.WithLabelValues("write", key).Set(locks.TimeAcquiringMicros.WriteLower) } locksTimeLockedGlobalMicrosecondsTotal.Collect(ch) locksTimeLockedLocalMicrosecondsTotal.Collect(ch) locksTimeAcquiringGlobalMicrosecondsTotal.Collect(ch) } // Describe describes the metrics for prometheus func (locks LockStatsMap) Describe(ch chan<- *prometheus.Desc) { locksTimeLockedGlobalMicrosecondsTotal.Describe(ch) locksTimeLockedLocalMicrosecondsTotal.Describe(ch) locksTimeAcquiringGlobalMicrosecondsTotal.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/main_test.go000066400000000000000000000003721301535607600235040ustar00rootroot00000000000000package collector import ( "io/ioutil" "os" "testing" ) func TestMain(m *testing.M) { os.Exit(m.Run()) } func LoadFixture(name string) []byte { data, err := ioutil.ReadFile("fixtures/" + name) if err != nil { panic(err) } return data } prometheus-mongodb-exporter-1.0.0/collector/memory.go000066400000000000000000000022041301535607600230250ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( memory = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Name: "memory", Help: "The mem data structure holds information regarding the target system architecture of mongod and current memory use", }, []string{"type"}) ) // MemStats tracks the mem stats metrics. type MemStats struct { Bits float64 `bson:"bits"` Resident float64 `bson:"resident"` Virtual float64 `bson:"virtual"` Mapped float64 `bson:"mapped"` MappedWithJournal float64 `bson:"mappedWithJournal"` } // Export exports the data to prometheus. func (memStats *MemStats) Export(ch chan<- prometheus.Metric) { memory.WithLabelValues("resident").Set(memStats.Resident) memory.WithLabelValues("virtual").Set(memStats.Virtual) memory.WithLabelValues("mapped").Set(memStats.Mapped) memory.WithLabelValues("mapped_with_journal").Set(memStats.MappedWithJournal) memory.Collect(ch) } // Describe describes the metrics for prometheus func (memStats *MemStats) Describe(ch chan<- *prometheus.Desc) { memory.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/metrics.go000066400000000000000000000502761301535607600231770ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( metricsCursorTimedOutTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_cursor", Name: "timed_out_total", Help: "timedOut provides the total number of cursors that have timed out since the server process started. If this number is large or growing at a regular rate, this may indicate an application error", }) ) var ( metricsCursorOpen = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Name: "metrics_cursor_open", Help: "The open is an embedded document that contains data regarding open cursors", }, []string{"state"}) ) var ( metricsDocumentTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "metrics_document_total", Help: "The document holds a document of that reflect document access and modification patterns and data use. Compare these values to the data in the opcounters document, which track total number of operations", }, []string{"state"}) ) var ( metricsGetLastErrorWtimeNumTotal = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "metrics_get_last_error_wtime", Name: "num_total", Help: "num reports the total number of getLastError operations with a specified write concern (i.e. w) that wait for one or more members of a replica set to acknowledge the write operation (i.e. a w value greater than 1.)", }) metricsGetLastErrorWtimeTotalMilliseconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_get_last_error_wtime", Name: "total_milliseconds", Help: "total_millis reports the total amount of time in milliseconds that the mongod has spent performing getLastError operations with write concern (i.e. w) that wait for one or more members of a replica set to acknowledge the write operation (i.e. a w value greater than 1.)", }) ) var ( metricsGetLastErrorWtimeoutsTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_get_last_error", Name: "wtimeouts_total", Help: "wtimeouts reports the number of times that write concern operations have timed out as a result of the wtimeout threshold to getLastError.", }) ) var ( metricsOperationTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "metrics_operation_total", Help: "operation is a sub-document that holds counters for several types of update and query operations that MongoDB handles using special operation types", }, []string{"type"}) ) var ( metricsQueryExecutorTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "metrics_query_executor_total", Help: "queryExecutor is a document that reports data from the query execution system", }, []string{"state"}) ) var ( metricsRecordMovesTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_record", Name: "moves_total", Help: "moves reports the total number of times documents move within the on-disk representation of the MongoDB data set. Documents move as a result of operations that increase the size of the document beyond their allocated record size", }) ) var ( metricsReplApplyBatchesNumTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_apply_batches", Name: "num_total", Help: "num reports the total number of batches applied across all databases", }) metricsReplApplyBatchesTotalMilliseconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_apply_batches", Name: "total_milliseconds", Help: "total_millis reports the total amount of time the mongod has spent applying operations from the oplog", }) ) var ( metricsReplApplyOpsTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_apply", Name: "ops_total", Help: "ops reports the total number of oplog operations applied", }) ) var ( metricsReplBufferCount = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "metrics_repl_buffer", Name: "count", Help: "count reports the current number of operations in the oplog buffer", }) metricsReplBufferMaxSizeBytes = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_buffer", Name: "max_size_bytes", Help: "maxSizeBytes reports the maximum size of the buffer. This value is a constant setting in the mongod, and is not configurable", }) metricsReplBufferSizeBytes = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "metrics_repl_buffer", Name: "size_bytes", Help: "sizeBytes reports the current size of the contents of the oplog buffer", }) ) var ( metricsReplNetworkGetmoresNumTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_network_getmores", Name: "num_total", Help: "num reports the total number of getmore operations, which are operations that request an additional set of operations from the replication sync source.", }) metricsReplNetworkGetmoresTotalMilliseconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_network_getmores", Name: "total_milliseconds", Help: "total_millis reports the total amount of time required to collect data from getmore operations", }) ) var ( metricsReplNetworkBytesTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_network", Name: "bytes_total", Help: "bytes reports the total amount of data read from the replication sync source", }) metricsReplNetworkOpsTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_network", Name: "ops_total", Help: "ops reports the total number of operations read from the replication source.", }) metricsReplNetworkReadersCreatedTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_network", Name: "readers_created_total", Help: "readersCreated reports the total number of oplog query processes created. MongoDB will create a new oplog query any time an error occurs in the connection, including a timeout, or a network operation. Furthermore, readersCreated will increment every time MongoDB selects a new source fore replication.", }) ) var ( metricsReplOplogInsertNumTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_oplog_insert", Name: "num_total", Help: "num reports the total number of items inserted into the oplog.", }) metricsReplOplogInsertTotalMilliseconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_oplog_insert", Name: "total_milliseconds", Help: "total_millis reports the total amount of time spent for the mongod to insert data into the oplog.", }) ) var ( metricsReplOplogInsertBytesTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_oplog", Name: "insert_bytes_total", Help: "insertBytes the total size of documents inserted into the oplog.", }) ) var ( metricsReplPreloadDocsNumTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_preload_docs", Name: "num_total", Help: "num reports the total number of documents loaded during the pre-fetch stage of replication", }) metricsReplPreloadDocsTotalMilliseconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_preload_docs", Name: "total_milliseconds", Help: "total_millis reports the total amount of time spent loading documents as part of the pre-fetch stage of replication", }) ) var ( metricsReplPreloadIndexesNumTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_preload_indexes", Name: "num_total", Help: "num reports the total number of index entries loaded by members before updating documents as part of the pre-fetch stage of replication", }) metricsReplPreloadIndexesTotalMilliseconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_repl_preload_indexes", Name: "total_milliseconds", Help: "total_millis reports the total amount of time spent loading index entries as part of the pre-fetch stage of replication", }) ) var ( metricsStorageFreelistSearchTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "metrics_storage_freelist_search_total", Help: "metrics about searching records in the database.", }, []string{"type"}) ) var ( metricsTTLDeletedDocumentsTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_ttl", Name: "deleted_documents_total", Help: "deletedDocuments reports the total number of documents deleted from collections with a ttl index.", }) metricsTTLPassesTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "metrics_ttl", Name: "passes_total", Help: "passes reports the number of times the background process removes documents from collections with a ttl index", }) ) // DocumentStats are the stats associated to a document. type DocumentStats struct { Deleted float64 `bson:"deleted"` Inserted float64 `bson:"inserted"` Returned float64 `bson:"returned"` Updated float64 `bson:"updated"` } // Export exposes the document stats to be consumed by the prometheus server. func (documentStats *DocumentStats) Export(ch chan<- prometheus.Metric) { metricsDocumentTotal.WithLabelValues("deleted").Set(documentStats.Deleted) metricsDocumentTotal.WithLabelValues("inserted").Set(documentStats.Inserted) metricsDocumentTotal.WithLabelValues("returned").Set(documentStats.Returned) metricsDocumentTotal.WithLabelValues("updated").Set(documentStats.Updated) } // BenchmarkStats is bechmark info about an operation. type BenchmarkStats struct { Num float64 `bson:"num"` TotalMillis float64 `bson:"totalMillis"` } // GetLastErrorStats are the last error stats. type GetLastErrorStats struct { Wtimeouts float64 `bson:"wtimeouts"` Wtime *BenchmarkStats `bson:"wtime"` } // Export exposes the get last error stats. func (getLastErrorStats *GetLastErrorStats) Export(ch chan<- prometheus.Metric) { metricsGetLastErrorWtimeNumTotal.Set(getLastErrorStats.Wtime.Num) metricsGetLastErrorWtimeTotalMilliseconds.Set(getLastErrorStats.Wtime.TotalMillis) metricsGetLastErrorWtimeoutsTotal.Set(getLastErrorStats.Wtimeouts) } // OperationStats are the stats for some kind of operations. type OperationStats struct { Fastmod float64 `bson:"fastmod"` Idhack float64 `bson:"idhack"` ScanAndOrder float64 `bson:"scanAndOrder"` } // Export exports the operation stats. func (operationStats *OperationStats) Export(ch chan<- prometheus.Metric) { metricsOperationTotal.WithLabelValues("fastmod").Set(operationStats.Fastmod) metricsOperationTotal.WithLabelValues("idhack").Set(operationStats.Idhack) metricsOperationTotal.WithLabelValues("scan_and_order").Set(operationStats.ScanAndOrder) } // QueryExecutorStats are the stats associated with a query execution. type QueryExecutorStats struct { Scanned float64 `bson:"scanned"` ScannedObjects float64 `bson:"scannedObjects"` } // Export exports the query executor stats. func (queryExecutorStats *QueryExecutorStats) Export(ch chan<- prometheus.Metric) { metricsQueryExecutorTotal.WithLabelValues("scanned").Set(queryExecutorStats.Scanned) metricsQueryExecutorTotal.WithLabelValues("scanned_objects").Set(queryExecutorStats.ScannedObjects) } // RecordStats are stats associated with a record. type RecordStats struct { Moves float64 `bson:"moves"` } // Export exposes the record stats. func (recordStats *RecordStats) Export(ch chan<- prometheus.Metric) { metricsRecordMovesTotal.Set(recordStats.Moves) } // ApplyStats are the stats associated with the apply operation. type ApplyStats struct { Batches *BenchmarkStats `bson:"batches"` Ops float64 `bson:"ops"` } // Export exports the apply stats func (applyStats *ApplyStats) Export(ch chan<- prometheus.Metric) { metricsReplApplyOpsTotal.Set(applyStats.Ops) metricsReplApplyBatchesNumTotal.Set(applyStats.Batches.Num) metricsReplApplyBatchesTotalMilliseconds.Set(applyStats.Batches.TotalMillis) } // BufferStats are the stats associated with the buffer type BufferStats struct { Count float64 `bson:"count"` MaxSizeBytes float64 `bson:"maxSizeBytes"` SizeBytes float64 `bson:"sizeBytes"` } // Export exports the buffer stats. func (bufferStats *BufferStats) Export(ch chan<- prometheus.Metric) { metricsReplBufferCount.Set(bufferStats.Count) metricsReplBufferMaxSizeBytes.Set(bufferStats.MaxSizeBytes) metricsReplBufferSizeBytes.Set(bufferStats.SizeBytes) } // MetricsNetworkStats are the network stats. type MetricsNetworkStats struct { Bytes float64 `bson:"bytes"` Ops float64 `bson:"ops"` GetMores *BenchmarkStats `bson:"getmores"` ReadersCreated float64 `bson:"readersCreated"` } // Export exposes the network stats. func (metricsNetworkStats *MetricsNetworkStats) Export(ch chan<- prometheus.Metric) { metricsReplNetworkBytesTotal.Set(metricsNetworkStats.Bytes) metricsReplNetworkOpsTotal.Set(metricsNetworkStats.Ops) metricsReplNetworkReadersCreatedTotal.Set(metricsNetworkStats.ReadersCreated) metricsReplNetworkGetmoresNumTotal.Set(metricsNetworkStats.GetMores.Num) metricsReplNetworkGetmoresTotalMilliseconds.Set(metricsNetworkStats.GetMores.TotalMillis) } // ReplStats are the stats associated with the replication process. type ReplStats struct { Apply *ApplyStats `bson:"apply"` Buffer *BufferStats `bson:"buffer"` Network *MetricsNetworkStats `bson:"network"` PreloadStats *PreloadStats `bson:"preload"` } // Export exposes the replication stats. func (replStats *ReplStats) Export(ch chan<- prometheus.Metric) { replStats.Apply.Export(ch) replStats.Buffer.Export(ch) replStats.Network.Export(ch) replStats.PreloadStats.Export(ch) } // PreloadStats are the stats associated with preload operation. type PreloadStats struct { Docs *BenchmarkStats `bson:"docs"` Indexes *BenchmarkStats `bson:"indexes"` } // Export exposes the preload stats. func (preloadStats *PreloadStats) Export(ch chan<- prometheus.Metric) { metricsReplPreloadDocsNumTotal.Set(preloadStats.Docs.Num) metricsReplPreloadDocsTotalMilliseconds.Set(preloadStats.Docs.TotalMillis) metricsReplPreloadIndexesNumTotal.Set(preloadStats.Indexes.Num) metricsReplPreloadIndexesTotalMilliseconds.Set(preloadStats.Indexes.TotalMillis) } // StorageStats are the stats associated with the storage. type StorageStats struct { BucketExhausted float64 `bson:"freelist.search.bucketExhausted"` Requests float64 `bson:"freelist.search.requests"` Scanned float64 `bson:"freelist.search.scanned"` } // Export exports the storage stats. func (storageStats *StorageStats) Export(ch chan<- prometheus.Metric) { metricsStorageFreelistSearchTotal.WithLabelValues("bucket_exhausted").Set(storageStats.BucketExhausted) metricsStorageFreelistSearchTotal.WithLabelValues("requests").Set(storageStats.Requests) metricsStorageFreelistSearchTotal.WithLabelValues("scanned").Set(storageStats.Scanned) } // CursorStatsOpen are the stats for open cursors type CursorStatsOpen struct { NoTimeout float64 `bson:"noTimeout"` Pinned float64 `bson:"pinned"` Total float64 `bson:"total"` } // CursorStats are the stats for cursors type CursorStats struct { TimedOut float64 `bson:"timedOut"` Open *CursorStatsOpen `bson:"open"` } // Export exports the cursor stats. func (cursorStats *CursorStats) Export(ch chan<- prometheus.Metric) { metricsCursorTimedOutTotal.Set(cursorStats.TimedOut) metricsCursorOpen.WithLabelValues("timed_out").Set(cursorStats.Open.NoTimeout) metricsCursorOpen.WithLabelValues("pinned").Set(cursorStats.Open.Pinned) metricsCursorOpen.WithLabelValues("total").Set(cursorStats.Open.Total) } // MetricsStats are all stats associated with metrics of the system type MetricsStats struct { Document *DocumentStats `bson:"document"` GetLastError *GetLastErrorStats `bson:"getLastError"` Operation *OperationStats `bson:"operation"` QueryExecutor *QueryExecutorStats `bson:"queryExecutor"` Record *RecordStats `bson:"record"` Repl *ReplStats `bson:"repl"` Storage *StorageStats `bson:"storage"` Cursor *CursorStats `bson:"cursor"` } // Export exports the metrics stats. func (metricsStats *MetricsStats) Export(ch chan<- prometheus.Metric) { if metricsStats.Document != nil { metricsStats.Document.Export(ch) } if metricsStats.GetLastError != nil { metricsStats.GetLastError.Export(ch) } if metricsStats.Operation != nil { metricsStats.Operation.Export(ch) } if metricsStats.QueryExecutor != nil { metricsStats.QueryExecutor.Export(ch) } if metricsStats.Record != nil { metricsStats.Record.Export(ch) } if metricsStats.Repl != nil { metricsStats.Repl.Export(ch) } if metricsStats.Storage != nil { metricsStats.Storage.Export(ch) } if metricsStats.Cursor != nil { metricsStats.Cursor.Export(ch) } metricsCursorTimedOutTotal.Collect(ch) metricsCursorOpen.Collect(ch) metricsDocumentTotal.Collect(ch) metricsGetLastErrorWtimeNumTotal.Collect(ch) metricsGetLastErrorWtimeTotalMilliseconds.Collect(ch) metricsGetLastErrorWtimeoutsTotal.Collect(ch) metricsOperationTotal.Collect(ch) metricsQueryExecutorTotal.Collect(ch) metricsRecordMovesTotal.Collect(ch) metricsReplApplyBatchesNumTotal.Collect(ch) metricsReplApplyBatchesTotalMilliseconds.Collect(ch) metricsReplApplyOpsTotal.Collect(ch) metricsReplBufferCount.Collect(ch) metricsReplBufferMaxSizeBytes.Collect(ch) metricsReplBufferSizeBytes.Collect(ch) metricsReplNetworkGetmoresNumTotal.Collect(ch) metricsReplNetworkGetmoresTotalMilliseconds.Collect(ch) metricsReplNetworkBytesTotal.Collect(ch) metricsReplNetworkOpsTotal.Collect(ch) metricsReplNetworkReadersCreatedTotal.Collect(ch) metricsReplOplogInsertNumTotal.Collect(ch) metricsReplOplogInsertTotalMilliseconds.Collect(ch) metricsReplOplogInsertBytesTotal.Collect(ch) metricsReplPreloadDocsNumTotal.Collect(ch) metricsReplPreloadDocsTotalMilliseconds.Collect(ch) metricsReplPreloadIndexesNumTotal.Collect(ch) metricsReplPreloadIndexesTotalMilliseconds.Collect(ch) metricsStorageFreelistSearchTotal.Collect(ch) metricsTTLDeletedDocumentsTotal.Collect(ch) metricsTTLPassesTotal.Collect(ch) } // Describe describes the metrics for prometheus func (metricsStats *MetricsStats) Describe(ch chan<- *prometheus.Desc) { metricsCursorTimedOutTotal.Describe(ch) metricsCursorOpen.Describe(ch) metricsDocumentTotal.Describe(ch) metricsGetLastErrorWtimeNumTotal.Describe(ch) metricsGetLastErrorWtimeTotalMilliseconds.Describe(ch) metricsGetLastErrorWtimeoutsTotal.Describe(ch) metricsOperationTotal.Describe(ch) metricsQueryExecutorTotal.Describe(ch) metricsRecordMovesTotal.Describe(ch) metricsReplApplyBatchesNumTotal.Describe(ch) metricsReplApplyBatchesTotalMilliseconds.Describe(ch) metricsReplApplyOpsTotal.Describe(ch) metricsReplBufferCount.Describe(ch) metricsReplBufferMaxSizeBytes.Describe(ch) metricsReplBufferSizeBytes.Describe(ch) metricsReplNetworkGetmoresNumTotal.Describe(ch) metricsReplNetworkGetmoresTotalMilliseconds.Describe(ch) metricsReplNetworkBytesTotal.Describe(ch) metricsReplNetworkOpsTotal.Describe(ch) metricsReplNetworkReadersCreatedTotal.Describe(ch) metricsReplOplogInsertNumTotal.Describe(ch) metricsReplOplogInsertTotalMilliseconds.Describe(ch) metricsReplOplogInsertBytesTotal.Describe(ch) metricsReplPreloadDocsNumTotal.Describe(ch) metricsReplPreloadDocsTotalMilliseconds.Describe(ch) metricsReplPreloadIndexesNumTotal.Describe(ch) metricsReplPreloadIndexesTotalMilliseconds.Describe(ch) metricsStorageFreelistSearchTotal.Describe(ch) metricsTTLDeletedDocumentsTotal.Describe(ch) metricsTTLPassesTotal.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/mongodb_collector.go000066400000000000000000000066511301535607600252220ustar00rootroot00000000000000package collector import ( "github.com/dcu/mongodb_exporter/shared" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" "gopkg.in/mgo.v2" ) var ( // Namespace is the namespace of the metrics Namespace = "mongodb" ) // MongodbCollectorOpts is the options of the mongodb collector. type MongodbCollectorOpts struct { URI string TLSCertificateFile string TLSPrivateKeyFile string TLSCaFile string TLSHostnameValidation bool CollectReplSet bool CollectOplog bool CollectDatabaseMetrics bool } func (in MongodbCollectorOpts) toSessionOps() shared.MongoSessionOpts { return shared.MongoSessionOpts{ URI: in.URI, TLSCertificateFile: in.TLSCertificateFile, TLSPrivateKeyFile: in.TLSPrivateKeyFile, TLSCaFile: in.TLSCaFile, TLSHostnameValidation: in.TLSHostnameValidation, } } // MongodbCollector is in charge of collecting mongodb's metrics. type MongodbCollector struct { Opts MongodbCollectorOpts } // NewMongodbCollector returns a new instance of a MongodbCollector. func NewMongodbCollector(opts MongodbCollectorOpts) *MongodbCollector { exporter := &MongodbCollector{ Opts: opts, } return exporter } // Describe describes all mongodb's metrics. func (exporter *MongodbCollector) Describe(ch chan<- *prometheus.Desc) { (&ServerStatus{}).Describe(ch) (&ReplSetStatus{}).Describe(ch) (&DatabaseStatus{}).Describe(ch) } // Collect collects all mongodb's metrics. func (exporter *MongodbCollector) Collect(ch chan<- prometheus.Metric) { mongoSess := shared.MongoSession(exporter.Opts.toSessionOps()) if mongoSess != nil { defer mongoSess.Close() glog.Info("Collecting Server Status") exporter.collectServerStatus(mongoSess, ch) if exporter.Opts.CollectReplSet { glog.Info("Collecting ReplSet Status") exporter.collectReplSetStatus(mongoSess, ch) } if exporter.Opts.CollectOplog { glog.Info("Collecting Oplog Status") exporter.collectOplogStatus(mongoSess, ch) } if exporter.Opts.CollectDatabaseMetrics { glog.Info("Collecting Database Metrics") exporter.collectDatabaseStatus(mongoSess, ch) } } } func (exporter *MongodbCollector) collectServerStatus(session *mgo.Session, ch chan<- prometheus.Metric) *ServerStatus { serverStatus := GetServerStatus(session) if serverStatus != nil { glog.Info("exporting ServerStatus Metrics") serverStatus.Export(ch) } return serverStatus } func (exporter *MongodbCollector) collectReplSetStatus(session *mgo.Session, ch chan<- prometheus.Metric) *ReplSetStatus { replSetStatus := GetReplSetStatus(session) if replSetStatus != nil { glog.Info("exporting ReplSetStatus Metrics") replSetStatus.Export(ch) } return replSetStatus } func (exporter *MongodbCollector) collectOplogStatus(session *mgo.Session, ch chan<- prometheus.Metric) *OplogStatus { oplogStatus := GetOplogStatus(session) if oplogStatus != nil { glog.Info("exporting OplogStatus Metrics") oplogStatus.Export(ch) } return oplogStatus } func (exporter *MongodbCollector) collectDatabaseStatus(session *mgo.Session, ch chan<- prometheus.Metric) { all, err := session.DatabaseNames() if err != nil { glog.Error("Failed to get database names") return } for _, db := range all { if db != "admin" && db != "test" { dbStatus := GetDatabaseStatus(session, db) if dbStatus != nil { glog.Info("exporting Database Metrics") dbStatus.Export(ch) } } } } prometheus-mongodb-exporter-1.0.0/collector/mongodb_collector_test.go000066400000000000000000000014611301535607600262530ustar00rootroot00000000000000package collector import ( "testing" "github.com/dcu/mongodb_exporter/shared" "github.com/prometheus/client_golang/prometheus" ) func Test_CollectServerStatus(t *testing.T) { shared.ParseEnabledGroups("assers,durability,backgrond_flushing,connections,extra_info,global_lock,index_counters,network,op_counters,memory,locks,metrics,cursors") collector := NewMongodbCollector(MongodbCollectorOpts{URI: "localhost"}) go collector.Collect(nil) } func Test_DescribeCollector(t *testing.T) { collector := NewMongodbCollector(MongodbCollectorOpts{URI: "localhost"}) ch := make(chan *prometheus.Desc) go collector.Describe(ch) } func Test_CollectCollector(t *testing.T) { collector := NewMongodbCollector(MongodbCollectorOpts{URI: "localhost"}) ch := make(chan prometheus.Metric) go collector.Collect(ch) } prometheus-mongodb-exporter-1.0.0/collector/network.go000066400000000000000000000030621301535607600232110ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( networkBytesTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "network_bytes_total", Help: "The network data structure contains data regarding MongoDB's network use", }, []string{"state"}) ) var ( networkMetricsNumRequestsTotal = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "network_metrics", Name: "num_requests_total", Help: "The numRequests field is a counter of the total number of distinct requests that the server has received. Use this value to provide context for the bytesIn and bytesOut values to ensure that MongoDB's network utilization is consistent with expectations and application use", }) ) //NetworkStats network stats type NetworkStats struct { BytesIn float64 `bson:"bytesIn"` BytesOut float64 `bson:"bytesOut"` NumRequests float64 `bson:"numRequests"` } // Export exports the data to prometheus func (networkStats *NetworkStats) Export(ch chan<- prometheus.Metric) { networkBytesTotal.WithLabelValues("in_bytes").Set(networkStats.BytesIn) networkBytesTotal.WithLabelValues("out_bytes").Set(networkStats.BytesOut) networkMetricsNumRequestsTotal.Set(networkStats.NumRequests) networkMetricsNumRequestsTotal.Collect(ch) networkBytesTotal.Collect(ch) } // Describe describes the metrics for prometheus func (networkStats *NetworkStats) Describe(ch chan<- *prometheus.Desc) { networkMetricsNumRequestsTotal.Describe(ch) networkBytesTotal.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/op_counters.go000066400000000000000000000057011301535607600240620ustar00rootroot00000000000000package collector import ( "github.com/prometheus/client_golang/prometheus" ) var ( opCountersTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "op_counters_total", Help: "The opcounters data structure provides an overview of database operations by type and makes it possible to analyze the load on the database in more granular manner. These numbers will grow over time and in response to database use. Analyze these values over time to track database utilization", }, []string{"type"}) ) var ( opCountersReplTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "op_counters_repl_total", Help: "The opcountersRepl data structure, similar to the opcounters data structure, provides an overview of database replication operations by type and makes it possible to analyze the load on the replica in more granular manner. These values only appear when the current host has replication enabled", }, []string{"type"}) ) // OpcountersStats opcounters stats type OpcountersStats struct { Insert float64 `bson:"insert"` Query float64 `bson:"query"` Update float64 `bson:"update"` Delete float64 `bson:"delete"` GetMore float64 `bson:"getmore"` Command float64 `bson:"command"` } // Export exports the data to prometheus. func (opCounters *OpcountersStats) Export(ch chan<- prometheus.Metric) { opCountersTotal.WithLabelValues("insert").Set(opCounters.Insert) opCountersTotal.WithLabelValues("query").Set(opCounters.Query) opCountersTotal.WithLabelValues("update").Set(opCounters.Update) opCountersTotal.WithLabelValues("delete").Set(opCounters.Delete) opCountersTotal.WithLabelValues("getmore").Set(opCounters.GetMore) opCountersTotal.WithLabelValues("command").Set(opCounters.Command) opCountersTotal.Collect(ch) } // Describe describes the metrics for prometheus func (opCounters *OpcountersStats) Describe(ch chan<- *prometheus.Desc) { opCountersTotal.Describe(ch) } // OpcountersReplStats opcounters stats type OpcountersReplStats struct { Insert float64 `bson:"insert"` Query float64 `bson:"query"` Update float64 `bson:"update"` Delete float64 `bson:"delete"` GetMore float64 `bson:"getmore"` Command float64 `bson:"command"` } // Export exports the data to prometheus. func (opCounters *OpcountersReplStats) Export(ch chan<- prometheus.Metric) { opCountersReplTotal.WithLabelValues("insert").Set(opCounters.Insert) opCountersReplTotal.WithLabelValues("query").Set(opCounters.Query) opCountersReplTotal.WithLabelValues("update").Set(opCounters.Update) opCountersReplTotal.WithLabelValues("delete").Set(opCounters.Delete) opCountersReplTotal.WithLabelValues("getmore").Set(opCounters.GetMore) opCountersReplTotal.WithLabelValues("command").Set(opCounters.Command) opCountersReplTotal.Collect(ch) } // Describe describes the metrics for prometheus func (opCounters *OpcountersReplStats) Describe(ch chan<- *prometheus.Desc) { opCountersReplTotal.Describe(ch) } prometheus-mongodb-exporter-1.0.0/collector/oplog_status.go000066400000000000000000000102061301535607600242410ustar00rootroot00000000000000package collector import ( "time" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" ) var ( oplogStatusCount = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "replset_oplog", Name: "items_total", Help: "The total number of changes in the oplog", }) oplogStatusHeadTimestamp = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "replset_oplog", Name: "head_timestamp", Help: "The timestamp of the newest change in the oplog", }) oplogStatusTailTimestamp = prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "replset_oplog", Name: "tail_timestamp", Help: "The timestamp of the oldest change in the oplog", }) oplogStatusSizeBytes = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: "replset_oplog", Name: "size_bytes", Help: "Size of oplog in bytes", }, []string{"type"}) ) // OplogCollectionStats represents metrics about an oplog collection type OplogCollectionStats struct { Count float64 `bson:"count"` Size float64 `bson:"size"` StorageSize float64 `bson:"storageSize"` } // OplogStatus represents oplog metrics type OplogStatus struct { TailTimestamp float64 HeadTimestamp float64 CollectionStats *OplogCollectionStats } // BsonMongoTimestampToUnix converts a mongo timestamp to UNIX time // there's gotta be a better way to do this, but it works for now :/ func BsonMongoTimestampToUnix(timestamp bson.MongoTimestamp) float64 { return float64(timestamp >> 32) } // GetOplogTimestamp fetches the latest oplog timestamp func GetOplogTimestamp(session *mgo.Session, returnTail bool) (float64, error) { sortBy := "$natural" if returnTail { sortBy = "-$natural" } var ( err error tries int result struct { Timestamp bson.MongoTimestamp `bson:"ts"` } ) maxTries := 2 for tries < maxTries { err = session.DB("local").C("oplog.rs").Find(nil).Sort(sortBy).Limit(1).One(&result) if err != nil { tries++ time.Sleep(500 * time.Millisecond) } else { return BsonMongoTimestampToUnix(result.Timestamp), err } } return 0, err } // GetOplogCollectionStats fetches oplog collection stats func GetOplogCollectionStats(session *mgo.Session) (*OplogCollectionStats, error) { results := &OplogCollectionStats{} err := session.DB("local").Run(bson.M{"collStats": "oplog.rs"}, &results) return results, err } // Export exports metrics to Prometheus func (status *OplogStatus) Export(ch chan<- prometheus.Metric) { oplogStatusSizeBytes.WithLabelValues("current").Set(0) oplogStatusSizeBytes.WithLabelValues("storage").Set(0) if status.CollectionStats != nil { oplogStatusCount.Set(status.CollectionStats.Count) oplogStatusSizeBytes.WithLabelValues("current").Set(status.CollectionStats.Size) oplogStatusSizeBytes.WithLabelValues("storage").Set(status.CollectionStats.StorageSize) } if status.HeadTimestamp != 0 && status.TailTimestamp != 0 { oplogStatusHeadTimestamp.Set(status.HeadTimestamp) oplogStatusTailTimestamp.Set(status.TailTimestamp) } oplogStatusCount.Collect(ch) oplogStatusHeadTimestamp.Collect(ch) oplogStatusTailTimestamp.Collect(ch) oplogStatusSizeBytes.Collect(ch) } // Describe describes metrics collected func (status *OplogStatus) Describe(ch chan<- *prometheus.Desc) { oplogStatusCount.Describe(ch) oplogStatusHeadTimestamp.Describe(ch) oplogStatusTailTimestamp.Describe(ch) oplogStatusSizeBytes.Describe(ch) } // GetOplogStatus fetches oplog collection stats func GetOplogStatus(session *mgo.Session) *OplogStatus { oplogStatus := &OplogStatus{} collectionStats, err := GetOplogCollectionStats(session) if err != nil { glog.Error("Failed to get local.oplog_rs collection stats.") return nil } headTimestamp, err := GetOplogTimestamp(session, false) tailTimestamp, err := GetOplogTimestamp(session, true) if err != nil { glog.Error("Failed to get oplog head or tail timestamps.") return nil } oplogStatus.CollectionStats = collectionStats oplogStatus.HeadTimestamp = headTimestamp oplogStatus.TailTimestamp = tailTimestamp return oplogStatus } prometheus-mongodb-exporter-1.0.0/collector/replset_status.go000066400000000000000000000211441301535607600246020ustar00rootroot00000000000000package collector import ( "time" "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" ) var ( subsystem = "replset" myState = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "my_state", Help: "An integer between 0 and 10 that represents the replica state of the current member", }, []string{"set"}) term = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "term", Help: "The election count for the replica set, as known to this replica set member", }, []string{"set"}) numberOfMembers = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "number_of_members", Help: "The number of replica set mebers", }, []string{"set"}) heartbeatIntervalMillis = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "heatbeat_interval_millis", Help: "The frequency in milliseconds of the heartbeats", }, []string{"set"}) memberHealth = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_health", Help: "This field conveys if the member is up (1) or down (0).", }, []string{"set", "name", "state"}) memberState = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_state", Help: "The value of state is an integer between 0 and 10 that represents the replica state of the member.", }, []string{"set", "name", "state"}) memberUptime = prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_uptime", Help: "The uptime field holds a value that reflects the number of seconds that this member has been online.", }, []string{"set", "name", "state"}) memberOptimeDate = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_optime_date", Help: "The last entry from the oplog that this member applied.", }, []string{"set", "name", "state"}) memberElectionDate = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_election_date", Help: "The timestamp the node was elected as replica leader", }, []string{"set", "name", "state"}) memberLastHeartbeat = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_last_heartbeat", Help: "The lastHeartbeat value provides an ISODate formatted date and time of the transmission time of last heartbeat received from this member", }, []string{"set", "name", "state"}) memberLastHeartbeatRecv = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_last_heartbeat_recv", Help: "The lastHeartbeatRecv value provides an ISODate formatted date and time that the last heartbeat was received from this member", }, []string{"set", "name", "state"}) memberPingMs = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_ping_ms", Help: "The pingMs represents the number of milliseconds (ms) that a round-trip packet takes to travel between the remote member and the local instance.", }, []string{"set", "name", "state"}) memberConfigVersion = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_config_version", Help: "The configVersion value is the replica set configuration version.", }, []string{"set", "name", "state"}) memberOptime = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: Namespace, Subsystem: subsystem, Name: "member_optime", Help: "Information regarding the last operation from the operation log that this member has applied.", }, []string{"set", "name", "state"}) ) // ReplSetStatus keeps the data returned by the GetReplSetStatus method type ReplSetStatus struct { Set string `bson:"set"` Date time.Time `bson:"date"` MyState int32 `bson:"myState"` Term *int32 `bson:"term,omitempty"` HeartbeatIntervalMillis *float64 `bson:"heartbeatIntervalMillis,omitempty"` Members []Member `bson:"members"` } // Member represents an array element of ReplSetStatus.Members type Member struct { Name string `bson:"name"` Self *bool `bson:"self,omitempty"` Health *int32 `bson:"health,omitempty"` State int32 `bson:"state"` StateStr string `bson:"stateStr"` Uptime float64 `bson:"uptime"` Optime interface{} `bson:"optime"` OptimeDate time.Time `bson:"optimeDate"` ElectionTime *time.Time `bson:"electionTime,omitempty"` ElectionDate *time.Time `bson:"electionDate,omitempty"` LastHeartbeat *time.Time `bson:"lastHeartbeat,omitempty"` LastHeartbeatRecv *time.Time `bson:"lastHeartbeatRecv,omitempty"` LastHeartbeatMessage *string `bson:"lastHeartbeatMessage,omitempty"` PingMs *float64 `bson:"pingMs,omitempty"` SyncingTo *string `bson:"syncingTo,omitempty"` ConfigVersion *int32 `bson:"configVersion,omitempty"` } // Export exports the replSetGetStatus stati to be consumed by prometheus func (replStatus *ReplSetStatus) Export(ch chan<- prometheus.Metric) { myState.Reset() term.Reset() numberOfMembers.Reset() heartbeatIntervalMillis.Reset() memberState.Reset() memberHealth.Reset() memberUptime.Reset() memberOptimeDate.Reset() memberElectionDate.Reset() memberLastHeartbeat.Reset() memberLastHeartbeatRecv.Reset() memberPingMs.Reset() memberConfigVersion.Reset() myState.WithLabelValues(replStatus.Set).Set(float64(replStatus.MyState)) // new in version 3.2 if replStatus.Term != nil { term.WithLabelValues(replStatus.Set).Set(float64(*replStatus.Term)) } numberOfMembers.WithLabelValues(replStatus.Set).Set(float64(len(replStatus.Members))) // new in version 3.2 if replStatus.HeartbeatIntervalMillis != nil { heartbeatIntervalMillis.WithLabelValues(replStatus.Set).Set(*replStatus.HeartbeatIntervalMillis) } for _, member := range replStatus.Members { ls := prometheus.Labels{ "set": replStatus.Set, "name": member.Name, "state": member.StateStr, } memberState.With(ls).Set(float64(member.State)) // ReplSetStatus.Member.Health is not available on the node you're connected to if member.Health != nil { memberHealth.With(ls).Set(float64(*member.Health)) } memberUptime.With(ls).Set(member.Uptime) memberOptimeDate.With(ls).Set(float64(member.OptimeDate.Unix())) // ReplSetGetStatus.Member.ElectionTime is only available on the PRIMARY if member.ElectionDate != nil { memberElectionDate.With(ls).Set(float64((*member.ElectionDate).Unix())) } if member.LastHeartbeat != nil { memberLastHeartbeat.With(ls).Set(float64((*member.LastHeartbeat).Unix())) } if member.LastHeartbeatRecv != nil { memberLastHeartbeatRecv.With(ls).Set(float64((*member.LastHeartbeatRecv).Unix())) } if member.PingMs != nil { memberPingMs.With(ls).Set(*member.PingMs) } if member.ConfigVersion != nil { memberConfigVersion.With(ls).Set(float64(*member.ConfigVersion)) } } // collect metrics myState.Collect(ch) term.Collect(ch) numberOfMembers.Collect(ch) heartbeatIntervalMillis.Collect(ch) memberState.Collect(ch) memberHealth.Collect(ch) memberUptime.Collect(ch) memberOptimeDate.Collect(ch) memberElectionDate.Collect(ch) memberLastHeartbeat.Collect(ch) memberLastHeartbeatRecv.Collect(ch) memberPingMs.Collect(ch) memberConfigVersion.Collect(ch) } // Describe describes the replSetGetStatus metrics for prometheus func (replStatus *ReplSetStatus) Describe(ch chan<- *prometheus.Desc) { myState.Describe(ch) term.Describe(ch) numberOfMembers.Describe(ch) heartbeatIntervalMillis.Describe(ch) memberState.Describe(ch) memberHealth.Describe(ch) memberUptime.Describe(ch) memberOptimeDate.Describe(ch) memberElectionDate.Describe(ch) memberLastHeartbeatRecv.Describe(ch) memberPingMs.Describe(ch) memberConfigVersion.Describe(ch) } // GetReplSetStatus returns the replica status info func GetReplSetStatus(session *mgo.Session) *ReplSetStatus { result := &ReplSetStatus{} err := session.DB("admin").Run(bson.D{{"replSetGetStatus", 1}}, result) if err != nil { glog.Error("Failed to get replSet status.") return nil } return result } prometheus-mongodb-exporter-1.0.0/collector/server_status.go000066400000000000000000000111001301535607600244210ustar00rootroot00000000000000package collector import ( "time" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" ) var ( instanceUptimeSeconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "instance", Name: "uptime_seconds", Help: "The value of the uptime field corresponds to the number of seconds that the mongos or mongod process has been active.", }) instanceUptimeEstimateSeconds = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "instance", Name: "uptime_estimate_seconds", Help: "uptimeEstimate provides the uptime as calculated from MongoDB's internal course-grained time keeping system.", }) instanceLocalTime = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Subsystem: "instance", Name: "local_time", Help: "The localTime value is the current time, according to the server, in UTC specified in an ISODate format.", }) ) // ServerStatus keeps the data returned by the serverStatus() method. type ServerStatus struct { Uptime float64 `bson:"uptime"` UptimeEstimate float64 `bson:"uptimeEstimate"` LocalTime time.Time `bson:"localTime"` Asserts *AssertsStats `bson:"asserts"` Dur *DurStats `bson:"dur"` BackgroundFlushing *FlushStats `bson:"backgroundFlushing"` Connections *ConnectionStats `bson:"connections"` ExtraInfo *ExtraInfo `bson:"extra_info"` GlobalLock *GlobalLockStats `bson:"globalLock"` IndexCounter *IndexCounterStats `bson:"indexCounters"` Locks LockStatsMap `bson:"locks,omitempty"` Network *NetworkStats `bson:"network"` Opcounters *OpcountersStats `bson:"opcounters"` OpcountersRepl *OpcountersReplStats `bson:"opcountersRepl"` Mem *MemStats `bson:"mem"` Metrics *MetricsStats `bson:"metrics"` Cursors *Cursors `bson:"cursors"` } // Export exports the server status to be consumed by prometheus. func (status *ServerStatus) Export(ch chan<- prometheus.Metric) { instanceUptimeSeconds.Set(status.Uptime) instanceUptimeEstimateSeconds.Set(status.Uptime) instanceLocalTime.Set(float64(status.LocalTime.Unix())) instanceUptimeSeconds.Collect(ch) instanceUptimeEstimateSeconds.Collect(ch) instanceLocalTime.Collect(ch) if status.Asserts != nil { status.Asserts.Export(ch) } if status.Dur != nil { status.Dur.Export(ch) } if status.BackgroundFlushing != nil { status.BackgroundFlushing.Export(ch) } if status.Connections != nil { status.Connections.Export(ch) } if status.ExtraInfo != nil { status.ExtraInfo.Export(ch) } if status.GlobalLock != nil { status.GlobalLock.Export(ch) } if status.IndexCounter != nil { status.IndexCounter.Export(ch) } if status.Network != nil { status.Network.Export(ch) } if status.Opcounters != nil { status.Opcounters.Export(ch) } if status.OpcountersRepl != nil { status.OpcountersRepl.Export(ch) } if status.Mem != nil { status.Mem.Export(ch) } if status.Locks != nil { status.Locks.Export(ch) } if status.Metrics != nil { status.Metrics.Export(ch) } if status.Cursors != nil { status.Cursors.Export(ch) } } // Describe describes the server status for prometheus. func (status *ServerStatus) Describe(ch chan<- *prometheus.Desc) { instanceUptimeSeconds.Describe(ch) instanceUptimeEstimateSeconds.Describe(ch) instanceLocalTime.Describe(ch) if status.Asserts != nil { status.Asserts.Describe(ch) } if status.Dur != nil { status.Dur.Describe(ch) } if status.BackgroundFlushing != nil { status.BackgroundFlushing.Describe(ch) } if status.Connections != nil { status.Connections.Describe(ch) } if status.ExtraInfo != nil { status.ExtraInfo.Describe(ch) } if status.GlobalLock != nil { status.GlobalLock.Describe(ch) } if status.IndexCounter != nil { status.IndexCounter.Describe(ch) } if status.Network != nil { status.Network.Describe(ch) } if status.Opcounters != nil { status.Opcounters.Describe(ch) } if status.OpcountersRepl != nil { status.OpcountersRepl.Describe(ch) } if status.Mem != nil { status.Mem.Describe(ch) } if status.Locks != nil { status.Locks.Describe(ch) } if status.Metrics != nil { status.Metrics.Describe(ch) } if status.Cursors != nil { status.Cursors.Describe(ch) } } // GetServerStatus returns the server status info. func GetServerStatus(session *mgo.Session) *ServerStatus { result := &ServerStatus{} err := session.DB("admin").Run(bson.D{{"serverStatus", 1}, {"recordStats", 0}}, result) if err != nil { glog.Error("Failed to get server status.") return nil } return result } prometheus-mongodb-exporter-1.0.0/collector/server_status_test.go000066400000000000000000000027261301535607600254760ustar00rootroot00000000000000package collector import ( "testing" "gopkg.in/mgo.v2/bson" ) func Test_ParserServerStatus(t *testing.T) { data := LoadFixture("server_status.bson") serverStatus := &ServerStatus{} loadServerStatusFromBson(data, serverStatus) if serverStatus.Asserts == nil { t.Error("Asserts group was not loaded") } if serverStatus.Dur == nil { t.Error("Dur group was not loaded") } if serverStatus.BackgroundFlushing == nil { t.Error("BackgroundFlushing group was not loaded") } if serverStatus.Connections == nil { t.Error("Connections group was not loaded") } if serverStatus.ExtraInfo == nil { t.Error("ExtraInfo group was not loaded") } if serverStatus.GlobalLock == nil { t.Error("GlobalLock group was not loaded") } if serverStatus.Network == nil { t.Error("Network group was not loaded") } if serverStatus.Opcounters == nil { t.Error("Opcounters group was not loaded") } if serverStatus.OpcountersRepl == nil { t.Error("OpcountersRepl group was not loaded") } if serverStatus.Mem == nil { t.Error("Mem group was not loaded") } if serverStatus.Connections == nil { t.Error("Connections group was not loaded") } if serverStatus.Locks == nil { t.Error("Locks group was not loaded") } if serverStatus.Metrics.Document.Deleted != 45726 { t.Error("Metrics group was not loaded correctly") } } func loadServerStatusFromBson(data []byte, status *ServerStatus) { err := bson.Unmarshal(data, status) if err != nil { panic(err) } } prometheus-mongodb-exporter-1.0.0/glide.lock000066400000000000000000000023231301535607600211400ustar00rootroot00000000000000hash: 7dc238d7118210ebe55b508ed1d11af8c39132025807b9cddc2ec2489d9926f6 updated: 2016-11-23T09:05:11.556107146-08:00 imports: - name: github.com/beorn7/perks version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 subpackages: - quantile - name: github.com/golang/glog version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998 - name: github.com/golang/protobuf version: 8ee79997227bf9b34611aee7946ae64735e6fd93 subpackages: - proto - name: github.com/matttproud/golang_protobuf_extensions version: c12348ce28de40eed0136aa2b644d0ee0650e56c subpackages: - pbutil - name: github.com/prometheus/client_golang version: c5b7fccd204277076155f10851dad72b76a49317 subpackages: - prometheus - name: github.com/prometheus/client_model version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6 subpackages: - go - name: github.com/prometheus/common version: 0d5de9d6d8629cb8bee6d4674da4127cd8b615a3 subpackages: - expfmt - model - internal/bitbucket.org/ww/goautoneg - name: github.com/prometheus/procfs version: abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 - name: gopkg.in/mgo.v2 version: 3f83fa5005286a7fe593b055f0d7771a7dce4655 subpackages: - bson - internal/sasl - internal/scram - internal/json testImports: [] prometheus-mongodb-exporter-1.0.0/glide.yaml000066400000000000000000000003451301535607600211540ustar00rootroot00000000000000package: github.com/dcu/mongodb_exporter import: - package: github.com/golang/glog - package: github.com/prometheus/client_golang version: v0.8.0 subpackages: - prometheus - package: gopkg.in/mgo.v2 subpackages: - bson prometheus-mongodb-exporter-1.0.0/groups.yml000066400000000000000000000752721301535607600212610ustar00rootroot00000000000000instance: metadata: type: metrics help: "Information about the server instance." uptime_seconds: help: "The value of the uptime field corresponds to the number of seconds that the mongos or mongod process has been active." type: counter uptime_estimate_seconds: help: "uptimeEstimate provides the uptime as calculated from MongoDB's internal course-grained time keeping system." type: counter local_time: help: "The localTime value is the current time, according to the server, in UTC specified in an ISODate format." type: counter asserts_total: metadata: type: counter_vec labels: - type help: "The asserts document reports the number of asserts on the database. While assert errors are typically uncommon, if there are non-zero values for the asserts, you should check the log file for the mongod process for more information. In many cases these errors are trivial, but are worth investigating." regular: help: "The regular counter tracks the number of regular assertions raised since the server process started. Check the log file for more information about these messages." warning: help: "The warning counter tracks the number of warnings raised since the server process started. Check the log file for more information about these warnings." msg: help: "The msg counter tracks the number of message assertions raised since the server process started. Check the log file for more information about these messages." user: help: "The user counter reports the number of \"user asserts\" that have occurred since the last time the server process started. These are errors that user may generate, such as out of disk space or duplicate key. You can prevent these assertions by fixing a problem with your application or deployment. Check the MongoDB log for more information." rollovers: help: "The rollovers counter displays the number of times that the rollover counters have rolled over since the last time the server process started. The counters will rollover to zero after 230 assertions. Use this value to provide context to the other values in the asserts data structure." background_flushing: metadata: help: "mongod periodically flushes writes to disk. In the default configuration, this happens every 60 seconds. The backgroundFlushing data structure contains data regarding these operations. Consider these values if you have concerns about write performance and journaling" type: metrics flushes_total: help: "flushes is a counter that collects the number of times the database has flushed all writes to disk. This value will grow as database runs for longer periods of time" type: counter total_milliseconds: help: "The total_ms value provides the total number of milliseconds (ms) that the mongod processes have spent writing (i.e. flushing) data to disk. Because this is an absolute value, consider the value offlushes and average_ms to provide better context for this datum" type: counter average_milliseconds: help: "The average_ms value describes the relationship between the number of flushes and the total amount of time that the database has spent writing data to disk. The larger flushes is, the more likely this value is likely to represent a \"normal,\" time; however, abnormal data can skew this value" type: gauge last_milliseconds: help: "The value of the last_ms field is the amount of time, in milliseconds, that the last flush operation took to complete. Use this value to verify that the current performance of the server and is in line with the historical data provided by average_ms and total_ms" type: gauge last_finished_time: help: "The last_finished field provides a timestamp of the last completed flush operation in the ISODateformat. If this value is more than a few minutes old relative to your server’s current time and accounting for differences in time zone, restarting the database may result in some data loss" type: gauge connections: metadata: help: "The connections sub document data regarding the current status of incoming connections and availability of the database server. Use these values to assess the current load and capacity requirements of the server" type: gauge_vec labels: - state current: help: "The value of current corresponds to the number of connections to the database server from clients. This number includes the current shell session. Consider the value of available to add more context to this datum" available: help: "available provides a count of the number of unused available incoming connections the database can provide. Consider this value in combination with the value of current to understand the connection load on the database, and the UNIX ulimit Settings document for more information about system thresholds on available connections" connections_metrics: metadata: help: "Total connections" type: metrics created_total: help: "totalCreated provides a count of all incoming connections created to the server. This number includes connections that have since closed" type: counter durability_commits: metadata: help: "Durability commits" type: gauge_vec labels: - state written: help: "The commits provides the number of transactions written to the journal during the last journal group commit interval." in_write_lock: help: "The commitsInWriteLock provides a count of the commits that occurred while a write lock was held. Commits in a write lock indicate a MongoDB node under a heavy write load and call for further diagnosis" durability: metadata: help: "The dur (for “durability”) document contains data regarding the mongod‘s journaling-related operations and performance. mongod must be running with journaling for these data to appear in the output of \"serverStatus\". MongoDB reports the data in dur based on 3 second intervals of data, collected between 3 and 6 seconds in the past" type: metrics journaled_megabytes: help: "The journaledMB provides the amount of data in megabytes (MB) written to journal during the last journal group commit interval" type: gauge write_to_data_files_megabytes: help: "The writeToDataFilesMB provides the amount of data in megabytes (MB) written from journal to the data files during the last journal group commit interval" type: gauge compression: help: "The compression represents the compression ratio of the data written to the journal: ( journaled_size_of_data / uncompressed_size_of_data )" type: gauge early_commits: help: "The earlyCommits value reflects the number of times MongoDB requested a commit before the scheduled journal group commit interval. Use this value to ensure that your journal group commit interval is not too long for your deployment" type: summary durability_time_milliseconds: metadata: help: "Summary of times spent during the journaling process." labels: - stage type: summary_vec dt: help: "The dt value provides, in milliseconds, the amount of time over which MongoDB collected the timeMSdata. Use this field to provide context to the other timeMS field values" type: summary prep_log_buffer: help: "The prepLogBuffer value provides, in milliseconds, the amount of time spent preparing to write to the journal. Smaller values indicate better journal performance" type: summary write_to_journal: help: "The writeToJournal value provides, in milliseconds, the amount of time spent actually writing to the journal. File system speeds and device interfaces can affect performance" type: summary write_to_data_files: help: "The writeToDataFiles value provides, in milliseconds, the amount of time spent writing to data files after journaling. File system speeds and device interfaces can affect performance" type: summary remap_private_view: help: "The remapPrivateView value provides, in milliseconds, the amount of time spent remapping copy-on-write memory mapped views. Smaller values indicate better journal performance" type: summary extra_info: metadata: help: "The extra_info data structure holds data collected by the mongod instance about the underlying system. Your system may only report a subset of these fields" type: metrics page_faults_total: help: "The page_faults Reports the total number of page faults that require disk operations. Page faults refer to operations that require the database server to access data which isn’t available in active memory. The page_faults counter may increase dramatically during moments of poor performance and may correlate with limited memory environments and larger data sets. Limited and sporadic page faults do not necessarily indicate an issue" type: gauge heap_usage_bytes: help: "The heap_usage_bytes field is only available on Unix/Linux systems, and reports the total size in bytes of heap space used by the database process" type: gauge global_lock: metadata: help: "The globalLock data structure contains information regarding the database’s current lock state, historical lock status, current operation queue, and the number of active clients" type: metrics ratio: help: "The value of ratio displays the relationship between lockTime and totalTime. Low values indicate that operations have held the globalLock frequently for shorter periods of time. High values indicate that operations have held globalLock infrequently for longer periods of time" type: gauge total: help: "The value of totalTime represents the time, in microseconds, since the database last started and creation of the globalLock. This is roughly equivalent to total server uptime" type: counter lock_total: help: "The value of lockTime represents the time, in microseconds, since the database last started, that the globalLock has been held" type: counter global_lock_current_queue: metadata: help: "The currentQueue data structure value provides more granular information concerning the number of operations queued because of a lock" labels: - type type: gauge_vec reader: help: "The value of readers is the number of operations that are currently queued and waiting for the read lock. A consistently small read-queue, particularly of shorter operations should cause no concern" type: gauge writer: help: "The value of writers is the number of operations that are currently queued and waiting for the write lock. A consistently small write-queue, particularly of shorter operations is no cause for concern" type: gauge global_lock_client: metadata: help: "The activeClients data structure provides more granular information about the number of connected clients and the operation types (e.g. read or write) performed by these clients" labels: - type type: gauge_vec reader: help: "The value of readers contains a count of the active client connections performing read operations" type: gauge writer: help: "The value of writers contains a count of active client connections performing write operations" type: gauge index_counters_total: metadata: help: "Total indexes by type" labels: - type type: counter_vec accesses: help: "accesses reports the number of times that operations have accessed indexes. This value is the combination of the hits and misses. Higher values indicate that your database has indexes and that queries are taking advantage of these indexes. If this number does not grow over time, this might indicate that your indexes do not effectively support your use" type: counter hits: help: "The hits value reflects the number of times that an index has been accessed and mongod is able to return the index from memory. A higher value indicates effective index use. hits values that represent a greater proportion of the accesses value, tend to indicate more effective index configuration" type: counter misses: help: "The misses value represents the number of times that an operation attempted to access an index that was not in memory. These \"misses,\" do not indicate a failed query or operation, but rather an inefficient use of the index. Lower values in this field indicate better index use and likely overall performance as well" type: counter resets: help: "The resets value reflects the number of times that the index counters have been reset since the database last restarted. Typically this value is 0, but use this value to provide context for the data specified by other indexCounters values" type: counter index_counters: metadata: help: "The indexCounters data structure reports information regarding the state and use of indexes in MongoDB" type: metrics miss_ratio: help: "The missRatio value is the ratio of hits to misses. This value is typically 0 or approaching 0" type: gauge locks_time_locked_global_microseconds_total: metadata: help: "amount of time in microseconds that any database has held the global lock" labels: - type - database type: counter_vec read: help: "The R field reports the amount of time in microseconds that any database has held the global read lock" type: counter write: help: "The W field reports the amount of time in microseconds that any database has held the global write lock" type: counter locks_time_locked_local_microseconds_total: metadata: help: "amount of time in microseconds that any database has held the local lock" labels: - type - database type: counter_vec read: help: "The r field reports the amount of time in microseconds that any database has held the local read lock" type: counter write: help: "The w field reports the amount of time in microseconds that any database has held the local write lock" type: counter locks_time_acquiring_global_microseconds_total: metadata: help: "amount of time in microseconds that any database has spent waiting for the global lock" labels: - type - database type: counter_vec write: help: "The W field reports the amount of time in microseconds that any database has spent waiting for the global write lock" type: counter read: help: "The R field reports the amount of time in microseconds that any database has spent waiting for the global read lock" type: counter cursors: metadata: help: "The cursors data structure contains data regarding cursor state and use" labels: - state type: gauge_vec open: help: "totalOpen provides the number of cursors that MongoDB is maintaining for clients. Because MongoDB exhausts unused cursors, typically this value small or zero. However, if there is a queue, stale tailable cursor, or a large number of operations, this value may rise." type: gauge no_timeout: help: "totalNoTimeout provides the number of open cursors with the option DBQuery.Option.noTimeout set to prevent timeout after a period of inactivity." type: gauge pinned: help: "serverStatus.cursors.pinned provides the number of \"pinned\" open cursors." type: gauge cursors_metrics: metadata: help: "The cursors data structure contains data regarding cursor state and use" type: metrics timed_out_total: help: "timedOut provides a counter of the total number of cursors that have timed out since the server process started. If this number is large or growing at a regular rate, this may indicate an application error." type: counter network_bytes_total: metadata: help: "The network data structure contains data regarding MongoDB’s network use" labels: - state type: counter_vec in_bytes: help: "The value of the bytesIn field reflects the amount of network traffic, in bytes, received by this database. Use this value to ensure that network traffic sent to the mongod process is consistent with expectations and overall inter-application traffic" type: counter out_bytes: help: "The value of the bytesOut field reflects the amount of network traffic, in bytes, sent from this database. Use this value to ensure that network traffic sent by the mongod process is consistent with expectations and overall inter-application traffic" type: counter network_metrics: metadata: help: "The network data structure contains data regarding MongoDB’s network use" type: metrics num_requests_total: help: "The numRequests field is a counter of the total number of distinct requests that the server has received. Use this value to provide context for the bytesIn and bytesOut values to ensure that MongoDB’s network utilization is consistent with expectations and application use" type: counter op_counters_total: metadata: help: "The opcounters data structure provides an overview of database operations by type and makes it possible to analyze the load on the database in more granular manner. These numbers will grow over time and in response to database use. Analyze these values over time to track database utilization" labels: - type type: counter_vec insert: help: "insert provides a counter of the total number of insert operations received since the mongod instance last started." type: counter query: help: "query provides a counter of the total number of queries received since the mongod instance last started" type: counter update: help: "update provides a counter of the total number of update operations recieved since the mongod instance last started" type: counter delete: help: "delete provides a counter of the total number of delete operations since the mongod instance last started" type: counter getmore: help: "getmore provides a counter of the total number of \"getmore\" operations since the mongod instance last started. This counter can be high even if the query count is low. Secondary nodes send getMore operations as part of the replication process" type: counter command: help: "command provides a counter of the total number of commands issued to the database since the mongod instance last started." type: counter op_counters_repl_total: metadata: help: "The opcountersRepl data structure, similar to the opcounters data structure, provides an overview of database replication operations by type and makes it possible to analyze the load on the replica in more granular manner. These values only appear when the current host has replication enabled" labels: - type type: counter_vec insert: help: "insert provides a counter of the total number of replicated insert operations since the mongod instance last started" type: counter query: help: "query provides a counter of the total number of replicated queries since the mongod instance last started" type: counter update: help: "update provides a counter of the total number of replicated update operations since the mongod instance last started" type: counter delete: help: "delete provides a counter of the total number of replicated delete operations since the mongod instance last started" type: counter getmore: help: "getmore provides a counter of the total number of \"getmore\" operations since the mongod instance last started. This counter can be high even if the query count is low. Secondary nodes send getMore operations as part of the replication process" type: counter command: help: "command provides a counter of the total number of replicated commands issued to the database since the mongod instance last started" type: counter memory: metadata: help: "The mem data structure holds information regarding the target system architecture of mongod and current memory use" labels: - type type: gauge_vec resident: help: "The value of resident is roughly equivalent to the amount of RAM, in megabytes (MB), currently used by the database process. In normal use this value tends to grow. In dedicated database servers this number tends to approach the total amount of system memory" type: gauge virtual: help: "virtual displays the quantity, in megabytes (MB), of virtual memory used by the mongod process. With journaling enabled, the value of virtual is at least twice the value of mapped. If virtual value is significantly larger than mapped (e.g. 3 or more times), this may indicate a memory leak" type: gauge mapped: help: "The value of mapped provides the amount of mapped memory, in megabytes (MB), by the database. Because MongoDB uses memory-mapped files, this value is likely to be to be roughly equivalent to the total size of your database or databases" type: gauge mapped_with_journal: help: "mappedWithJournal provides the amount of mapped memory, in megabytes (MB), including the memory used for journaling. This value will always be twice the value of mapped. This field is only included if journaling is enabled" type: gauge metrics_cursor: metadata: help: "The cursor is a document that contains data regarding cursor state and use" type: metrics timed_out_total: help: "timedOut provides the total number of cursors that have timed out since the server process started. If this number is large or growing at a regular rate, this may indicate an application error" type: counter metrics_cursor_open: metadata: help: "The open is an embedded document that contains data regarding open cursors" labels: - state type: gauge_vec no_timeout: help: "noTimeout provides the number of open cursors with the option DBQuery.Option.noTimeout set to prevent timeout after a period of inactivity" type: gauge pinned: help: "serverStatus.metrics.cursor.open.pinned provides the number of \"pinned\" open cursors" type: gauge total: help: "total provides the number of cursors that MongoDB is maintaining for clients. Because MongoDB exhausts unused cursors, typically this value small or zero. However, if there is a queue, stale tailable cursors, or a large number of operations this value may rise" type: gauge metrics_document_total: metadata: help: "The document holds a document of that reflect document access and modification patterns and data use. Compare these values to the data in the opcounters document, which track total number of operations" labels: - state type: counter_vec deleted: help: "deleted reports the total number of documents deleted" type: counter inserted: help: "inserted reports the total number of documents inserted" type: counter returned: help: "returned reports the total number of documents returned by queries" type: counter updated: help: "updated reports the total number of documents updated" type: counter metrics_get_last_error_wtime: metadata: help: "wtime is a sub-document that reports getLastError operation counts with a w argument greater than 1" type: metrics num_total: help: "num reports the total number of getLastError operations with a specified write concern (i.e. w) that wait for one or more members of a replica set to acknowledge the write operation (i.e. a w value greater than 1.)" type: gauge total_milliseconds: help: "total_millis reports the total amount of time in milliseconds that the mongod has spent performing getLastError operations with write concern (i.e. w) that wait for one or more members of a replica set to acknowledge the write operation (i.e. a w value greater than 1.)" type: counter metrics_get_last_error: metadata: help: "getLastError is a document that reports on getLastError use" type: metrics wtimeouts_total: help: "wtimeouts reports the number of times that write concern operations have timed out as a result of the wtimeout threshold to getLastError." type: counter metrics_operation_total: metadata: help: "operation is a sub-document that holds counters for several types of update and query operations that MongoDB handles using special operation types" labels: - type type: counter_vec fastmod: help: "fastmod reports the number of update operations that neither cause documents to grow nor require updates to the index. For example, this counter would record an update operation that use the $inc operator to increment the value of a field that is not indexed" type: counter idhack: help: "idhack reports the number of queries that contain the _id field. For these queries, MongoDB will use default index on the _id field and skip all query plan analysis" type: counter scan_and_order: help: "scanAndOrder reports the total number of queries that return sorted numbers that cannot perform the sort operation using an index" type: counter metrics_query_executor_total: metadata: help: "queryExecutor is a document that reports data from the query execution system" labels: - state type: counter_vec scanned: help: "scanned reports the total number of index items scanned during queries and query-plan evaluation. This counter is the same as nscanned in the output of explain()." type: counter scanned_objects: help: "record is a document that reports data related to record allocation in the on-disk memory files" type: counter metrics_record: metadata: help: "record is a document that reports data related to record allocation in the on-disk memory files" type: metrics moves_total: help: "moves reports the total number of times documents move within the on-disk representation of the MongoDB data set. Documents move as a result of operations that increase the size of the document beyond their allocated record size" type: counter metrics_repl_apply_batches: metadata: help: "batches reports on the oplog application process on secondaries members of replica sets. See Multithreaded Replication for more information on the oplog application processes" type: metrics num_total: help: "num reports the total number of batches applied across all databases" type: counter total_milliseconds: help: "total_millis reports the total amount of time the mongod has spent applying operations from the oplog" type: counter metrics_repl_apply: metadata: help: "apply holds a sub-document that reports on the application of operations from the replication oplog" type: metrics ops_total: help: "ops reports the total number of oplog operations applied" type: counter metrics_repl_buffer: metadata: help: "MongoDB buffers oplog operations from the replication sync source buffer before applying oplog entries in a batch. buffer provides a way to track the oplog buffer. See Multithreaded Replication for more information on the oplog application process" type: metrics count: help: "count reports the current number of operations in the oplog buffer" type: gauge max_size_bytes: help: "maxSizeBytes reports the maximum size of the buffer. This value is a constant setting in the mongod, and is not configurable" type: counter size_bytes: help: "sizeBytes reports the current size of the contents of the oplog buffer" type: gauge metrics_repl_network_getmores: metadata: help: "getmores reports on the getmore operations, which are requests for additional results from the oplog cursor as part of the oplog replication process" type: metrics num_total: help: "num reports the total number of getmore operations, which are operations that request an additional set of operations from the replication sync source." type: counter total_milliseconds: help: "total_millis reports the total amount of time required to collect data from getmore operations" type: counter metrics_repl_network: metadata: help: "network reports network use by the replication process" type: metrics bytes_total: help: "bytes reports the total amount of data read from the replication sync source" type: counter ops_total: help: "ops reports the total number of operations read from the replication source." type: counter readers_created_total: help: "readersCreated reports the total number of oplog query processes created. MongoDB will create a new oplog query any time an error occurs in the connection, including a timeout, or a network operation. Furthermore, readersCreated will increment every time MongoDB selects a new source fore replication." type: counter metrics_repl_oplog_insert: metadata: help: "insert is a document that reports insert operations into the oplog" type: metrics num_total: help: "num reports the total number of items inserted into the oplog." type: counter total_milliseconds: help: "total_millis reports the total amount of time spent for the mongod to insert data into the oplog." type: counter metrics_repl_oplog: metadata: help: "oplog is a document that reports on the size and use of the oplog by this mongod instance" type: metrics insert_bytes_total: help: "insertBytes the total size of documents inserted into the oplog." type: counter metrics_repl_preload_docs: metadata: help: "docs is a sub-document that reports on the documents loaded into memory during the pre-fetch stage" type: metrics num_total: help: "num reports the total number of documents loaded during the pre-fetch stage of replication" type: counter total_milliseconds: help: "total_millis reports the total amount of time spent loading documents as part of the pre-fetch stage of replication" type: counter metrics_repl_preload_indexes: metadata: help: "indexes is a sub-document that reports on the index items loaded into memory during the pre-fetch stage of replication" type: metrics num_total: help: "num reports the total number of index entries loaded by members before updating documents as part of the pre-fetch stage of replication" type: counter total_milliseconds: help: "total_millis reports the total amount of time spent loading index entries as part of the pre-fetch stage of replication" type: counter metrics_storage_freelist_search_total: metadata: help: "metrics about searching records in the database." labels: - type type: counter_vec bucket_exhausted: help: "bucketExhausted reports the number of times that mongod has checked the free list without finding a suitably large record allocation" type: counter requests: help: "requests reports the number of times mongod has searched for available record allocations" type: counter scanned: help: "scanned reports the number of available record allocations mongod has searched" type: counter metrics_ttl: metadata: help: "ttl is a sub-document that reports on the operation of the resource use of the ttl index process" type: metrics deleted_documents_total: help: "deletedDocuments reports the total number of documents deleted from collections with a ttl index." type: counter passes_total: help: "passes reports the number of times the background process removes documents from collections with a ttl index" type: counter prometheus-mongodb-exporter-1.0.0/mongodb_exporter.go000066400000000000000000000150621301535607600231120ustar00rootroot00000000000000package main import ( "crypto/tls" "flag" "fmt" slog "log" "net/http" "os" "strings" "github.com/dcu/mongodb_exporter/collector" "github.com/dcu/mongodb_exporter/shared" "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus" ) func mongodbDefaultURI() string { if u := os.Getenv("MONGODB_URL"); u != "" { return u } return "mongodb://localhost:27017" } var ( listenAddressFlag = flag.String("web.listen-address", ":9001", "Address on which to expose metrics and web interface.") metricsPathFlag = flag.String("web.metrics-path", "/metrics", "Path under which to expose metrics.") webTLSCert = flag.String("web.tls-cert", "", "Path to PEM file that conains the certificate (and optionally also the private key in PEM format).\n"+ " \tThis should include the whole certificate chain.\n"+ " \tIf provided: The web socket will be a HTTPS socket.\n"+ " \tIf not provided: Only HTTP.") webTLSPrivateKey = flag.String("web.tls-private-key", "", "Path to PEM file that conains the private key (if not contained in web.tls-cert file).") webTLSClientCa = flag.String("web.tls-client-ca", "", "Path to PEM file that conains the CAs that are trused for client connections.\n"+ " \tIf provided: Connecting clients should present a certificate signed by one of this CAs.\n"+ " \tIf not provided: Every client will be accepted.") mongodbURIFlag = flag.String("mongodb.uri", mongodbDefaultURI(), "Mongodb URI, format: [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]") mongodbTLSCert = flag.String("mongodb.tls-cert", "", "Path to PEM file that conains the certificate (and optionally also the private key in PEM format).\n"+ " \tThis should include the whole certificate chain.\n"+ " \tIf provided: The connection will be opened via TLS to the MongoDB server.") mongodbTLSPrivateKey = flag.String("mongodb.tls-private-key", "", "Path to PEM file that conains the private key (if not contained in mongodb.tls-cert file).") mongodbTLSCa = flag.String("mongodb.tls-ca", "", "Path to PEM file that conains the CAs that are trused for server connections.\n"+ " \tIf provided: MongoDB servers connecting to should present a certificate signed by one of this CAs.\n"+ " \tIf not provided: System default CAs are used.") mongodbTLSDisableHostnameValidation = flag.Bool("mongodb.tls-disable-hostname-validation", false, "Do hostname validation for server connection.") 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/") authUserFlag = flag.String("auth.user", "", "Username for basic auth.") authPassFlag = flag.String("auth.pass", "", "Password for basic auth.") mongodbCollectOplog = flag.Bool("mongodb.collect.oplog", true, "collect Mongodb Oplog status") mongodbCollectReplSet = flag.Bool("mongodb.collect.replset", true, "collect Mongodb replica set status") mongodbCollectDatabaseMetrics = flag.Bool("mongodb.collect.database", false, "collect MongoDB database metrics") version = flag.Bool("version", false, "Print mongodb_exporter version") ) type basicAuthHandler struct { handler http.HandlerFunc user string password string } func (h *basicAuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { user, password, ok := r.BasicAuth() if !ok || password != h.password || user != h.user { w.Header().Set("WWW-Authenticate", "Basic realm=\"metrics\"") http.Error(w, "Invalid username or password", http.StatusUnauthorized) return } h.handler(w, r) return } func hasUserAndPassword() bool { return *authUserFlag != "" && *authPassFlag != "" } func prometheusHandler() http.Handler { handler := prometheus.Handler() if hasUserAndPassword() { handler = &basicAuthHandler{ handler: prometheus.Handler().ServeHTTP, user: *authUserFlag, password: *authPassFlag, } } return handler } func startWebServer() { handler := prometheusHandler() registerCollector() http.Handle(*metricsPathFlag, handler) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` MongoDB Exporter

MongoDB Exporter

Metrics

`)) }) server := &http.Server{ Addr: *listenAddressFlag, ErrorLog: createHTTPServerLogWrapper(), } var err error if len(*webTLSCert) > 0 { clientValidation := "no" if len(*webTLSClientCa) > 0 && len(*webTLSCert) > 0 { certificates, err := shared.LoadCertificatesFrom(*webTLSClientCa) if err != nil { glog.Fatalf("Couldn't load client CAs from %s. Got: %s", *webTLSClientCa, err) } server.TLSConfig = &tls.Config{ ClientCAs: certificates, ClientAuth: tls.RequireAndVerifyClientCert, } clientValidation = "yes" } targetTLSPrivateKey := *webTLSPrivateKey if len(targetTLSPrivateKey) <= 0 { targetTLSPrivateKey = *webTLSCert } fmt.Printf("Listening on %s (scheme=HTTPS, secured=TLS, clientValidation=%s)\n", server.Addr, clientValidation) err = server.ListenAndServeTLS(*webTLSCert, targetTLSPrivateKey) } else { fmt.Printf("Listening on %s (scheme=HTTP, secured=no, clientValidation=no)\n", server.Addr) err = server.ListenAndServe() } if err != nil { panic(err) } } func registerCollector() { mongodbCollector := collector.NewMongodbCollector(collector.MongodbCollectorOpts{ URI: *mongodbURIFlag, TLSCertificateFile: *mongodbTLSCert, TLSPrivateKeyFile: *mongodbTLSPrivateKey, TLSCaFile: *mongodbTLSCa, TLSHostnameValidation: !(*mongodbTLSDisableHostnameValidation), CollectOplog: *mongodbCollectOplog, CollectReplSet: *mongodbCollectReplSet, CollectDatabaseMetrics: *mongodbCollectDatabaseMetrics, }) prometheus.MustRegister(mongodbCollector) } type bufferedLogWriter struct { buf []byte } func (w *bufferedLogWriter) Write(p []byte) (n int, err error) { glog.Info(strings.TrimSpace(strings.Replace(string(p), "\n", " ", -1))) return len(p), nil } func createHTTPServerLogWrapper() *slog.Logger { return slog.New(&bufferedLogWriter{}, "", 0) } func main() { flag.Parse() if *version { fmt.Println("mongodb_exporter version: {{VERSION}}") return } shared.ParseEnabledGroups(*enabledGroupsFlag) startWebServer() } prometheus-mongodb-exporter-1.0.0/screenshots/000077500000000000000000000000001301535607600215425ustar00rootroot00000000000000prometheus-mongodb-exporter-1.0.0/screenshots/mongodb-dashboard-1.png000066400000000000000000002422521301535607600257670ustar00rootroot00000000000000PNG  IHDRE k iCCPICC ProfileHPЛtkFHB%Tl+KUp-,*b*]E@Y EQyy;oN~9ssn|~ , @*O$qEp`8CSw $Z_%b @!dzT!@e#q" W! @De9s9slNx“I  iLR9!33B%i3܊A[xIM#Yf )voC YDo(YUryAer7bByf1 !%H \o;GfI!l~! uB$A"`6xRЀ'!#9":i.'QDsCnF1M,-sG]b"AB PbL c.6slN2@(u 6/IbjDIE&Y 6 䁽(GqPN3+:A7xzx F8 ! i@1d AΐB1Pāxm:UC@+- zA If*Ùp6.p|wý[x PR(*JeCyQ*BPͨvT/jES4 틎@3t} ݇Eǐ1cp0k19"L<:3bT>닍&a7`wcc뱭.l?v )qN`'N.px)E%} ~ K%8 ,zB>p0@"Npbq+XGN|N(%%%e/\+%U,uZTg<ɈAZI*I'd2YJ%{Ii4]%ETA;jL"2dFd z ͲdɎQ,Rvݒ{ɳ_易( rr2UW+$))RPUW\NTb/Eգҩ)|j"En؋v-[tфb%W%RRRer>F*h#*kU\WYq1sq3ªFTUSSWQR6NUwUOR/T>Apjj\xCSRhŴkQMUM_M1)-}mZZ/v څmڣ:::ujuttuNEkWҧg?7 <4&64J6\]K0K/ydB2q3053n3m4}gckϬ컹y g~,->XY2-K-ZX5Y_j@mllm6u6ö:qeBvݴػoo` r8𗣉cc2ee';i919:ӜrutarveV%t{n.p?>ɣ%U[˛]=c]ΤWGl6]' 0 4~A0=x_c,/]:"tch{%lMXMx{x~qD[Lȉ(ϨhMwbTb1M؊ر^+Xi2ge*UVZ:e52kkaj2匱xz|Y(Ӄy*d !g?g8%(q-OM:4\<ROK%󮥩KsG !$\%l) @CܗQ16rurx7Zk`w6jnܺoۦc۶ho2U5yo̷l=j{sZvVv99G;w]VʽgWu7s-~,qzOž|#{{y{{*+,V[=H<(>[PtHC_KKKKTvMfHQyG1c zEDZ3>yg+T**U*{BUVWרµ+Ov}z<$Oe=<}! wUثg:hPczre{͊7ooFrsuw4zt?V~Zm,dxDdg_ NZ[wϧS c '$r _M̳|,9_=+*] (G5g,S+$&XY"!39=Q \3S@}@kWy)WC 8^iTXtXML:com.adobe.xmp 1242 581 $I@IDATx{PTg! QPaVxD- L Jh$qgS:l܉2SLav̸g?J)/xbD 0jkh~tSbs}=~λ?y $@$@$@$@$@$@$@$@$@D R31 FB/        :"          mHHHHHHHHHPĥHHHHHHHH(         N @ 2         kHHHHHHHH:N,HHHHHHHHHB        :"          mHHHHHHHHHPĥHHHHHHHH(         N @ 2         kHHHHHHHH:N,HHHHHHHHHB        :"          mHHHHHHHHHPĥHHHHHHHH(         N @ 2         kHHHHHHHH:N,HHHHHHHHH yjPKdu&yKV"+*]eS=-ʦ *(=]iA\iQ6 k7WOʖFVVaSYpnʗc=5͗i˙rXwOv/E+g踖3{1=     #=c?Q}:^eNW^7"Os1~tGevZ2s:PUY5LOrܵsuB%q0N4VUd kt|>^wVahkӆRDo{$ x߮X=+$@$@$@$@$@$Ѕz~ y >~_-PIֹht(+-Y98K9c߿1pY%!>+ݎ+Gw"?X0=bmQai.#l%0X7N³$@$@$@$@$@$-H?7 AOewݍ ,is͍Ϛu;ZVtlrKEF(tҫ* $@$@$@$@$@$ YXj%KgזV=G7>FۣޯZҰ~̻_3_       /Gh 92Yw(!}bC8]@@h߄ 3bךY dVoڵZ$b<^O|^[Z`DZ#קRyU`ڎ-;oFd!-^.+6}C1oغEg~N5^X%%X%oEEwB)g)hQ|^ODc*s=ކҫ|y؃E^7[{UO      nMG:j5͡`rx0 Ok!wGqPD6 iy pu2DAD6?9 >эŶWl*E>"سS>!sf\9*$M%OÇ7>E*| r %{}ؒBr!Ihy`T'M_09wnV CTx,ڂVUjx74͝۱?+TI90lY p`K`Mflmmb7|ʁD6-4, ]wC\yfI=cչ=o!I\cT,Lds[hRc      ^@G m3(4di(\9w'[BnUHh c!?6qPu6{a'lP`v.?pc@,K9U,awyYsUc^GlKqX@ʝ~uXn @!У6D9|^_sLWa2WCҏimYs,Dqi :ΕBC_\JĜRX݉e2qQ/гTqc9Xl JeЃ2ۆ5ˤsfq),E;C/=17$`]jUwbݖq;lĆ{= HMj0&d(VteoBpnA jF/PɊU 6l(x#UQkxs̐{5WhVFר >]oxy&z70(l;Uf9)y;khp 3ۈlM.^{Uoɸ_aVrEE\^(4LAyz.i -%r&ڮV\M>LD )wt(w6tdNfƳHxS{c[JE K]~n#ezN:aa~$@$@$@$@$@$Ы l6k+PZiMK}re!޹y/ܕk{]f՘9eN<leQz#Wr_+$>iQd6˥ybf.ްxvKfHvH'ϓ90ĺ&N+*E3ӊ`FIY)p\{x[i+emȕfngwIMBmKoK,*,-Ҳ 2|:\z5> (ݳPEkH7^~՜{vaIJ:씕G[>e1xU6c+퀏.9      =z먻#֠19@v;%]P]@fL&ɢ;pWKȟ'df"(ee!mM'Ikw_@*LC ( V'     Ewm"y7 g.'yK|ϔ_3Yޭ lgqT"x/Cvdx6%y7[el+ޙCD/D8Th8y)BQ,CUe\&QZqg~IVǢ=1}p_O󵓋YVP,^--^9\i/+ޕHt y+sA!Z%-pףYU[nsy?"wze!},_ UHVxLbhuݙqHHHHHz2^::p3d̍Ya]ց \LȮI %XȻP2 ǽOdq{ ϥ؉56Ri|HʭWL |B3Gk,hΦC"7as1=olǂ<";C){ 3DeߊĦ`+,`g(C꭪y>*$afa3flqc\"Sq&;tml,IHHHHH5Brmk~2RyC-3_;̪ #BY|D1U= ;b^8){Ole)d۩VszsWSX[ye_ -94bv;JKM|2DX jh' k|<Ҭ+"V5w[RZHs yg2o# a!eUwx X!LD6ocOσ$@$@$@$@$@$@z&B_, vy ̊AܴF^j!s _6zz.H"O3WҐ"S}9C,M+É*E6Ƭ,o=QYQM's$SS^Xj 4k.·KQX1r]׫DMxmLL߄sv8 7ޛS lkQ@Gfqz5Qsxpcx.XB#!ZLHH$&b/Uj9IIg>`u?pHHHHHz>^%UV곂fRy%Fۧ~y:oz>Zt*qo Q{YB;XEB9zwJYf&9hԣg;_A̚NRيCWmae֤YمMZ%*iD[dˆnPR.ϾikGpcE.Γ#r r#*Pjf~HLC$@$@$@$@$@ݝ@/Yu*Q( UbEXeZy2]^M"Gc-2Xq;WM(o;ج84 ^MlĻo)Ӌʛ-i6nAWM)2 +4{5S\]+XRbw?6612Y1M :Q\L-Yc$g!;WzbN؁%(c$mG҈`Aҷaᩇ y:ݗ<+k6Id- ,+ҶoX+5֖FG p~F$Lo*)nqMŧ| KRp&p$Lq_(*ZqulVYx|T+]+k_S6֮㍜X9SXFYmTVV#HHHHHz^'iV]b}fɵغckK*K7]!-^{ ŧe#'[ЫR[\?Y+)h2/:^lv!n\CJ4K-> dYsʴP;B4pջ%jK rx9*gUI*ώ 9 V0OorgU)ƚ}d3feW PUYFxdTO%U{٦kZ,:WݸmeIqGBr_!ɷ5$6k;GQ;jE^SG`W޸{eAQmX ZrGg[p@7mlh$=a_&?$󱣶dʪfȳ,9,F"     F m&WU [}} a f[K{zZ_AD?ש1cMXԳTcb[xȐWxx%6 |V /Oo!"n+ LK*r O~^EH](tC^9f Ue%J (SBXl HJ[8' W¢&ϘWIL영e qU(3s׺'/,s yS1*K1':Gdӳ/yeᖅxYa"G2vg`N' {­R=>(ZeU-hwC=tK`$@==G     BBi4?, 3<|ntKtq2g    I~bݳXk        0#@-:!       (u~cIHHHHHHHŒ0VHHHHHHHH{=&    \c.L  .'o.VHHHH:gʕ+_@s|'I}ETUUy, 2DE]]~8l <)))FSSN: $@$@]Dm]Œ @gl,xO=Ǻ͛:·kHJJO? ej5-\z hWBd,F?VS:'ΝMK$@'숌M36AH=jpyO#s ! |=ac0$ZZZq-h8~ 7?O4bn-\~CXyH[_}Two'{ٞ222p%XRl*;whꫯp裏bԨQ:·cP"[zz&>|Xbkii??`ܸqXmu`h`D!n+/Gåf>}0$1Dʽ/Å+mwLPD\FS]O$@&@s[*Me‰?bK̞:@qS^~i>bAt>sc<7oKyiKiӒ*/0*m#b$ۊm'$1yfpɓg"?4~RCN@C }ǧ[}bY9p[S"7qUb5j RXT.nDIg(&܎sa`T|Tۗq`4R<z!/wK\HuP0r{.?U7ئCuN% FD#}D6UO墫۔ب6yFAV. IHDNcZzof sK`U~$$c4U}"!UTuK tK=*rLޅHt{|3(,Ez~.b%K7!g1Iu*? ⋿}&[$HR+N-5a!OҶ3JYA{V봔N8'A92B ȳb?bHk#;+H<Q7qh]oGdkc md włDSr})W}0.;Əą=l-'p<ĄQC0.k0>;;TBSO= r|2  S….?ݿ^syT=AJ~1ێBZZ%[LL ]_& >cRTR_d*u=f#14c<25[1O=l?EsdLyP $@$~Y/Q; QQ73S:3ɃoN> w-=g 1aIzo(y2<:sRN,.,Q#>3'`2פwm $oy ?) ҇aSF5o6}/ Ǟ9!8D]V_`ILۿM6b؄)\shæu?ogET<';,(ҳ"Th2ҵv4V/]"-qs@bm<kOO% JvwZ8U"i|~t[؏4xsq*bҪ~XerO1W? !N|D2err3Fګ75r`r 7h00@︼lP_"5rK:zTje)sUCf|qIS~/`3EMe, .\,33SMW񔕘(p5OJD;w+Wqc:OxCStwQeݦ,jkkqi:n"W*zT[q $ zV m6ai<9(gĝ\>x|OUZ[AzJ<sy޴ݩ>_`E?.0K#5Y4#ι҉1#1_oS8yI~qZk<ְqOȤxyxqoDaH!› Dxa\db7>vZ0yn&R/3 S.=nt~X.~=wl~.9]y/<{d@[?*)Z^&"#LW|?lɼ+侷!=WD6'ybCj,r̸=^z5vHNUXxPE(=3D&?tL}~"yPr"dq4L"bj9V/O*K7HkBtxy% ev֨X )ZfA?BRI"n^ξlxӑ3N"Q HyZܾǵV<$ d)~W "*zAMY5ib(ಲrS~-7`*_wĠd?lDvYԟ>զ2aAc#G6*.VWF1XiǍq׵&JrPY?/B t:T^U4ď'Sks"w8gj.$~,n^>gEB|^۳Ms 5 |vM- g踋~CF!]{0zwgglT?y%hy$"SwkrZ,fG SVucRy֛} 9ae:y)熍ØxD\;-?_' mZw#f'ϸ|lͳ2vTCgⷿmb&/bv*m?_p0$s?sٴsk|>3'(m16o0zdx;A)149ǯGreZ[EeS+8FJQ/W¤"^Ir.!NIݕeW jdsSyQOh,]·˱~*J066U>Z:TVVB6-DwjXWc|RYF㨩5.}u9yciSSƃhOWǔ|6ȃaۿs$vY5Gc_n\؍&uٍC k\`(VuoTW E "ĕV:3LS \ v<0D~x'HHl#qEW%1 ׅͪߝ}pml,J5e׿ \Nq dW;]dYEg=.seȹ˗-U\T$?}dFNCb%|ːsV}VitzqMTS4&dyUʻlmACnQM?H' &AA׾8Cn.;sɰ\#I"&+sW&ׅᶸ޸Epܖ"k8#/K#2 RWrKTS&+ʋՖ;W/ӗtq(2y CIOU7}_'OAaimzH z)q='/+I8gsŶ")*(qG!==ݵJ,$п'MVUA-"Z@oM岺Z*)cy1=A}6Lnp]VxU߽#FVu=sL"Ԃ-ľhu ׯ\NOFT}ЄMd r MgqOC:sPw/D hrm8H,Uvذ~!r L#-5VC$ JIv];*_= V9|KddAC'Hݮ!Sn ׳ӞH5ydD>(Y,Μu 6:y duB52 ?g9ԉV|>b/c농a ?4Ns $2}n8wÐ3wq Ƹgv[Eyrj:=\@gle kObGˠZMN*gqlhBKX$ n;P}.hGM7ՇpB7zQ{ub?rdQQ0YlE{ĥ]%z[KqGkKgu'%O ;8^=USqyix47j/>=brv 3 NDԫ'y++H[2X5掻g"?@Ăjk=*TAd)$;ЦWיZU-/𠎩m%ruvKD(5#r3--ZUȦݔ;7|W|FGb;Co⦺_mɛgˊ.yR[Cb5qP:^.%Fk}ݳEL\t<><ѹse~62->سسNc1wq!&~3,\B]\;ŋ犷+,3@ 9N ޸Y}yԧb7P{3>ߵN ʊ>@IDATT,|LH  05^><_ϛ)ȗH3y+nMxW\X+{ln^&W"wDaȝJ1>J€j hʼn/OKf }ҏ%bWvKyRS'ΩDx>|3$rlLib\g<#~W݇ 1IϔAOj_~Q 5֦8$wpUdRYLgD\ Aͭ-0RyJR;7e.0 yيC8xYTv?xn_/7& @O'@{/Q [~ܬ-婊 2yU~ i>nE啯y'& OH׮kWq2# ںr2R[|;duҮ]S"ѕ+hgmH]Պ55qxVV% iʷ#> l}\]O:թl}m`Sַ~Kܳ-Ԣ'9Y!i")hw!b!u{ו6"; P{NK'!r, ae5R;Y*e {:\( 3Z3.@j0#>> peLF`;qMdS(A-BC@Ԋ $@aAFsbmjũvj;>[RQ﷐J wQTdShD@ X)yYr^tçw]%u IIJP3%Y p~} Ss)wDlK{M=/<;0hT=]dSW.;ɲPˀAqVq&Ky׊{j)  m.*͘iϤbrnڊӇ˰!  d4q7WG\n[ 8GgV#GG1Jv#+ =31/ 5IC/$ Vl^t6HHHD?g$@$ Ǐ_Ј@$@-bIHHHHHHHH| Phe#$@$@$@$@$@$@$@$@$nڍ HHHHHHHHH6_&a u{~A!   J qM(qkYo   E'kֻ.AHHHHHHHH \ Ph מaHHHHHHHH mݪXY        p%Э E$@$@$`:`jRHHHHs M:k tcJ        A         8 mgǔ$@$@$@$@$@$@$@$@$!@̓$@$@$@$@$@$@$@$@$q:Ύ)IHHHHHHHHCB7HHHHHHHHH(uS 6 n @ Ph8;$         m         qvLI$@$@$@$@$@$@$@$@<(A$@$@$@$@$@$@$@$@' EHim>[/Wݻ^x E#utjG*}d/N͜ @0}jذ'5x2?  z~MWccc; EƴT|Wssxh 6OLMB_@͖:{ѲUy~^dǂVWo³ <@>*V]G-PNyS/g1m=5"az :iS͑#h :wM¾}5:ߣ&&r~iK%  `>ug۾ 3漇MEh!~dDDPF!  (,}1}[i(F0m{J8u=.\} zyl=5Ƨ;>8.c]zLয়n؎߻§O[q;4W}:?py"uGVLD xqqqЇyۡ_ħQnp7xs [ikak[[E]_۷B'ɛ5׹>nXw=f>6O<)\8@0y8a#d[;4ǰ~1GP+sƎzb[4z~}pP$  Bzh ] Nw"c 6v_{_cg3 86^8_ۡUz5Iޔ.c"x?XH+Xꘌ#>jwq*PΧ;22x筏;mL# R[FIx";x)5(UL]~ t#YSf`[rr@s3 G ,4l1qʟ%OK~1PMUl7-c'mH=Wqs(>l1.doj c"_ ͘MO*ȐDH]ZkǾ~U͖ f$b n7| \BNXIY^1`Fr[|wbR'?gE${eW8n&v|ڂ!LJI3uƊ2R3 l%i2`Ow#nk<#.Jv} F~e~XI7DK)]ꅆ;$@$@@6^mƉDM@6wTPd١7#&bQө l1xvcBqWv*AFp,];&㈙-J~0 mdk_1Kx 1s)~:5^oLoX{|x"2fhbl1O_meqW#C$@$@$ Тͧfj~ ~uDDh.4ioC'&Ȧ+0kbPKܑfl[~9w^*4O?CvaôGdS O8rMLQD;64%_+| 1?y T7t ~tz9q92>?qС)~KhWib)Bghp67n<1}"0y}qύ7EEiNQ TGҳo!l#n#3Μ -sg_Xw0N/ߙXj0v9OG":Ho]*'#  C`;1w>::g ~_KbM&b]ve[Y0=/uؾ槮, {_m̹g~2 6QϊoO?95Mj }2+8Dɨ;ܕ_1uGM42$  7h3FbFլRYxvʷm^mE$@$@$(t:Q 1~d7jiݾ53ܓN+bP)skYVlC}6hn Ӆm'jQyCѯ\hjy]1ZDl5-=* uhڳ]Jt-Ơ.|=M[ك5kc1ﶌIE8{CVۜQwmBsGēinol_1/TxE=͑՜[}} }hSnR)id^L=ϩ+/Mb'.Hfs5_޳t  Bw3TL0嘦 L&Ͳ.= Q\߇28fkkm1,đ?Ưz!XT.=' @T~$nL5uyz:~ thꫯV&e!ND?׎O52mG 2݊}ų6۲hieזWfa#. /)CٚlAmϯnL&,d H;<#<٢Q\F3s5סxů,?i&JB^#n1&oY-NĮEԏ4P7boQcvI]"qNxK[mnhl9._Cَ{|UՙܐK`1A: KjЙ ŊZC-Z?gF ߟJ3~}Ѫ%-(PDb$r Ivz>9dg g^Y{gSQ @@@ y>łN7 8ٸor;b>R_Q\YX@v9sq/!>'DlWl=vFo   6 r.@NM/^^PwiKjgU5Ք+4*zfo^5$%e˨LLXE -Z,l '"ZrVTTSpXoGUup擻UL*%ڽbi-vY!:܂2nDyˎzq,H@*a7n.cS!:;;41(>HM)65n^N7?%5}| 郞b^;5FHMobHw^/Q*DlAX8жpc?$J Z'r<6+hm)eH Cu6MqED5^ykmH'Ū㜶>8#d  )A Zǎ0Gfs[4r11d7  =b?2 C"mo9L{)O 8ܒH8@@@"6i@@@R@Grt D^ b1 :F A@@@@@@@L G Q |        &Ȩ@@@@@@@@hsBfdT        |p9_h! h32p>8ڜc@@@@@@@@pU8cm3fPOp`C`p}@ˤWٖ`p>9=\vmŋ> F5a֬YtСQeaGe4`NӨ큍I%>NEд4M#}4O|'RrY,bH^=CrY,bQ}OEI6iTA@@@R@Φmo@@R@8v^w$G `#* |j}hQ> .ր>֏>ր>֏ӥCD5؆@h*!(@ }b(B,h ,u7sʄ}8GF6bYg pm>5_rvh~oɦ#}ȥ٤}ȦԒmZNJ+Id L2_O?Gu%KN     D{kXD[τwҘ G><|RH~%3 ~ڋ[\^o=: ·1 keIy1FEzT''#  =h}!vI#cJ~R1p ;ٸ*Ώ3ipn+&PGBR$HJ;>F:'Dh唑?@$m<m#2?v\̜tMC#iOͯ{ HX K #n#M^>t"&CGsil(8v20%98 㺬 >-IUu5UGZG*ZXR>RK6XK z`VҗD>ƭcbi21~ial̇pbgs%ӂo]QIc!NЦd*W=kBO׫e贌޽^ s:HM'h&:o#UoG'nUxi;M﬋]^}VpUkв'ݴlT-XF쯊7k/1}R5l9Bߦ} k/#^h1#}JA-۶-ތ04R}T[4 Cв6oB㸶"Hvl Gl@oqk&RI'Ӯ8f}gS-=X[Kr{14V|dPyUqP9)e,'jܳSr41l$# vy?\=M{Zi#|L*h@`Q8M'?t،f sc֞a#ʚDN|s{?m t,Լ^JUKST#FYf}E}Dwvnu)E.}eN[訃NT~JGvn oN4|oGa#G_SAZ7BͭiFrEB.rʖUz =#G_#׾וM$!U,azFl,*'¡`w*$a_[g>D-)*[_w^ W/#;&};Eދ""0O-|rZ/Ӯ~BvѼ}fR ֯]Ѽ*!FjhE#CöѴV 6*Qj>}"&^X7uw^EFE?j;Pkk3zk\SWђ #:pqTߑ&A L {Vr}ׅ8ʶ{jM`Bp@Q\ŸǪ_ j?mmzUkF-#Ԥ\z_"8/''/?'x=7)-԰G\g]]!̄}V1K&mZE uUD'EB R_.ܟCKyUʧ2vxTG/Ŏ~F=O# %7)Tqx!vlBkWCnmESp_}x?J_%y\Uk~Tz=Dl!TFjȅKB8ިQ@|{N )>h`P?R%%/a!;~G%Ze؟/,OrǪ.X[1s-&L) cd!2fwhgBDyS>)qby9"Fu/>y2m?@b":,6?x;n#^^Υ_K9sk4o"nJ9nny aֈi)-Z^|Dm}zHtnžlzA޷*+/ 6І^@ۅS9_OA3x| h u/]B<p&C-%ڻv!Uc_wlLql©*kb扎ˁ-#\^|^9c1WVS%/_6a" VR6nݥpް-8/j%7gQ*uڶNpF%zsR=/,Uvm=64sLz|OQNb^YOSk~9mڋk/>sQ?"m%Mo=]K/;l{K9@9O-Z?G]0s|y%͙y3ch5h牶l^xocxsJz9tnTlFEwEТ{k6kvQ Ӣ.Z#N* ՋEEد ޵{\TQȵCvOOKlO|{~:z~~}c39sUh_Oirb}7ӀмW#Tj m&grv5PN_H/Uᤘ ۻS^,*vT5xe_oу>J_ .rRؔ @Ihng!>[2}E/RfuǕm#_,UҰV/^A%/-[D=lLesGĆvlXUM-% J>wDNx;󽩖}O[sU5 &i(T.}'ϻ| xn}sʨJOdƉK4K~y#/ʣ⹥e_Vi'7ܣg_:_h[ 5๦`~ذ@ :ΓW^ߟ8ܮwO i[ Qn"쟣4U|-ӘM>`.Ⱥ1m/NWC?{śΝܵlѻgc^+DȬWC]]zd*ZO4 NVwXy+XJ"H}@D*5旹rQDm?Dh¡=qEͬꊩ]J]1&\t.ֽ[D+v4ZDdPygᰪ,^MZxsc[Sv `,+m9g|U]{wTyGmggϦR-|^jmbX SM6x(Wwݻc cp~C>ir</%s*_O>BV]#a}~)X"7;2˨\ ʾ/o"mp{5PD,fDT:KH#pH,Gy6>'V[DYJk ǜf1y11S kxz^M5.u`;3gҍû0]>oyL sH֋ôV MbQ_lR56DgˬTh_6P[3X+5)T>t"z2HqkG] H 5pE4cmR*ߥDtnmbLKC*~rS3G|=-mƒxIv+6M>Eߤ!>=5W+S{rؙ}94uYL9͡"־jUd:^&ȍ"Fxs(-s .9R`b!vO&4UگYYRN8ODVҒ Z _#G-{ZtYmQvU"GsD񳢔)opTW  ѡ#RXm8SLJ/QhRpa}o ;ŐY47[T9ax[ 9WWޥQW"=HB/S®TڵuU(U})_Vj=;o2 ?V^{[82Vp(ajrβJ{wsa^&LBC-@`e| NzɯsBe>\/ ORmmV3p:'΢PwkQ?0Q ZN+|aB>Lk*ia˅7nwa="NM/u,nܡovU%"o J9eXQA5b~ۼKLH_/S#OWpW3<>Y]}i", Dr.rL[[Ji{"LEcDGV)m|{QnT)g{F>wÅ5swC`NӾtQ{j*KEDz/ Ϗy/ r7uO6;!{#/>{92TSTU#mXM8t<R]' @F}Piqg!n|X镓_Ԯ Oskه.l'8Fe?Cw%?}LO<kbhHbҖ#tѹ=|\4Ws?CھZ-l؝iyiv&W#I|Ջ_K.#;<[b{=Q*bNҒV1CŞ_:@R/^VpC+О)披^m(<̕4n_CoErRL;=s*% ]H*tRھgaZrd=!rj4=uloIw<F"sNDuA/>@53v$kǣ~ǀxs볗| C8W8oWy"rM ={$U?O[I1" 59тݫ<< Gpnzv砦RmSM4~k{/{}pќ1tí-6]Nx^\_ ?t"2zm rF%*e^ ޼E9Ku8|WEN9}ClɥzT}1_=p UgWMgYI2V3JNUb0*.9+SBDM1<}bp7Rk7Ӏ>DVD8EUUDZrhF񉓉Zf\TQf Y%sl9sOgE8(D9vRQ/>WD ĐNG|^8/fĶ/'Qs4jZQ&"/!k7 l@u+v=CI +Fy/^Ķ(eϣocKp#1mNe{ -4Ox5]s,"+mh{l2?_!O4=H N0H}՛&rv\ޒYnyE3(ov/{"?MRau{mkܰ  &c֧h}dDR8FX%ޒح('ckD ?1׎/2Owwla9{_!WKBWW4l:NsE ZG.icfLlِ+ib >58a &Z9ʙ"=Cd[GRLv`4؇\աi -at8@@@%."@@@%ctnJYI`;!N O}8Z46 Fpjcn]wEӧOt={PCCr7MKam۶ѹsk[nrss*]v\ghR8#hR8#ou333ZdUĉu~wM^Y:Q6؇o`#tZi{h_4ܪj+>1Ehoo/tWбcG;NRJq77:auO0GD0#GU>>8koܢBF)iկhtAȠ3f(nz뭷7 555QAAU?8χ~H|.\PkVNeRG );>B>RH6CG%R J E} Tд!quHviA>Ս^ͩR_O^Oh&rhhhHl<G}+_^ޮ.e%\ag]QQ?od :|؇l`tgW!M" .2$Nqar?䦎tPnbd#@ `D巻,]yB~D}2\C>y(G)CYN6Rr>NTlꐎc*8ω'vG9ȡO4^ԓiSz3o,^~8GX9vgyppI`x%R>ةRyzϭ>GR;6!`1(5G4B?Vi>~tsOR*馛hʕHyn})y*..V)UTT(8Gp>S2lɜOjOE=ۉ=%xC=9b.g8ct{>1jwF=)Mt.ڵg6_\vJvN|.Qlu赌3w3.d_v[-qN>fu*z ~j?ʂ|#z!: OKF$Ū#,,(@jdv^>jBbg\΁ѫK Fl#6XQaP:L3UGZ~UJf}cn4#fT)aO0tNڂ ޻{n8.i=iT2lH@@цOC ▘F*&2k?>w(le¯*rđmH    h}1,`&(u >xQ&ڵ+n'sv*Hõ# $&=2h1TnOͼt3~g8`ۍ>/ݰud+nD@b&hVͥ* @@)M&-QnQ!]Ce뉝rH    `4h}7|DA52uO\f} MJ*MKE}_룞nHFtk 20-~RKgc5E3~|zS4m+}Vu0m+}VumP#yؔ bf[  `?JpFh+zrsn}ǡY@@@t!K%&6G^Htpl(dc[udfۈNb>Lmj:6u??ly:URD׭Qb' viU p.h )]ڱ%h 3H n,XKpYhUQN|$mۮZz CmGJ%v!}2f61P9@m/dc$3l$9 q`Vҷg-G^ xxtZhC/G]Aѹe>[=ZЃsˀ}8Wvh"%"MI?.lC )A []3#gr;htC~D7P   q0I2#MEK E1>[Ŧs-j¾e#1T,6 $c[ߣOYDq*iB%w;ID9u"-}$B-u΁}el)"d dp[c4`b" *-VתP>z A@Օ%-r;J_47 +w >Amm) ^w!(Gݽ\l  Cӆ~ ڈF )S¦tm3tiʔJ?(.\]#L;D>`V@DQ7@ܵn5hBmV.1x'/Y `qPK ZO/""J ZGgOzH[K EI>(6;:X->CLFLa&^=؋W&y B,. y<ʢ ~~TISC `* |j}hQ> ,ҨL$C.;TU&VkXU7O8:0P0 U8D{kgrb7ݼB+z=}Y    b}mi ^w!(GT'{E7k&cB}ԁegfL-.i>xj>e߸KYo]: }0(5؇ahQp $d;{tbJIҚRDM `Sbgy(6 I^mEWO(_Rǔ/槃 p.'Fgff>kwjĉu~wn2i@u=E /8#"1D\#D e—%O|x=1Pe7d'Tar^ZZZ>O /""ڎWL#䑫gNSNEx)pwK bܷ̠l:`՝>̠O}͜Rfg(6^Q/cD `/|U<~dSqX4 $]6D[>D#M%Oph};4ÎB;Uf:tȩC$hMn,$YNaZ3)ёkڈ%4'}$E^ɹkEW?.9ʍ1Y 7zSfeir-S$P9-BK$Гʂ}8Ovj"- @~|.Fp:(bhŻ ,IDń)LzN Mz[@x?vmw?xn9*-R =D:H%#1u MR74D?1R]"-1v>+s;!{b`qK`i9ۉ69@`UpPm.T_A@@ED>)%Ts@@@t 6 u&Տp[+iTG~^/! +S=$<%#HPE!]&o6*EM } 20D6 aC9HdD9Hh3 ֺ֔-fyEٔ)@L*P[)֨a"2ub@DluuZr+dr!MCE̦N6wFم{H:>SKth#+2֨IfW8Uް©JO+t= >鼲`өZ6;i IGʵ;gͦµs@RGwtG#!M֖KKv+CN'ZKz#k v0"ᜲ랲l8s׺G1`zqy<v ZNmvF32"gA@VĒΌ3$rP'v뽪h4hF"O`?=F:>mtSc X)_:kZ@D[(ؾ{*? ^*=3ATCn,@U$C 2VZ6il?N:\l !F!>d!FRKDZ `Qs>\̩ XF@hL8ܢB*9&^7xi+YDRoV#C   'Fgff> N=ĉUbv9!g8] ;m߱AS6KD=Fʲ@T*l]G:E #;WrE%gyh3yhc_O&MѣG??"օ 'ϒJE8 QiM)kx(G ΊU"* ƑnoU\$v'ixknAt@@m@m%%%t҅ (;;رc^wU`oUx*DD[(шfG?Ǐo~C`QC&Uk*'ONZ"x{@ !"ڍ#`JvZ896381m3T3tyR?|~fƌ!ϥErz|N,/c=zebJa{#9/R</:&V=ʚc>umuhv-zܮ^Nkipo]*hEqN|Iļn{'6Qӎ]>BYc] n{9OSz=T؇m#>%a֯smJW\deB<ΫZQo lzhi3)7=F7})\ƹJ[si?~nؕce>6b塐aRӧ 1˛F>Sg`/h!^Tt݊ /8* B J >i`li.ٕU_U ~@u AՎ>2mlC2@T֋#bm8T;G=Խv*UfGu/v(Wx DovuQ/1qD\ǜ8tюhN+mr;ѓVՓqe6 ^*0  ֺV:?QG n%s*ŋTvO/gHC6jH#CFWznG!Kk}cl%{pP~-l0S`74*ҡZc哴uneշ<}*MT3?RM/֗@JGhz ]*I 'MB[ȺP6@x| %DɩH>L]vzKA:Rk2d/'xQܢB(" NU>yөix--{n8WbHf&؇QW(DwЉ]'֩("g"VWw}#QѦ90@-Dvnxk7#] ٓ@aVBD >%gy}̗,і8; a yn[<&@!V ,5 DZ}ށ2\`$u L/E!1CR@@%6sy\[]DŌu&CF("tV wʦsl-/=r#?"bhEȩ'Wq}=r \%Tz)gL'. %-J/i?]2l@ @DJ֊hxe㙔bubˈUFCՌV ̈́)M xyi @}SZ:6^g 4z~1:b(DńI$]4sm8`>򴵋H<+"xS^TooeShLfw(ej>%^>dFdYGEOj9?Gn^Ce7ͧޥV21sT~ %G@FHE8[O=i["ڌwD&3_Z>A V %v"Ll; YF ZET$V7RG p*Nno!yI+z:}~pCb7DÅ# N$c6c2,}9ٔCi^CS8J5)d yá@ xmm'_/VWXwY1wvu* YQm;/Qn&v98-\EPl>y?ExK%#>/ >gG\#_.:Dүs.&$Sap4SAPnZ?>AAW689Es5O,Qqj4\W)u}d9L) WznʯˏŒ[=JySc IF+%Zγ/dl~ϗ G$ 3sqNJQdQBbenjd:TgFñqGqasl4o H#!l8I')Ѧ29r>Q&б }N Wv 9K*/*:4}= #^pSk SBs(W*81<5Qr);|G}wt-JiU#`)ԐBdDk;P*JYţpxYN'+.zLHQ j4HGM7[if#1FC@@~Nh+D5H=wXc$74kPi#lsh!9LoױN6gXy.k5j#VRS#xc[ߣZG^,YQz jAn؇mT%Fr)ѦbDl0VL }>>p9h5n,qWLx}ĉtwSNoڴ4:y$t5-BbCѮ]6 `ZZT6d;%qf HQ@ $e|x8 ^w-JgRh Fوm*Ј\SKҔъ+hܸq4fȠe_kkc'V}k_ OМ9sk .:bَ=b1 }_>zT4s:| ]Fx.? ؇mV> cδ:WxX ^w5]UJcTRh=n_Fl_MY#,2jfc+ѫ  t#~eimm-͛7OwQi;0 VG[LJ"rMa6x)5 񍻔x!YCM)CNTp k'#{$DW7 ̰^j{ iʀ/.OON_f}X/}'/Qh+nK98Z)[؇!!} a {l2n%}\e4T"Bw0FQ‘m齻75`|{{}|OSS';W 6GaGQS#&L6p6Nj=|]&s~+sBGiǪ5ċ#ʤytM"^,C⿾u>B)]q{UF}Ie>C; W<1 U4vQށQ&{]~9\QXQY|ygfc=V>c^bl$WUٳ+_ c^EW!塤eeet+CCCN8NJeܹS-.O+2iQ`#Nw7w *_66 CބL$G"R؇}tUG1t4(7o,f;Oڈd-w' Q;G+1ٽFeVHTbOMgL}Գܷrlɪ~jLlK/Tnl\vF|C>J #̩2].mu$͌0}}GwN: G[+q!e7dGȁCN#CFy!4͢=f`g[ ()Ӭ*Q ~4Crz3t~}9ڎK]K'_BSozNR)P l3#C >B݉s!- ,͙W^(00G.>Q JE@@&a _SXiRT~!kNX+{jMۨ{fRK[S hlztZaA@a >¢?5)}Hm1~he)ϥǐ;,'.3e\e4MD+ F G V$Fxư5mVEgLMCIVn7Uo= .9a[nN:^yhףH+T^MM%^TЋ3ˁ}W~5&։mpbbU4vXr'!$/e26dtzڈw/,dSڐ|5k ~xKԽBTPҧmpҏ:g*rʿ&]? 7=#)4v7]4vR‚C2ƕ5~y7Hɼs aɬ"G5j"2j"lTxEҢE41tvRm}QmLm孋} FmǭciE46d'Nio6ZT&lJ?8ε)_&͝Mb<]cu˰p$ε"Kh\(o67E"hVG[ \H q[v:t(ȥ wG]j~֫o꧙vijP.``s95SEts%d͢«G9>鐚)+1lj^vpr'F8J}Q蟋dNG4.ɂ}ȧ$N.8v&(ʬdkp""nl3RϋKn<4^^j9i}ؑq4:hܮpIf1V9~ Jf q~H ]j!q6 AV\[]/V23Fk<īrCC$+\+Je5 `Ҝ!WqB)vF, c3չN>B~ {5+.u2LOvO~( c#`s-PB8iHm>Ҍ Hʰ MG;{7lY-ZIVN 6bvya*ԡ&1gן=V"hS/J~3D9,TۈO{}K_HݯDp:47siVdyW,>yvĢ >*FG {;m\ dG[ tqCKY&BdxT %|.faK7:Tk 'ͨ!#jH]~ ]󈇒rtaegϙ,hƫM3kd@vUhcyEUDdtR-V;A2P;}'1_vFgLgm'g"$1Af=)f'J+1Td])/2\Vͮit_V,CuMzʿ,>v*x uaaTn"/^HE/aEn-KZРu`t+-Z'l#%>r[ElVs \X-^HB s-m4$ؖ(fS`$@r" -TRֺV8ζAڑm* vSy^eO}9+ tl{qεpB82Z m8iH-qLX3r>ILxXc!Z)0F5gj7swuP{jr596=j.+J+ѹ#Բ:gL۪KKRNguppr* \ui^@Dm95r-^V,Pф̊w~;}H/x\ l&m4$ؖs?VLI8l Rh)q4'k\Nw|x8)6ml됻OMɕU9  `!䟝?{}$W22q9Re>m-<>pŪHÍ#n_";܎_/\Y"rli8ZMdwCRls->y GAJ?6wA5^bK= `g.cwvRm}vTSѦxpn;ISUf| #:0$?@9e;˕( e{.B(\Gqۡ>:+cpĜ^%"nmbg[ߥD%kqᯡqx/Aʼn}F\?^L9'mi7[]#UosूuY`3rG]g̤i!Z)i/6"e[ Gg 8rAD8|ߚR@K&B=CEt[{pRR:}9+9:+x`Ss$ZCh|obxc}8uG}3ʎ=v =1-vMvG8/;ܚ+eV""^P;/:`4!=bpbKvGCJ#%u>I+FVA@gPIm!e[](YcRG[ZHpR-8@=yGX#󍇥wo*R⡨xSBv398&?^ l tyζQ>!j9"9kdEx¡5W8€jmD4uJ?֋qFf"ÞxYg7Otq$=ѦMgՈm&}<[bA5ܓl2.V+$CQչ!p(*GpD^Np|wPnbR1gʉXCX2 tNbE; mi6Fj+%k&ʊ'O( R_O\RȢ<}ZXm/ɧ&(x8)a*M؝VDL!fz [PY9ʊ Eeg#/ȠEzA9"[mCw֛pSYpš>38u8j J4g\2\^tX8Jp;sbC^x3;ngTT҄Mpp+|rZ5LmJb*(Z'B$˄UGMTHcŖ2qW(fը~:6ScL&%;*r٢+UKM%O z[i:B 48d1FqjZq26;xIf+jnj!vErqeNm)-[]V Gڅ-UE}_3g~rpdЂ2FK7 +1[Z1kip۪֠>8bWW];TjV8b#f [:z'gg"bזjĎO.Cm^8%q>萲s{TW*JD+{4'_sJVU?U6Gf^qAe &%Gd񹶹(._2ֈ?:dG i<8j{(+;tMh} jDe3D2Np\fdG\Y507fF@0#ЈVt>]O>sU)VST?oU޻fO6Jp5'.3A H+Ǐ_=[?5 Aj]v ڤ2jӫjU׫:Wn:Dر2ӲT{w_o kg >r~Ԭ;w{fRkm,6E84oeb[=1YeCReơv4/1@c>9vŔFef47o}jUr25~Y1W3Ȓ xiq:ӆ>t)Wh(w>'᜚6d"j4{xvr.9yCrg^Ali r Z9P@2jhI4E~Rum0Yl@ 6{@$yߋ*mۿnmGء|#&M'֟r2H !͠y`*cM3Z0)GYq^HKfuY {NKMaVkP%G[%R7 w#!cvs8])ue/Pա6dҼ#yЇG}.1s slwͻ~eJl -Ж DK][Uu]]u'NmI;}^; U]!ek@1a\l2ξJ~dXUKڪϼ`q R݉?>/HQD+#4-i d\rB7?N8#`t]?aްR6W=E">rQG>JmMܺ`Rbڪo3q 7 @ 9.PzB^"G'@+ MOyצW*ՀT TZa1fv@ܫl6#>Gx$̲%ewJ~Q@@sYU FU]+?z4L -_\\ೢ86%0!K͔ qR|}Eqm# і/G@3IW\.:[.yoYN'P(},x5Ȅ# <@4Vc+z@I貤(G,XNECj=wʤG6H󧯒s|k_]P і !@ML{B #dwұCΌl{vB{OF[hi PJ͟2]?+Qqq1d##{4R˜^rNfnl%re9usm =@DZWfyݰINtuY//䪿LpO!@FJ 8DFix!P4ԤS֙hK7 $.pMqmHM+γ/&C cF۰ڿ NzwCjMk6Jl|/TTrɹ$=WH 20ߝov;::r#] r 9\^7dȐۧ,KGMƬ*@UGVE+'Nț#'cΝbW*Ȭɔ߽ҮRzrq48v2lP ?g~>kWfՅ">fO p!m!@I@@7Le5{ ц @#0ߦR %h,'E7د4K&x] "G[#oO.z<m5E{MlmǶU=-a- zxm0O]tVovw9`]ɔ ,|R48ḃB6}ǛCoџLKs6X{iyyu퓢s&KM34_uιBہ_rwX5I=GF[R=O!@ ɞR.߲FaطgΤV;vlTp`' @ 4$sR i*p1瘬)v(fi] y@:|;#fU0@Xo:|6C6CpS3 8*s#gN\y [D3 eM"D3*%]W?MVlp=_4#fAgM(:ܳ-H37 ԱK`yR @ t)Jo^3J5CNOk iPhNtu9o;yXUD3ptXF |4ESV0vojdݎsK@tjJƝ(_ܯvY:󂹭nQfL5=g#;}3 KL,@ͱ<% s @5HF_t]%wt*K 68^ $@J8d`036h+*##B0ݱ&tI][?_?ovxt@@uYimmmn}x+jM4{Mh 3S{j\mOF:FoX @I@kp[#52'h0 cFЪ4 |d8@#dό:#|E37fێ. c>\ Zwp+؄>񅋖hK) $Fؿ;'N?8#@F[p,  x - wd @N Јnpƈc]ۼov>lt%X"Wܱ } -A.z%96і_S@H |m:ceR rl x -^t7d8 BNIcЈn ݨCw7F-t`@22`r1 2 @} @* i  dm6۩I0!Qmі(wY@ @F[8Q3Jr d;)z,ْ}LƁ :n}ퟰCa{ іl{@&zFٟ۟I{{< _s=W "uɡCK/|32b9yܹS6mڔ @dCk (D@vΣ_[}$MMM{{rmɨQdСi ϗ;O'7N,X`]RSS#կKfΜ)_~yr_qP.x_>r{>%Gzrطض.J}J.9סR皆9ԩSrLߪrȇ~(V'xB+cǎVd{g駟ӧOA 96#>@Q*M@[ ~ +EG>.AKXq0gu7ސ+W#|!9bi>:a„tr_qP.x_>r{>%Gzj\70Im$oc̒vI_18^Q,f[Pf8`<y q0H` +>Av} XŠ9ܱC6p>tc&91Tӧoiidu6ݼ'Ni9 bi6i$^JЭfs`.픮j?܇Ar~wugo/W?c*gF7V @U%֪:a:L_G׾5;w-Ю@/ ~3ym_WEꪤ^;S=!~UG>Pd3vع4ƪB+w~ 4oꖽ.'N{OF-]w]UY=*= JSLyUov;~kƁrAG=2-UB }}V}cCf)A8fq!>sC_ޮ:z˵^kJK/ȑ#eѢEr9[o%?|`3N`(cY[ e,+Btkd:Q\|r!>sIzkh=Թ^P_lo^6ۀ%Ќ> /8.2%R'q(K!@"!@8:H5ۀ |d8@# \T868`@>#+ @ph4a @ QhK, @e x\Z%HR#wdퟰCa{ч :>m? @ `d @-2bZ7:F~p gܱ -A.z8@Bt8>@!Um&L $mr7 @ dK ۠8$@IZ }$nu#l>p?a[>@'- LR @%@F[l]F6pZ>\;vw|%E`hQ(D}ùJ ҄ D -Q A2qiqdg#w4SRKz~q¶}Gn'lGHvd% 0P @ hknU+Їq.4/\}l" "> @Bt8WidU0C (d%t 2V<.-Nl⌒\}$FqJj)Tϻo2O֡=vmmn?mYxǥvHt?%<j/g_3>}9~|eWdjTb},NH @#@F[|N!@(mqs|b'#k 6~V& 1CȄ ߐ~R/ . .^~ɖ!`ĒU}$ߥB-9נ:J=% Jު̣"o+>sZ#P294Ã?Ngm<{4IlnrAݽ۞>d\Tw>uXcvl[)qMB8n2%R'G;w՘\-Zߪe5iE`hq?l'Ϝ=NG_ϯ; ~h"#}і:tweJxAA t HK*)\B.C qtĺIAVesGG91U\Qe4׏@|3>4<ױ}uN56zA=H|џoOV4vi.٦H6fBi=B#AW}#^Ko8'+Qq}œ WjG mPN_כ.3{^6|M6qDOʿw 8]0iX]s-?f̨a;<ޗlRӆ.rr,`-<@tqʘAs9Rj_~g}{|/{?B ;GчH .>|]G> Qbbh7ˋG^ZW;woy—Ec?zp/RMEIÐYq`gƈql9Ef5̲]e-r~z!#z@@lu?q2NN<)55fu{رٕ D@}"ҍ~f~G"~գn%ּ+OӚŦ #] gמmo/5Ck'Dyt;\2|'Æ CGm#?5cerdi cYfSGӋ)d[lKo|[P=tP칢+*{ 1sz֭63ˍ:R?A;=wqy yճ @ F`蒺_[]2JE 8J>]ЬڔĚT$feHi9yE*CsPX*,r9cN}t1U9@֚qy%$H_18^I+>qmз2 @ bH@@ 1UG+ 4lG}#qHE}± h@p\ tP(Ds&衣K $m9= @4dƍ| 'C u F+EG.q'h"~FpR&Ӵц @@h &UA kdڽwl}eaqZ>\L" /{QPϣdXG1B$2*I!@H2r: @@h+#@?NI->yF#Y%$H3(L Go}0-Daҧm2 @m¤*@bMX7Αm\}7lC#nU+ЇI]d$z>Jyl=(F$@F[%R7 @#@F[\N!@(m%26)GR=h?$DI}&}i!m}#LMF @ -@T@@ k9 mh ?jp3ɶdXG1B>>bGqh$] @ qhK0 @% Dp\8%HgĒ#^wd0-Dawmᾏ´}IhC @ @d @52b;GA>pwܰ W@z&vql=(F(G_!WmK $ms9 @Dd BI~X}$}#L>Q06mh HaR @&@F[~6.[>\7U$.2bG%qྏ´}Iч> B&}& @  L @ h{e Їq64\}dEA_dGޣb8_IdU.uC 8d%t  V"8.GlZ }$FJbIDg2Q06pGaZ>¤Od@  2IU Ěmvo# |lp;n؆FVW=l8H}##/{Q+IJҥn@GĹC P"2JemSRKzшVI,>uLƁ> B&}F(L Gi64@$@F[0  X - sd-@.{ Ј~p gmޣb}}$z>|% VI @@і8a@J$@F[ qJj)T7*%G~8pGaZ>¤~}i!>mц @@h &UA kdڽwl}eaqZ>\L" /{QPϣdXG1B$2*I!@H2r: @@h+#@?NI->yF#Y%$H3(L Go}0-Daҧm2 @m¤*@bMX7Αm\}7lC#nU+ЇI]d$z>Jyl=(F$@F[%R7 @#@F[\N!@(m%26)GR=h?$DI}&}i!m}#LMF @ -@T@@ 1&X4`۱msE}TttA#]5,Gՠv_Wdܸqi#G'N9seȐ!rAYn:t(]R/4`Νz#N}D܁6}TpīGw`qce>AX3ΠƮB4;!(U6j(?~=zTe߾}ryg6` [U0GYUpQ̑m}Du0@Cnl6#>Gx$">rQIα'ʰalX[[]Vzzz .?PVZ%O<ݻWƎ[8q|G>2W"> \ 0t҄Q?#Nd %8Hól~ӟNyWdƍRSSc3ݼeܓqPIѯ}D߇$׍>(6GCpS3 8A9444ȩSۏ=ڶ{f_L;&3f̰lÇH#FH_/:T qd}.픮j?B]%чwdz7y׫}?GOw3#6|![3Q[yD&?59r+xjۍ>*8-{ͬ}F2y]lBxvtQo?CC{HΝ+/lL+VsZVW }衇+׌T o+FwaE;>*7򕣏踰>I0{W x{]>5{]fGGlHzMOudhdӹt(>k4i9Rƌ#_~tuu3kͮc@>#+ @u>nY]Sճm|G>2\G.*9"^z|sFw!_ޮ:kVN>mVfA  Ǖ@Bt8Wi 4a @ Q:sڼkpѺ @@0lHƳO+4$Յ>Rztz+Rs ~(% H}2#wdl @ 6\h d:@@, 7  hmmp#><s@p#>< @F[i  -c LR]dGH d‘>裏E\_}a7E>e9 tw8p=>Bwq#t$2~:@@h7zrK9@A m5GhcTĹ|F#F G/n3FN&mҥ2ʼ9VƟ_g?ƥah }q`OtI 8عsg@u\FUfGDh# @n+Pי[*8R:|䰜=i֛Y @dCk ۠(D@vΣ_[}$( kEmp7|g&]:BLN1COu{/uaaL\9Ћ#>.z}.6m Dmp0xk2ӚA2!ox @@>d#@mV>b:FCˊG,ZNiLl\F :ԛ{o}x󿥲r/@A/^ $>2HǂW L8R=dU5-A Qh`UOߴs1@@ C B|yn  N>vO5vHpi\o猺D9J{sfaCf.8oӡzL7͔cG&{2Rנz>"zG<^Oa' @ 8[!s7>{ԩeL9:7DY ys  bmvoٝCe#}h$..,|\ ~i7[|Ӏn m.^.Pk{rIT@,;{N6<Gu-5]`D7,g+4$ojShU3,R=~6Y9㢏rpm~#?TmgL  $mw)絬9 歜gﳳ =c.*&aϙ.Q04}(;`LR̠2('Nc,H@tZ/_a@+7dBk| 7DDt:F+qG\qeyfOFNLei=sZ egymVh ==V5xW>\ %J42st)vBR 2 *fk?}f}^gͨe3LXvϟ١ 0@)Ϗ@2 /R~7B5&fǂW @ld b wTpīGw`L͏bAvrVh ==V 8e~rl|(+krJy>kdTXi]i]eǦ_4C;m4_l5ôLfw^m:ZͲ.ux _&h&-+|0mg @ /2D ք\TZg3ɂz2{6͕aWL족j0r "y<n3+˶(3r7IDAT0y.3Hy^^~tf}fR sI `d@0xɉRp@3\diS#ӦFko2h7?ۤ7%η8K!wl2g5Vk͜,dt\A03}mA3 F`V,mMk Ĝm1w0݃ 0Hv\ūRٷ7<ܻaԟ/:]63iSf`,xf,xfdeׁ p}ʝ T>bXu$WFJ:Hg">\v*syN7;{;-ol]guU7=|Aޅ QH 2C @2ڊ4 8O W^)F:t&h"c5qU(PGQ4)wUXQu4@2R|t"@Yu裲͙W&}IA8 @(DBt8@1,}}U4ۀ |d8@# \T868`@>#+ @ph4a @ QhK, @e x\Z%HR#wdퟰCa{ч :>m? @ `d @-2bZ7:F~p gܱ -A.z8@Bt8>@!Um&L $mr7 @ dK ۠8$@IZ }$nu#l>p?a[>@'- LR @%@F[l]F6pZ>\;vw|%E`hQ(D}ùJ ҄ D -Q A2qiqdg#w4SRKz~q¶}Gn'lGHvd% 0P @ hknU+Їq.4/\}l" "> @Bt8WidU0C (d%t 2V<.-Nl⌒\}$FqJj)Tϻo2O֡=vmmndC 02Ju Ėmu# ?jp3؅F񅋖MdBG!:ChQ*MJ~@EDB P2ʀǥ mPQK${_ш?NI->yMƁ :n}ퟰCa{ іl{@&@F[@  -ucdW@zЈ;pW4P(Ds "> \ Vi@@і(wY@ @F[8 3Jr d;)GR=v8p?a[>u#l$}2ڒz@@h (A [dֵntl7Uϸcq.Z>\ 6q @p}BG!:42*M!@H2n: @@h+'@AqFI.>}}G#8%H7n'lGp}¶}dOF[O!@m:@bKغ֍m\}wB#EKЇ^&2@!Ρ4P(Ds&@F[ S? @"@F[Mg!@(me6((%GhBI&mmn>O֡=hK= @ -`T@@l [׺1 VW=]h_h p+D(D}9 @ph4a @ QhK, @e x\Z%HR#wdퟰCa{ч :>m c#>&?2rH. Cly|3 ɆOc!rџ˓ j1C'}]^~YZc{6sL\dؐaP*wǨt/%t1;vdJЩC؁jG6Dy|+2~x'9t萴u]'CW^G5Ց"0|>} 2D?.O=ˑVj5#F!/9x?} 0g>f>}Z~_ɦMly2ce\Z eB86zvw=={2:f =gzdd|b'Y_}&̿m)R3FveV[Ex3߳AW{^':3gg|V._dձ_ɳGa fD e\du|D fk`{wN!l}̘1~~PQb{T@>jnq{~477KCC>?xbMp>qE̙37O>8p?n4ϳ%wI9ێmY'KvswN#ϜcmLN7<{ͱl]>zqըlȎc;x,mZ*L'_=2~x'0HK}|jԧlac3ڳęd̑g) Rԇ =rq Q]UsL*@~BL6Af-k ޞ[&N(cǎ~y-Z}DRʽdg,CÐ߮uneMl&lz^GJ:v:J|=mE?veܰqp 51‰OʊWu衣󍟷ψϾ[.q9I>~z䪑W_NK\;Y4KZ!!޳Ew=;G34v!xx ߉15Vi2o<9| y6=>rHѽf|_Mhl_]~_STM*ևf4jDmifϞm+<߿_=\g?8vlٲEs JT+'":Q:;;? Bևf1˿6 رYs=[4Al}h$  *6f=v.5tJSt6$GN4k,۳מCo)?99|=&Sr<怎ysM(:'~+̀rlhҋ]/ڀZvOMt}='A;Ģ>[^&NJ-nmE"4hKFS_ $qp79fhu>4eƍ65ߤ~=f+<Ó"M [ xH^jjjl&?묳_ڵ+>]ԠIMt}>=zf]~YU4PYl'K+ ۀ'L|ϖ1cs~E=Qfd~4cI'?ӣ>-:ZҀ'G~R~CF %K׹:W$dCT'>k~#/_:L/Oɾc?w{;}Z^\͔}2qIӉϋ|Cb;r;&NLwԙS~J+v.ȫG^m;O;\,/ 0qaHt&L`3tXO%\e?[gݲ_:% 00  {F3lZn.cy0f>vmYg~_/yϥt\RW} dCmfɐԷn ^.Í?K/l]-5_vGjt}[h#}>nդJg6Qщ>[zzUH/hn>GtH֭[:$[ dÛr@/߳Eo`AmTfdnv<(?ţ/@<-vN9v'>)3g?4af!=ˬ&_^z%e;::/孷޲A_Sou~6KT;]]]VG\sbUևγ_8f {hf>gGĜLo,tu6zhyه~_^+<[]Xpo'?.Yl.u!_t1(l}hVf!6f6̴C_~Y~OPQ=ٌLѕi"J-[| dkNl+EjPD3U;l8w2E4hAUWIN)ы>[: T:\:f30P]siV ]`WZ-{}<du6}h@å-mw+/HY;\*9 Íf) i5M? f=6t(.+޽;=w[9>l}I&9t@Ь0aRP:Nsœ@>2{w l߾V=GW6 f@B{}~hfȶeVև{DpNCs=[t\PG>X_Je{*V-x+钼&,5 {hO1;.SE'U*\9^G@>}hf/ fn  ş@>G|.s9fnh'F<^Ӈᢻw }wf@jdǀN4 7ވnDZ|(t˥ #v2,ffu4 ӇZmx-߳}dR u}o5h#+ @ph4a @ QhK, @e x\Z%HR#wdퟰCa{ч :~l}l7D{Ծv{8>ή1=}ThYb{[(t5'OUt_m^F'}}П\\@ @ h "u@ @ $K @ @A E @ @H<m @ @ @-@ @ x/@ @ A&J®1l~ggg~㛢"Qsn,R[R:@ @ xdϔ!@ @H@[N!@ @'@-x@ @ @t @ @ <m3F@ @  Ж@e@ @ h )5B @ $:.C @ O@[L @ @ %t @ @ x5W[ߔfG)߲^Y#/_v,-ܳ:w Y7eih=;W1-d7)`W -Zɢ{fm.78{xy=T垕uUܵ.i,u"=֭o?GA @#0L{ب%ɾ؋V,s\y_/lNާ~W̓-/+;zD6Of2|`yrTZм EeW*Ktvvf,2ٱni2v=*S9Y>YؼGn|M:Hձ|97{߱u=|)!wL)Cu.2sζU2}7Dؾri<}xn|{}QC/Gm*G"wuctt,\EYL>K㦽~Ϻ%¾*޿MoF3a,ߛ??o%!@ JH]fܓT Q9Ț [GZ, 7[ufUO{ԮniXV R?c,)\! /uulrʭ7*Z:kn[M굲~<ٌ lפl[W͞o]!m&^8&<-zuM[)oT{/ci_/7p _:rAJSAvYjL{Ũ6'Jf^-SYZyM-,ڦ̐EWR*,kׯusRş#-E@ @> $v-| o߾-rosZ?ܥee2/\jZeٽ2ۤguLs3va7μZWwyAx\\QnY.-K mۻ[h/ ^@ @Htm6cmf7˜jBM6hmmi2{y[ }BijmҶCgNeNxe2--T=[2+r:4M4agM g؀I} mm&ϪQf/=Q"cd,465Icf>v+g̿=)a>7"[-+4(i2Pt D͘s[Ek2)&HݴYb'/UoLf 6c fekvӤ߭!Yl`䡵6ˬ?-yDiP딶mFsƋrͥSوiB?Cu#{z얎vӯoB}Q}zF'Ӳ0Q'f+:V?Y,Z"˖-YbaSZ syI}nmbK ޾|i{z/6Av́T-,u&0k6[[d4{m]x̨ͥɦܕs~ĥL7n3垬İCgoo]8Ci5Q{s>e@tsqL3¨/]D{c>!tܽR? @ Z6;YyxwEG!5;)jW؉Sgʍ_+3~ԷG{̽f|k}fufP`l&cnn=dGqo VbK\͢j_jƹ3MFfӯoFߺ~`[W-ݟ/_&@W4]ͱ߹ӘqιBl)~dke̹rkd[⼝mwnw,Du.4M; FsWـMcL:i={L}?kрAc̽h;l&WCl[- dڴ^=dsᬠw]xrVuA5uuT0fTY(33Tᄏ>Yy|{ӯ kӯ4ςv`ŝmr9?ޛ7}Km2ώ?7]knm+n̵>㇛2ny{1T,$pK*3sӷskYex4Β2Mw"y_]?e}ͱ=rë}< Sx Ʒ_t(c@ @HnM34[dݾelrLIK&.1ehhdT埭MwFև3J|g 򟼒5I1S}&Mfe<7![`-fx'gչLڟ55g0!!um>l޵) Rx9-2.)[}EX瘌RCaͶg[fC=Q?33NNNJ);lo`IF泘^R3/תoE%&m YQvafsQ,5e672!ʾLFd]prEٱM]B}Q2{ o\3Un6}Kj"Y[]ihG~[ ;GSRuQf,V(z]3[9o15kIVޔ^llMHe2- k4Wbo ^}̟w-tҶ6 cka):g.1ٍZי 2l3w@ I&ʢT׽2lF4 Um盌m=o맵ש=rߧ743Erz[Ԋًρ |mj̊sT(Okc ==AT kl% ٗ)uvƒ?43}qH/Z&o=+߇;e~!d0MfSP#_uwaۇLߑuѦ]S}Fe,Sd_Ӽ7oJ abIz]2 g-0Os>l2[t^-}m1ʑޝWohL7Yv!}benK{Hn2)VʺcqW{:И./~JZ}fSSl]3ǢSϹ9m opE @N mKd~jfʟ:[`2'V,SfܶȂa]ۜ2kAFĎeLF)^~s~췦VeMfJ9Gg:ݲb3^y/ Y3gM>NlڜuنmVMfo*']u+j, wʪ͐ƙ:;&/)_! >4+D[䖖,jUNeτvgi %yT;.(Xa~nf>j3Gv0Ўlmiff}j-&ɽhZ2by_)w4{otȦm܌Y:7fqJyxxyC @A jVԏ^gelu;d{kgv&3$i-kd=2m;;Wn-'kƬ3&{Lʞwu|w{N,^bzynaiz Ŧ;]w.0kKSPV~Bd{S^f~ੁ"kYs~En*-3,fΕgC2mb1de- SC;=>n~E2{4=;Ma̛&ݬyGZ7hCV|a3r;D}*h&7;iT3Uz{mu,y9`0fYi͇8ŏo17@ޡ9m zLsw7V?M1w,X NXZק3/9I[Y<@ o|kMfۍίKooUi.ȢWec4$=f!vvyeW}`+:i7wtj73Co]XZ:Λ}gxs5Mg̷^V^  @$ȌEf; 9mZqؚV/;Viք |,\,&ʒQ+?+k5ɬ:gfA63惶7, K|h]|b҇9~5͚o50x{3s2ʛ++;ʛyR{:ڀ+RMW˽_-ܱʮ8m| H:Ҍw1w}]n-d3ݡslwΝ.PO~%dlR?pY%vŭ3S"~Z6{S]]SLhѢOhul4\(WCA݇^#3ͬz,4wgGoˬ:`9\@u{m ۵w_ 6纷2OU>bwf?7Lm[Wض2GDBzmkm pzKPjά:gMf"fe޵w\i;gO_/̵:y5J{I} [GEE9rY(Tl/cHH=Ha{ݽ  @ ! g-dZZΉaumِY-o'el6.yȴ"<'7 +DZZJ~3^:oԽZ6L\.{Ysgjڜm;ٙp[\}+c;dY)r Ĺ>4a [eђi2ѬcwQD`mW)wul<]>l1\͍xbfφm ]KE>7l(O+G->3}rUZ?p_V@1_jy"w;W Kvm7+P9 0 { certificates, err := LoadKeyPairFrom(opts.TLSCertificateFile, opts.TLSPrivateKeyFile) if err != nil { return fmt.Errorf("Cannot load key pair from '%s' and '%s' to connect to server '%s'. Got: %v", opts.TLSCertificateFile, opts.TLSPrivateKeyFile, opts.URI, err) } config := &tls.Config{ Certificates: []tls.Certificate{certificates}, InsecureSkipVerify: !opts.TLSHostnameValidation, } if len(opts.TLSCaFile) > 0 { ca, err := LoadCertificatesFrom(opts.TLSCaFile) if err != nil { return fmt.Errorf("Couldn't load client CAs from %s. Got: %s", opts.TLSCaFile, err) } config.RootCAs = ca } dialInfo.DialServer = func(addr *mgo.ServerAddr) (net.Conn, error) { conn, err := tls.Dial("tcp", addr.String(), config) if err != nil { glog.Infof("Could not connect to %v. Got: %v", addr, err) return nil, err } if config.InsecureSkipVerify { err = enrichWithOwnChecks(conn, config) if err != nil { glog.Infof("Could not disable hostname validation. Got: %v", err) } } return conn, err } } return nil } func enrichWithOwnChecks(conn *tls.Conn, tlsConfig *tls.Config) error { var err error if err = conn.Handshake(); err != nil { conn.Close() return err } opts := x509.VerifyOptions{ Roots: tlsConfig.RootCAs, CurrentTime: time.Now(), DNSName: "", Intermediates: x509.NewCertPool(), } certs := conn.ConnectionState().PeerCertificates for i, cert := range certs { if i == 0 { continue } opts.Intermediates.AddCert(cert) } _, err = certs[0].Verify(opts) if err != nil { conn.Close() return err } return nil } prometheus-mongodb-exporter-1.0.0/shared/group_desc.go000066400000000000000000000007121301535607600231310ustar00rootroot00000000000000package shared import ( "strings" ) var ( // EnabledGroups is map with the group name as field and a boolean indicating wether that group is enabled or not. EnabledGroups = make(map[string]bool) ) // ParseEnabledGroups parses the groups passed by the command line input. func ParseEnabledGroups(enabledGroupsFlag string) { for _, name := range strings.Split(enabledGroupsFlag, ",") { name = strings.TrimSpace(name) EnabledGroups[name] = true } } prometheus-mongodb-exporter-1.0.0/shared/group_desc_test.go000066400000000000000000000004521301535607600241710ustar00rootroot00000000000000package shared import ( "testing" ) func Test_ParseEnabledGroups(t *testing.T) { ParseEnabledGroups("a, b, c") if !EnabledGroups["a"] { t.Error("a was not loaded.") } if !EnabledGroups["b"] { t.Error("b was not loaded.") } if !EnabledGroups["c"] { t.Error("c was not loaded.") } } prometheus-mongodb-exporter-1.0.0/shared/utils.go000066400000000000000000000024411301535607600221400ustar00rootroot00000000000000package shared import ( "crypto/tls" "crypto/x509" "io/ioutil" "regexp" "strings" ) var ( snakeRegexp = regexp.MustCompile("\\B[A-Z]+[^_$]") parameterizeRegexp = regexp.MustCompile("[^A-Za-z0-9_]+") ) // SnakeCase converts the given text to snakecase/underscore syntax. func SnakeCase(text string) string { result := snakeRegexp.ReplaceAllStringFunc(text, func(match string) string { return "_" + match }) return ParameterizeString(result) } // ParameterizeString parameterizes the given string. func ParameterizeString(text string) string { result := parameterizeRegexp.ReplaceAllString(text, "_") return strings.ToLower(result) } // LoadCertificatesFrom returns certificates for a given pem file func LoadCertificatesFrom(pemFile string) (*x509.CertPool, error) { caCert, err := ioutil.ReadFile(pemFile) if err != nil { return nil, err } certificates := x509.NewCertPool() certificates.AppendCertsFromPEM(caCert) return certificates, nil } // LoadKeyPairFrom returns a configured TLS certificate func LoadKeyPairFrom(pemFile string, privateKeyPemFile string) (tls.Certificate, error) { targetPrivateKeyPemFile := privateKeyPemFile if len(targetPrivateKeyPemFile) <= 0 { targetPrivateKeyPemFile = pemFile } return tls.LoadX509KeyPair(pemFile, targetPrivateKeyPemFile) } prometheus-mongodb-exporter-1.0.0/shared/utils_test.go000066400000000000000000000015321301535607600231770ustar00rootroot00000000000000package shared import ( "testing" ) func Test_SnakeCase(t *testing.T) { cases := []struct { in string out string }{ {in: "testing-string", out: "testing_string"}, {in: "TestingString", out: "testing_string"}, {in: "Testing_String", out: "testing__string"}, {in: "", out: ""}, } for _, test := range cases { if out := SnakeCase(test.in); out != test.out { t.Errorf("expected %s but got %s", test.out, out) } } } func Test_ParameterizeString(t *testing.T) { cases := []struct { in string out string }{ {in: "testing-string", out: "testing_string"}, {in: "TestingString", out: "testingstring"}, {in: "Testing-String", out: "testing_string"}, {in: "", out: ""}, } for _, test := range cases { if out := ParameterizeString(test.in); out != test.out { t.Errorf("expected %s but got %s", test.out, out) } } }