diff options
author | Vadim Ipatov <vadim.ipatov@zabbix.com> | 2021-03-16 18:36:16 +0300 |
---|---|---|
committer | Vadim Ipatov <vadim.ipatov@zabbix.com> | 2021-03-16 22:38:34 +0300 |
commit | 96f79a8cb426f23fb48a2844c9eaf59ddc4bf434 (patch) | |
tree | 108ef32d949fab676636eda22684d4092f73ba27 | |
parent | 0e9ab342a8f983113f452fce0ac67439b2e17240 (diff) |
...G...... [ZBXNEXT-6408] added mongoDB plugin
(cherry picked from commit 4b3dd53b207a0282cd2b0827702b5b1c11e90969)
43 files changed, 2752 insertions, 26 deletions
diff --git a/ChangeLog.d/feature/ZBXNEXT-6408 b/ChangeLog.d/feature/ZBXNEXT-6408 new file mode 100644 index 00000000000..1f2d6b963d2 --- /dev/null +++ b/ChangeLog.d/feature/ZBXNEXT-6408 @@ -0,0 +1 @@ +...G...... [ZBXNEXT-6408] added mongoDB plugin (vadimipatov) diff --git a/src/go/conf/zabbix_agent2.conf b/src/go/conf/zabbix_agent2.conf index a85be944fb6..a89bc39d832 100644 --- a/src/go/conf/zabbix_agent2.conf +++ b/src/go/conf/zabbix_agent2.conf @@ -550,6 +550,72 @@ ControlSocket=/tmp/agent.sock # Default: # Plugins.Memcached.Sessions.*.Password= +### Option: Plugins.Modbus.Timeout +# The maximum time (in seconds) for connections. +# +# Mandatory: no +# Range: 1-30 +# Default: global timeout + +### Option: Plugins.Modbus.Sessions.*.Endpoint +# Endpoint is a connection string consisting of a protocol scheme, a host address and a port or seral port name and attributes. +# +# Mandatory: no + +### Option: Plugins.Modbus.Sessions.*.SlaveID +# Slave ID of modbus devices. +# +# Mandatory: no + +### Option: Plugins.Modbus.Sessions.*.Timeout +# The maximum time (in seconds) for connections. +# +# Mandatory: no +# Range: 1-30 +# Default: plugin modbus timeout + +### Option: Plugins.Mongo.Timeout +# Amount of time to wait for a server to respond when first connecting and on +# follow up operations in the session. +# +# Mandatory: no +# Range: 1-30 +# Default: +# Plugins.Mongo.Timeout=<Global timeout> + +### Option: Plugins.Mongo.KeepAlive +# Time in seconds for waiting before unused connections will be closed. +# +# Mandatory: no +# Range: 60-900 +# Default: +# Plugins.Mongo.KeepAlive=300 + +### Option: Plugins.Mongo.Sessions.*.Uri +# Uri to connect. "*" should be replaced with a session name. +# +# Mandatory: no +# Range: +# Must matches the URI format. +# The only supported schema is "tcp". +# Embedded credentials will be ignored. +# Default: +# Plugins.Mongo.Sessions.*.Uri= + +### Option: Plugins.Mongo.Sessions.*.User +# Username to send to protected MongoDB server. "*" should be replaced with a session name. +# +# Mandatory: no +# Default: +# Plugins.Mongo.Sessions.*.User= + +### Option: Plugins.Mongo.Sessions.*.Password +# Password to send to protected MongoDB server. "*" should be replaced with a session name. +# +# Mandatory: no +# Default: +# Plugins.Mongo.Sessions.*.Password= + ### Option: Plugins.MQTT.Timeout # The maximum time (in seconds) for connections, disconnections and subscribtions. # @@ -776,30 +842,6 @@ ControlSocket=/tmp/agent.sock # Default: # Plugins.Redis.Sessions.*.Password= -### Option: Plugins.Modbus.Timeout -# The maximum time (in seconds) for connections. -# -# Mandatory: no -# Range: 1-30 -# Default: global timeout - -### Option: Plugins.Modbus.Sessions.*.Endpoint -# Endpoint is a connection string consisting of a protocol scheme, a host address and a port or seral port name and attributes. -# -# Mandatory: no - -### Option: Plugins.Modbus.Sessions.*.SlaveID -# Slave ID of modbus devices. -# -# Mandatory: no - -### Option: Plugins.Modbus.Sessions.*.Timeout -# The maximum time (in seconds) for connections. -# -# Mandatory: no -# Range: 1-30 -# Default: plugin modbus timeout - ### Option: Plugins.Smart.Timeout # The maximum time in seconds for waiting before smartctl execution is terminated. # The timeout is for a single smartctl command line execution. diff --git a/src/go/conf/zabbix_agent2.win.conf b/src/go/conf/zabbix_agent2.win.conf index c6a5190fbe3..b2cc2caf8f8 100644 --- a/src/go/conf/zabbix_agent2.win.conf +++ b/src/go/conf/zabbix_agent2.win.conf @@ -535,6 +535,48 @@ ControlSocket=\\.\pipe\agent.sock # Default: # Plugins.Memcached.Sessions.*.Password= +### Option: Plugins.Mongo.Timeout +# Amount of time to wait for a server to respond when first connecting and on +# follow up operations in the session. +# +# Mandatory: no +# Range: 1-30 +# Default: +# Plugins.Mongo.Timeout=<Global timeout> + +### Option: Plugins.Mongo.KeepAlive +# Time in seconds for waiting before unused connections will be closed. +# +# Mandatory: no +# Range: 60-900 +# Default: +# Plugins.Mongo.KeepAlive=300 + +### Option: Plugins.Mongo.Sessions.*.Uri +# Uri to connect. "*" should be replaced with a session name. +# +# Mandatory: no +# Range: +# Must matches the URI format. +# The only supported schema is "tcp". +# Embedded credentials will be ignored. +# Default: +# Plugins.Mongo.Sessions.*.Uri= + +### Option: Plugins.Mongo.Sessions.*.User +# Username to send to protected MongoDB server. "*" should be replaced with a session name. +# +# Mandatory: no +# Default: +# Plugins.Mongo.Sessions.*.User= + +### Option: Plugins.Mongo.Sessions.*.Password +# Password to send to protected MongoDB server. "*" should be replaced with a session name. +# +# Mandatory: no +# Default: +# Plugins.Mongo.Sessions.*.Password= + ### Option: Plugins.Mysql.CallTimeout # The maximum time in seconds for waiting when a request has to be done. # diff --git a/src/go/go.mod b/src/go/go.mod index 24eb35b8554..88f81159bd8 100644 --- a/src/go/go.mod +++ b/src/go/go.mod @@ -24,6 +24,7 @@ require ( golang.org/x/sys v0.0.0-20200428200454-593003d681fa golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect + gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/src/go/go.sum b/src/go/go.sum index 557c8d15fa7..4ec168a030c 100644 --- a/src/go/go.sum +++ b/src/go/go.sum @@ -1,5 +1,3 @@ -github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU= -github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= @@ -223,6 +221,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/src/go/plugins/mongodb/README.md b/src/go/plugins/mongodb/README.md new file mode 100644 index 00000000000..eba29e9f4cd --- /dev/null +++ b/src/go/plugins/mongodb/README.md @@ -0,0 +1,143 @@ +# MongoDB plugin +Provides native Zabbix solution for monitoring MongoDB servers and clusters (document-based, distributed database). +It can monitor several MongoDB instances simultaneously, remotes or locals to the Zabbix Agent. +The plugin keeps connections in the opened state to reduce network +congestion, latency, CPU and memory usage. Best for use in conjunction with the official +[MongoDB template.](https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/app/mongodb) +You can extend it or create your template for your specific needs. + +## Requirements +* Zabbix Agent 2 +* Go >= 1.13 (required only to build from the source) + +## Supported versions +* MongoDB, versions 4.4, 4.2, 4.0 and 3.6 + +## Installation +Depending on your configuration you need to create a local read-only user in the admin database: +- *STANDALONE*: for each single MongoDB node. +- *REPLICASET*: create the user on the primary node of the replica set. +- *SHARDING*: for each shard in your cluster (just create the user on the primary node of the replica set). +Also, create the same user on a mongos router. It will automatically spread to config servers. + +```javascript +use admin + +db.auth("admin", "<ADMIN_PASSWORD>") + +db.createUser({ + "user": "zabbix", + "pwd": "<PASSWORD>", + "roles": [ + { role: "readAnyDatabase", db: "admin" }, + { role: "clusterMonitor", db: "admin" }, + ] +}) +``` + +## Configuration +The Zabbix Agent's configuration file is used to configure plugins. + +**Plugins.Mongo.KeepAlive** — Sets a time for waiting before unused connections will be closed. +*Default value:* 300 sec. +*Limits:* 60-900 + +**Plugins.Mongo.Timeout** — The amount of time to wait for a server to respond when first connecting and on follow up +operations in the session. +*Default value:* equals the global Timeout configuration parameter. +*Limits:* 1-30 + +### Configuring connection +A connection can be configured using either keys' parameters or named sessions. + +*Notes*: +* It is not possible to mix configuration using named sessions and keys' parameters simultaneously. +* You can leave any connection parameter empty, a default hard-coded value will be used in the such case: + localhost:27017 without authentication. +* Embedded URI credentials (userinfo) are forbidden and will be ignored. So, you can't pass the credentials by this: + + mongodb.ping[tcp://user:password@127.0.0.1] — WRONG + + The correct way is: + + mongodb.ping[tcp://127.0.0.1,user,password] + +* Currently, only TCP connections supported. + +Examples of valid URIs: + - tcp://127.0.0.1:27017 + - tcp://localhost + - localhost + +#### Using keys' parameters +The common parameters for all keys are: [ConnString][,User][,Password] +Where ConnString can be either a URI or session name. +ConnString will be treated as a URI if no session with the given name found. +If you use ConnString as a session name, just skip the rest of the connection parameters. + +#### Using named sessions +Named sessions allow you to define specific parameters for each MongoDB instance. Currently, there are only three supported +parameters: Uri, User and Password. It's a bit more secure way to store credentials compared to item keys or macros. + +E.g: suppose you have two MongoDB instances: "Prod" and "Test". +You should add the following options to the agent configuration file: + + Plugins.Mongo.Sessions.Prod.Uri=tcp://192.168.1.1:27017 + Plugins.Mongo.Sessions.Prod.User=<UserForProd> + Plugins.Mongo.Sessions.Prod.Password=<PasswordForProd> + + Plugins.Mongo.Sessions.Test.Uri=tcp://192.168.0.1:27017 + Plugins.Mongo.Sessions.Test.User=<UserForTest> + Plugins.Mongo.Sessions.Test.Password=<PasswordForTest> + +Then you will be able to use these names as the 1st parameter (ConnString) in keys instead of URIs, e.g: + + mongodb.ping[Prod] + mongodb.ping[Test] + +*Note*: sessions names are case-sensitive. + +## Supported keys +**mongodb.collection.stats[\<commonParams\>[,database],collection]** — Returns a variety of storage statistics for a +given collection. +*Parameters:* +database — database name (default: admin). +collection (required) — collection name. + +**mongodb.cfg.discovery[\<commonParams\>]** — Returns a list of discovered config servers. + +**mongodb.collections.discovery[\<commonParams\>]** — Returns a list of discovered collections. + +**mongodb.collections.usage[\<commonParams\>]** — Returns usage statistics for collections. + +**mongodb.connpool.stats[\<commonParams\>]** — Returns information regarding the open outgoing connections from the +current database instance to other members of the sharded cluster or replica set. + +**mongodb.db.stats[\<commonParams\>[,database]]** — Returns statistics reflecting a given database system’s state. +*Parameters:* +database — database name (default: admin). + +**mongodb.db.discovery[\<commonParams\>]** — Returns a list of discovered databases. + +**mongodb.jumbo_chunks.count[\<commonParams\>]** — Returns count of jumbo chunks. + +**mongodb.oplog.stats[\<commonParams\>]** — Returns a status of the replica set, using data polled from the oplog. + +**mongodb.ping[\<commonParams\>]** — Tests if a connection is alive or not. +*Returns:* +- "1" if a connection is alive. +- "0" if a connection is broken (if there is any error presented including AUTH and configuration issues). + +**mongodb.rs.config[\<commonParams\>]** — Returns a current configuration of the replica set. + +**mongodb.rs.status[\<commonParams\>]** — Returns a replica set status from the point of view of the member +where the method is run. + +**mongodb.server.status[\<commonParams\>]** — Returns a database’s state. + +**mongodb.sh.discovery[\<commonParams\>]** — Returns a list of discovered shards present in the cluster. + +## Troubleshooting +The plugin uses Zabbix agent's logs. You can increase debugging level of Zabbix Agent if you need more details about +what is happening. +Set the DebugLevel configuration option to "5" (extended debugging) in order to turn on verbose log messages for the MGO package. diff --git a/src/go/plugins/mongodb/config.go b/src/go/plugins/mongodb/config.go new file mode 100644 index 00000000000..55a3b9c901c --- /dev/null +++ b/src/go/plugins/mongodb/config.go @@ -0,0 +1,57 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "zabbix.com/pkg/conf" + "zabbix.com/pkg/plugin" +) + +type PluginOptions struct { + // Timeout is the amount of time to wait for a server to respond when + // first connecting and on follow up operations in the session. + Timeout int `conf:"optional,range=1:30"` + + // KeepAlive is a time to wait before unused connections will be closed. + KeepAlive int `conf:"optional,range=60:900,default=300"` + + // Sessions stores pre-defined named sets of connections settings. + Sessions map[string]conf.Session `conf:"optional"` +} + +// Configure implements the Configurator interface. +// Initializes configuration structures. +func (p *Plugin) Configure(global *plugin.GlobalOptions, options interface{}) { + if err := conf.Unmarshal(options, &p.options); err != nil { + p.Errf("cannot unmarshal configuration options: %s", err) + } + + if p.options.Timeout == 0 { + p.options.Timeout = global.Timeout + } +} + +// Validate implements the Configurator interface. +// Returns an error if validation of a plugin's configuration is failed. +func (p *Plugin) Validate(options interface{}) error { + var opts PluginOptions + + return conf.Unmarshal(options, &opts) +} diff --git a/src/go/plugins/mongodb/conn.go b/src/go/plugins/mongodb/conn.go new file mode 100644 index 00000000000..882aae1a63f --- /dev/null +++ b/src/go/plugins/mongodb/conn.go @@ -0,0 +1,293 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "context" + "sync" + "time" + + "gopkg.in/mgo.v2/bson" + + "gopkg.in/mgo.v2" + "zabbix.com/pkg/log" + "zabbix.com/pkg/uri" +) + +type MongoConn struct { + addr string + timeout time.Duration + lastTimeAccess time.Time + session *mgo.Session +} + +// DB shadows *mgo.DB to returns a Database interface instead of *mgo.Database. +func (conn *MongoConn) DB(name string) Database { + conn.checkConnection() + + return &MongoDatabase{Database: conn.session.DB(name)} +} + +func (conn *MongoConn) DatabaseNames() (names []string, err error) { + conn.checkConnection() + + return conn.session.DatabaseNames() +} + +func (conn *MongoConn) Ping() error { + return conn.session.DB("admin").Run(&bson.D{ + bson.DocElem{ + Name: "ping", + Value: 1, + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: conn.GetMaxTimeMS(), + }, + }, nil) +} + +func (conn *MongoConn) GetMaxTimeMS() int64 { + return conn.timeout.Milliseconds() +} + +// updateAccessTime updates the last time a connection was accessed. +func (conn *MongoConn) updateAccessTime() { + conn.lastTimeAccess = time.Now() +} + +// checkConnection implements db reconnection. +func (conn *MongoConn) checkConnection() { + if err := conn.Ping(); err != nil { + conn.session.Refresh() + log.Debugf("[%s] Attempt to reconnect: %s", pluginName, conn.addr) + } +} + +// Session is an interface to access to the session struct. +type Session interface { + DB(name string) Database + DatabaseNames() (names []string, err error) + GetMaxTimeMS() int64 + Ping() error +} + +// Database is an interface to access to the database struct. +type Database interface { + C(name string) Collection + CollectionNames() (names []string, err error) + Run(cmd, result interface{}) error +} + +// MongoDatabase wraps a mgo.Database to embed methods in models. +type MongoDatabase struct { + *mgo.Database +} + +// C shadows *mgo.DB to returns a Database interface instead of *mgo.Database. +func (d *MongoDatabase) C(name string) Collection { + return &MongoCollection{Collection: d.Database.C(name)} +} + +func (d *MongoDatabase) CollectionNames() (names []string, err error) { + return d.Database.CollectionNames() +} + +// Run shadows *mgo.DB to returns a Database interface instead of *mgo.Database. +func (d *MongoDatabase) Run(cmd, result interface{}) error { + return d.Database.Run(cmd, result) +} + +// MongoCollection wraps a mgo.Collection to embed methods in models. +type MongoCollection struct { + *mgo.Collection +} + +// Collection is an interface to access to the collection struct. +type Collection interface { + Find(query interface{}) Query +} + +// Find shadows *mgo.Collection to returns a Query interface instead of *mgo.Query. +func (c *MongoCollection) Find(query interface{}) Query { + return &MongoQuery{Query: c.Collection.Find(query)} +} + +// Query is an interface to access to the query struct +type Query interface { + All(result interface{}) error + Count() (n int, err error) + Limit(n int) Query + One(result interface{}) error + SetMaxTime(d time.Duration) Query + Sort(fields ...string) Query +} + +// MongoQuery wraps a mgo.Query to embed methods in models. +type MongoQuery struct { + *mgo.Query +} + +func (q *MongoQuery) Limit(n int) Query { + q.Query.Limit(n) + return q +} + +func (q *MongoQuery) SetMaxTime(d time.Duration) Query { + q.Query.SetMaxTime(d) + return q +} + +func (q *MongoQuery) Sort(fields ...string) Query { + q.Query.Sort(fields...) + return q +} + +// ConnManager is thread-safe structure for manage connections. +type ConnManager struct { + sync.Mutex + connMutex sync.Mutex + connections map[uri.URI]*MongoConn + keepAlive time.Duration + timeout time.Duration + Destroy context.CancelFunc +} + +// NewConnManager initializes connManager structure and runs Go Routine that watches for unused connections. +func NewConnManager(keepAlive, timeout, hkInterval time.Duration) *ConnManager { + ctx, cancel := context.WithCancel(context.Background()) + + connMgr := &ConnManager{ + connections: make(map[uri.URI]*MongoConn), + keepAlive: keepAlive, + timeout: timeout, + Destroy: cancel, // Destroy stops originated goroutines and close connections. + } + + go connMgr.housekeeper(ctx, hkInterval) + + return connMgr +} + +// closeUnused closes each connection that has not been accessed at least within the keepalive interval. +func (c *ConnManager) closeUnused() { + c.connMutex.Lock() + defer c.connMutex.Unlock() + + for uri, conn := range c.connections { + if time.Since(conn.lastTimeAccess) > c.keepAlive { + conn.session.Close() + delete(c.connections, uri) + log.Debugf("[%s] Closed unused connection: %s", pluginName, uri.Addr()) + } + } +} + +// closeAll closes all existed connections. +func (c *ConnManager) closeAll() { + c.connMutex.Lock() + for uri, conn := range c.connections { + conn.session.Close() + delete(c.connections, uri) + } + c.connMutex.Unlock() +} + +// housekeeper repeatedly checks for unused connections and close them. +func (c *ConnManager) housekeeper(ctx context.Context, interval time.Duration) { + ticker := time.NewTicker(interval) + + for { + select { + case <-ctx.Done(): + ticker.Stop() + c.closeAll() + + return + case <-ticker.C: + c.closeUnused() + } + } +} + +// create creates a new connection with given credentials. +func (c *ConnManager) create(uri uri.URI) (*MongoConn, error) { + c.connMutex.Lock() + defer c.connMutex.Unlock() + + if _, ok := c.connections[uri]; ok { + // Should never happen. + panic("connection already exists") + } + + session, err := mgo.DialWithInfo(&mgo.DialInfo{ + Addrs: []string{uri.Addr()}, + Direct: true, + FailFast: false, + Password: uri.Password(), + PoolLimit: 1, + Timeout: c.timeout, + Username: uri.User(), + }) + if err != nil { + return nil, err + } + + // Read from one of the nearest members, irrespective of it being primary or secondary. + session.SetMode(mgo.Nearest, true) + + c.connections[uri] = &MongoConn{ + addr: uri.Addr(), + timeout: c.timeout, + lastTimeAccess: time.Now(), + session: session, + } + + log.Debugf("[%s] Created new connection: %s", pluginName, uri.Addr()) + + return c.connections[uri], nil +} + +// get returns a connection with given uri if it exists and also updates lastTimeAccess, otherwise returns nil. +func (c *ConnManager) get(uri uri.URI) *MongoConn { + c.connMutex.Lock() + defer c.connMutex.Unlock() + + if conn, ok := c.connections[uri]; ok { + conn.updateAccessTime() + return conn + } + + return nil +} + +// GetConnection returns an existing connection or creates a new one. +func (c *ConnManager) GetConnection(uri uri.URI) (conn *MongoConn, err error) { + c.Lock() + defer c.Unlock() + + conn = c.get(uri) + + if conn == nil { + conn, err = c.create(uri) + } + + return +} diff --git a/src/go/plugins/mongodb/handler_collection_stats.go b/src/go/plugins/mongodb/handler_collection_stats.go new file mode 100644 index 00000000000..1c27dfc7b41 --- /dev/null +++ b/src/go/plugins/mongodb/handler_collection_stats.go @@ -0,0 +1,54 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +// collectionStatsHandler +// https://docs.mongodb.com/manual/reference/command/collStats/index.html +func collectionStatsHandler(s Session, params map[string]string) (interface{}, error) { + colStats := &bson.M{} + err := s.DB(params["Database"]).Run(&bson.D{ + bson.DocElem{ + Name: "collStats", + Value: params["Collection"], + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: s.GetMaxTimeMS(), + }, + }, colStats) + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + jsonRes, err := json.Marshal(colStats) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_collection_stats_test.go b/src/go/plugins/mongodb/handler_collection_stats_test.go new file mode 100644 index 00000000000..5aeaca5ac3f --- /dev/null +++ b/src/go/plugins/mongodb/handler_collection_stats_test.go @@ -0,0 +1,84 @@ +package mongodb + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "reflect" + "strings" + "testing" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +func Test_collectionStatsHandler(t *testing.T) { + var testData map[string]interface{} + + jsonData, err := ioutil.ReadFile("testdata/collStats.json") + if err != nil { + log.Fatal(err) + } + + err = json.Unmarshal(jsonData, &testData) + if err != nil { + log.Fatal(err) + } + + mockSession := NewMockConn() + db := mockSession.DB("MyDatabase") + db.(*MockMongoDatabase).RunFunc = func(dbName, cmd string) ([]byte, error) { + if cmd == "collStats" { + return bson.Marshal(testData) + } + + return nil, errors.New("no such cmd: " + cmd) + } + + type args struct { + s Session + params map[string]string + } + + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must parse an output of \" + collStats + \"command", + args: args{ + s: mockSession, + params: map[string]string{"Database": "MyDatabase", "Collection": "MyCollection"}, + }, + want: strings.TrimSpace(string(jsonData)), + wantErr: nil, + }, + { + name: "Must catch DB.Run() error", + args: args{ + s: mockSession, + params: map[string]string{"Database": mustFail}, + }, + want: nil, + wantErr: zbxerr.ErrorCannotFetchData, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := collectionStatsHandler(tt.args.s, tt.args.params) + + if !errors.Is(err, tt.wantErr) { + t.Errorf("collectionStatsHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("collectionStatsHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_collections_discovery.go b/src/go/plugins/mongodb/handler_collections_discovery.go new file mode 100644 index 00000000000..5d8f886da91 --- /dev/null +++ b/src/go/plugins/mongodb/handler_collections_discovery.go @@ -0,0 +1,69 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + "sort" + + "zabbix.com/pkg/zbxerr" +) + +type colEntity struct { + ColName string `json:"{#COLLECTION}"` + DbName string `json:"{#DBNAME}"` +} + +// collectionsDiscoveryHandler +// https://docs.mongodb.com/manual/reference/command/listDatabases/ +func collectionsDiscoveryHandler(s Session, _ map[string]string) (interface{}, error) { + dbs, err := s.DatabaseNames() + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + sort.Strings(dbs) + + lld := make([]colEntity, 0) + + for _, db := range dbs { + collections, err := s.DB(db).CollectionNames() + + sort.Strings(collections) + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + for _, col := range collections { + lld = append(lld, colEntity{ + ColName: col, + DbName: db, + }) + } + } + + jsonLLD, err := json.Marshal(lld) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonLLD), nil +} diff --git a/src/go/plugins/mongodb/handler_collections_discovery_test.go b/src/go/plugins/mongodb/handler_collections_discovery_test.go new file mode 100644 index 00000000000..029451ccea5 --- /dev/null +++ b/src/go/plugins/mongodb/handler_collections_discovery_test.go @@ -0,0 +1,78 @@ +package mongodb + +import ( + "errors" + "reflect" + "testing" + + "zabbix.com/pkg/zbxerr" +) + +func Test_collectionsDiscoveryHandler(t *testing.T) { + type args struct { + s Session + dbs map[string][]string + } + + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must return a list of collections", + args: args{ + s: NewMockConn(), + dbs: map[string][]string{ + "testdb": {"col1", "col2"}, + "local": {"startup_log"}, + "config": {"system.sessions"}, + }, + }, + want: "[{\"{#COLLECTION}\":\"system.sessions\",\"{#DBNAME}\":\"config\"},{\"{#COLLECTION}\":" + + "\"startup_log\",\"{#DBNAME}\":\"local\"},{\"{#COLLECTION}\":\"col1\",\"{#DBNAME}\":\"testdb\"}," + + "{\"{#COLLECTION}\":\"col2\",\"{#DBNAME}\":\"testdb\"}]", + wantErr: nil, + }, + { + name: "Must catch DB.DatabaseNames() error", + args: args{ + s: NewMockConn(), + dbs: map[string][]string{mustFail: {}}, + }, + want: nil, + wantErr: zbxerr.ErrorCannotFetchData, + }, + { + name: "Must catch DB.CollectionNames() error", + args: args{ + s: NewMockConn(), + dbs: map[string][]string{"MyDatabase": {mustFail}}, + }, + want: nil, + wantErr: zbxerr.ErrorCannotFetchData, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for db, cc := range tt.args.dbs { + tt.args.s.DB(db) + for _, c := range cc { + tt.args.s.DB(db).C(c) + } + } + + got, err := collectionsDiscoveryHandler(tt.args.s, nil) + if !errors.Is(err, tt.wantErr) { + t.Errorf("collectionsDiscoveryHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("collectionsDiscoveryHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_collections_usage.go b/src/go/plugins/mongodb/handler_collections_usage.go new file mode 100644 index 00000000000..1ef260472cd --- /dev/null +++ b/src/go/plugins/mongodb/handler_collections_usage.go @@ -0,0 +1,55 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +// collectionsUsageHandler +// https://docs.mongodb.com/manual/reference/command/top/index.html +func collectionsUsageHandler(s Session, _ map[string]string) (interface{}, error) { + colUsage := &bson.M{} + + err := s.DB("admin").Run(&bson.D{ + bson.DocElem{ + Name: "top", + Value: 1, + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: s.GetMaxTimeMS(), + }, + }, colUsage) + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + jsonRes, err := json.Marshal(colUsage) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_collections_usage_test.go b/src/go/plugins/mongodb/handler_collections_usage_test.go new file mode 100644 index 00000000000..478141c6a5e --- /dev/null +++ b/src/go/plugins/mongodb/handler_collections_usage_test.go @@ -0,0 +1,70 @@ +package mongodb + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "reflect" + "strings" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +func Test_collectionsUsageHandler(t *testing.T) { + var testData map[string]interface{} + + jsonData, err := ioutil.ReadFile("testdata/top.json") + if err != nil { + log.Fatal(err) + } + + err = json.Unmarshal(jsonData, &testData) + if err != nil { + log.Fatal(err) + } + + mockSession := NewMockConn() + db := mockSession.DB("admin") + db.(*MockMongoDatabase).RunFunc = func(dbName, cmd string) ([]byte, error) { + if cmd == "top" { + return bson.Marshal(testData) + } + + return nil, errors.New("no such cmd: " + cmd) + } + + type args struct { + s Session + } + + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must parse an output of \" + top + \"command", + args: args{ + s: mockSession, + }, + want: strings.TrimSpace(string(jsonData)), + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := collectionsUsageHandler(tt.args.s, nil) + if !errors.Is(err, tt.wantErr) { + t.Errorf("collectionsUsageHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("collectionsUsageHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_config_discovery.go b/src/go/plugins/mongodb/handler_config_discovery.go new file mode 100644 index 00000000000..9d96bd99e82 --- /dev/null +++ b/src/go/plugins/mongodb/handler_config_discovery.go @@ -0,0 +1,96 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + "fmt" + "net" + "strings" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +type lldCfgEntity struct { + ReplicaSet string `json:"{#REPLICASET}"` + Hostname string `json:"{#HOSTNAME}"` + MongodURI string `json:"{#MONGOD_URI}"` +} + +type shardMap struct { + Map map[string]string +} + +// configDiscoveryHandler +// https://docs.mongodb.com/manual/reference/command/getShardMap/#dbcmd.getShardMap +func configDiscoveryHandler(s Session, params map[string]string) (interface{}, error) { + var cfgServers shardMap + err := s.DB("admin").Run(&bson.D{ + bson.DocElem{ + Name: "getShardMap", + Value: 1, + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: s.GetMaxTimeMS(), + }, + }, &cfgServers) + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + lld := make([]lldCfgEntity, 0) + + if servers, ok := cfgServers.Map["config"]; ok { + var rs string + + hosts := servers + + h := strings.SplitN(hosts, "/", 2) + if len(h) > 1 { + rs = h[0] + hosts = h[1] + } + + for _, hostport := range strings.Split(hosts, ",") { + host, _, err := net.SplitHostPort(hostport) + if err != nil { + return nil, zbxerr.ErrorCannotParseResult.Wrap(err) + } + + lld = append(lld, lldCfgEntity{ + Hostname: host, + MongodURI: fmt.Sprintf("%s://%s", uriDefaults.Scheme, hostport), + ReplicaSet: rs, + }) + } + } else { + return nil, zbxerr.ErrorCannotParseResult + } + + jsonRes, err := json.Marshal(lld) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_connPool_stats.go b/src/go/plugins/mongodb/handler_connPool_stats.go new file mode 100644 index 00000000000..a4e94142860 --- /dev/null +++ b/src/go/plugins/mongodb/handler_connPool_stats.go @@ -0,0 +1,54 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +// connPoolStatsHandler +//https://docs.mongodb.com/manual/reference/command/connPoolStats/#dbcmd.connPoolStats +func connPoolStatsHandler(s Session, params map[string]string) (interface{}, error) { + connPoolStats := &bson.M{} + err := s.DB(params["Database"]).Run(&bson.D{ + bson.DocElem{ + Name: "connPoolStats", + Value: 1, + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: s.GetMaxTimeMS(), + }, + }, connPoolStats) + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + jsonRes, err := json.Marshal(connPoolStats) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_connPool_stats_test.go b/src/go/plugins/mongodb/handler_connPool_stats_test.go new file mode 100644 index 00000000000..0b819e040de --- /dev/null +++ b/src/go/plugins/mongodb/handler_connPool_stats_test.go @@ -0,0 +1,90 @@ +package mongodb + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "reflect" + "strings" + "testing" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +func Test_connPoolStatsHandler(t *testing.T) { + var testData map[string]interface{} + + jsonData, err := ioutil.ReadFile("testdata/connPoolStats.json") + if err != nil { + log.Fatal(err) + } + + err = json.Unmarshal(jsonData, &testData) + if err != nil { + log.Fatal(err) + } + + mockSession := NewMockConn() + db := mockSession.DB("testdb") + db.(*MockMongoDatabase).RunFunc = func(dbName, cmd string) ([]byte, error) { + if cmd == "connPoolStats" { + return bson.Marshal(testData) + } + + return nil, errors.New("no such cmd: " + cmd) + } + + type args struct { + s Session + params map[string]string + } + + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must parse an output of \" + connPoolStats + \"command", + args: args{ + s: mockSession, + params: map[string]string{"Database": "testdb"}, + }, + want: strings.TrimSpace(string(jsonData)), + wantErr: nil, + }, + { + name: "Must not fail on unknown db", + args: args{ + s: mockSession, + params: map[string]string{"Database": "not_exists"}, + }, + want: "{\"ok\":1}", + wantErr: nil, + }, + { + name: "Must catch DB.Run() error", + args: args{ + s: mockSession, + params: map[string]string{"Database": mustFail}, + }, + want: nil, + wantErr: zbxerr.ErrorCannotFetchData, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := connPoolStatsHandler(tt.args.s, tt.args.params) + if !errors.Is(err, tt.wantErr) { + t.Errorf("connPoolStatsHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("connPoolStatsHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_database_stats.go b/src/go/plugins/mongodb/handler_database_stats.go new file mode 100644 index 00000000000..30861f95b2b --- /dev/null +++ b/src/go/plugins/mongodb/handler_database_stats.go @@ -0,0 +1,54 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +// databaseStatsHandler +// https://docs.mongodb.com/manual/reference/command/dbStats/index.html +func databaseStatsHandler(s Session, params map[string]string) (interface{}, error) { + dbStats := &bson.M{} + err := s.DB(params["Database"]).Run(&bson.D{ + bson.DocElem{ + Name: "dbStats", + Value: 1, + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: s.GetMaxTimeMS(), + }, + }, dbStats) + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + jsonRes, err := json.Marshal(dbStats) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_database_stats_test.go b/src/go/plugins/mongodb/handler_database_stats_test.go new file mode 100644 index 00000000000..38d752bd35d --- /dev/null +++ b/src/go/plugins/mongodb/handler_database_stats_test.go @@ -0,0 +1,91 @@ +package mongodb + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "reflect" + "strings" + "testing" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +func Test_databaseStatsHandler(t *testing.T) { + var testData map[string]interface{} + + jsonData, err := ioutil.ReadFile("testdata/dbStats.json") + if err != nil { + log.Fatal(err) + } + + err = json.Unmarshal(jsonData, &testData) + if err != nil { + log.Fatal(err) + } + + mockSession := NewMockConn() + db := mockSession.DB("testdb") + db.(*MockMongoDatabase).RunFunc = func(dbName, cmd string) ([]byte, error) { + if cmd == "dbStats" { + return bson.Marshal(testData) + } + + return nil, errors.New("no such cmd: " + cmd) + } + + type args struct { + s Session + params map[string]string + } + + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must parse an output of \" + dbStats + \"command", + args: args{ + s: mockSession, + params: map[string]string{"Database": "testdb"}, + }, + want: strings.TrimSpace(string(jsonData)), + wantErr: nil, + }, + { + name: "Must not fail on unknown db", + args: args{ + s: mockSession, + params: map[string]string{"Database": "not_exists"}, + }, + want: "{\"ok\":1}", + wantErr: nil, + }, + { + name: "Must catch DB.Run() error", + args: args{ + s: mockSession, + params: map[string]string{"Database": mustFail}, + }, + want: nil, + wantErr: zbxerr.ErrorCannotFetchData, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := databaseStatsHandler(tt.args.s, tt.args.params) + if !errors.Is(err, tt.wantErr) { + t.Errorf("databaseStatsHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("databaseStatsHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_databases_discovery.go b/src/go/plugins/mongodb/handler_databases_discovery.go new file mode 100644 index 00000000000..9b3c79261a4 --- /dev/null +++ b/src/go/plugins/mongodb/handler_databases_discovery.go @@ -0,0 +1,55 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + "sort" + + "zabbix.com/pkg/zbxerr" +) + +type dbEntity struct { + DBName string `json:"{#DBNAME}"` +} + +// databasesDiscoveryHandler +// https://docs.mongodb.com/manual/reference/command/listDatabases/ +func databasesDiscoveryHandler(s Session, _ map[string]string) (interface{}, error) { + dbs, err := s.DatabaseNames() + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + lld := make([]dbEntity, 0) + + sort.Strings(dbs) + + for _, db := range dbs { + lld = append(lld, dbEntity{DBName: db}) + } + + jsonLLD, err := json.Marshal(lld) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonLLD), nil +} diff --git a/src/go/plugins/mongodb/handler_databases_discovery_test.go b/src/go/plugins/mongodb/handler_databases_discovery_test.go new file mode 100644 index 00000000000..5eed8622bf8 --- /dev/null +++ b/src/go/plugins/mongodb/handler_databases_discovery_test.go @@ -0,0 +1,60 @@ +package mongodb + +import ( + "errors" + "reflect" + "testing" + + "zabbix.com/pkg/zbxerr" +) + +func Test_databasesDiscoveryHandler(t *testing.T) { + type args struct { + s Session + dbs []string + } + + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must return a list of databases", + args: args{ + s: NewMockConn(), + dbs: []string{"testdb", "local", "config"}, + }, + want: "[{\"{#DBNAME}\":\"config\"},{\"{#DBNAME}\":\"local\"},{\"{#DBNAME}\":\"testdb\"}]", + wantErr: nil, + }, + { + name: "Must catch DB.DatabaseNames() error", + args: args{ + s: NewMockConn(), + dbs: []string{mustFail}, + }, + want: nil, + wantErr: zbxerr.ErrorCannotFetchData, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, db := range tt.args.dbs { + tt.args.s.DB(db) + } + + got, err := databasesDiscoveryHandler(tt.args.s, nil) + if !errors.Is(err, tt.wantErr) { + t.Errorf("databasesDiscoveryHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("databasesDiscoveryHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_jumbo_chunks.go b/src/go/plugins/mongodb/handler_jumbo_chunks.go new file mode 100644 index 00000000000..a92a93505c2 --- /dev/null +++ b/src/go/plugins/mongodb/handler_jumbo_chunks.go @@ -0,0 +1,36 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +// jumboChunksHandler +// https://docs.mongodb.com/manual/core/sharding-data-partitioning/#indivisible-jumbo-chunks +func jumboChunksHandler(s Session, _ map[string]string) (interface{}, error) { + jumboChunks, err := s.DB("config").C("chunks").Find(bson.M{"jumbo": true}).Count() + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + return jumboChunks, nil +} diff --git a/src/go/plugins/mongodb/handler_oplog_stats.go b/src/go/plugins/mongodb/handler_oplog_stats.go new file mode 100644 index 00000000000..6da5cfe0d95 --- /dev/null +++ b/src/go/plugins/mongodb/handler_oplog_stats.go @@ -0,0 +1,92 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + "time" + + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +type oplogStats struct { + TimeDiff int `json:"timediff"` // in seconds +} + +type oplogEntry struct { + Timestamp bson.MongoTimestamp `bson:"ts"` +} + +const ( + oplogReplicaSet = "oplog.rs" // the capped collection that holds the oplog for Replica Set Members + oplogMasterSlave = "oplog.$main" // oplog for the master-slave configuration +) + +const ( + sortAsc = "$natural" + sortDesc = "-$natural" +) + +var oplogQuery = bson.M{"ts": bson.M{"$exists": true}} + +// oplogStatsHandler +// https://docs.mongodb.com/manual/reference/method/db.getReplicationInfo/index.html +func oplogStatsHandler(s Session, _ map[string]string) (interface{}, error) { + var ( + stats oplogStats + opFirst, opLast oplogEntry + ) + + localDb := s.DB("local") + + for _, collection := range []string{oplogReplicaSet, oplogMasterSlave} { + if err := localDb.C(collection).Find(oplogQuery). + Sort(sortAsc).Limit(1). + SetMaxTime(time.Duration(s.GetMaxTimeMS()) * time.Millisecond). + One(&opFirst); err != nil { + if err == mgo.ErrNotFound { + continue + } + + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + if err := localDb.C(collection).Find(oplogQuery).Sort(sortDesc).Limit(1).One(&opLast); err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + break + } + + // BSON has a special timestamp type for internal MongoDB use and is not associated with the regular Date type. + // This internal timestamp type is a 64 bit value where: + // the most significant 32 bits are a time_t value (seconds since the Unix epoch) + // the least significant 32 bits are an incrementing ordinal for operations within a given second. + stats.TimeDiff = int(opLast.Timestamp>>32 - opFirst.Timestamp>>32) + + jsonRes, err := json.Marshal(stats) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_oplog_stats_test.go b/src/go/plugins/mongodb/handler_oplog_stats_test.go new file mode 100644 index 00000000000..07c3a586983 --- /dev/null +++ b/src/go/plugins/mongodb/handler_oplog_stats_test.go @@ -0,0 +1,95 @@ +package mongodb + +import ( + "errors" + "reflect" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +func Test_oplogStatsHandler(t *testing.T) { + var ( + opFirst = map[string]int64{"ts": 6908630134576644097} + opLast = map[string]int64{"ts": 6925804549152178177} + ) + + mockSession := NewMockConn() + localDb := mockSession.DB("local") + + dataFunc := func(_ string, _ interface{}, sortFields ...string) ([]byte, error) { + if len(sortFields) == 0 { + panic("sortFields must be set") + } + + switch sortFields[0] { + case sortAsc: + return bson.Marshal(opFirst) + + case sortDesc: + return bson.Marshal(opLast) + + default: + panic("unknown sort type") + } + } + + type args struct { + s Session + collections []string + } + + // WARN: tests order is significant + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must return 0 if neither oplog.rs nor oplog.$main collection found", + args: args{ + s: mockSession, + collections: []string{}, + }, + want: "{\"timediff\":0}", + wantErr: nil, + }, + { + name: "Must calculate timediff from oplog.$main collection", + args: args{ + s: mockSession, + collections: []string{oplogMasterSlave}, + }, + want: "{\"timediff\":3998730}", + wantErr: nil, + }, + { + name: "Must calculate timediff from oplog.rs collection", + args: args{ + s: mockSession, + collections: []string{oplogReplicaSet}, + }, + want: "{\"timediff\":3998730}", + wantErr: nil, + }, + } + + for _, tt := range tests { + for _, col := range tt.args.collections { + localDb.C(col).Find(oplogQuery).(*MockMongoQuery).DataFunc = dataFunc + } + + t.Run(tt.name, func(t *testing.T) { + got, err := oplogStatsHandler(tt.args.s, nil) + if !errors.Is(err, tt.wantErr) { + t.Errorf("oplogStatsHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("oplogStatsHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_ping.go b/src/go/plugins/mongodb/handler_ping.go new file mode 100644 index 00000000000..2aceabceb64 --- /dev/null +++ b/src/go/plugins/mongodb/handler_ping.go @@ -0,0 +1,35 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +const ( + pingFailed = 0 + pingOk = 1 +) + +// pingHandler executes 'ping' command and returns pingOk if a connection is alive or pingFailed otherwise. +// https://docs.mongodb.com/manual/reference/command/ping/index.html +func pingHandler(s Session, _ map[string]string) (interface{}, error) { + if err := s.Ping(); err != nil { + return pingFailed, nil + } + + return pingOk, nil +} diff --git a/src/go/plugins/mongodb/handler_replset_config.go b/src/go/plugins/mongodb/handler_replset_config.go new file mode 100644 index 00000000000..17130ebc6b4 --- /dev/null +++ b/src/go/plugins/mongodb/handler_replset_config.go @@ -0,0 +1,58 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +// replSetConfigHandler +// https://docs.mongodb.com/manual/reference/command/replSetGetConfig/index.html +func replSetConfigHandler(s Session, _ map[string]string) (interface{}, error) { + replSetGetConfig := &bson.M{} + err := s.DB("admin").Run(&bson.D{ + bson.DocElem{ + Name: "replSetGetConfig", + Value: 1, + }, + bson.DocElem{ + Name: "commitmentStatus", + Value: true, + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: s.GetMaxTimeMS(), + }, + }, replSetGetConfig) + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + jsonRes, err := json.Marshal(replSetGetConfig) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_replset_config_test.go b/src/go/plugins/mongodb/handler_replset_config_test.go new file mode 100644 index 00000000000..fb9926f8b76 --- /dev/null +++ b/src/go/plugins/mongodb/handler_replset_config_test.go @@ -0,0 +1,71 @@ +package mongodb + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "reflect" + "strings" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +func Test_replSetConfigHandler(t *testing.T) { + var testData map[string]interface{} + + jsonData, err := ioutil.ReadFile("testdata/replSetGetConfig.json") + if err != nil { + log.Fatal(err) + } + + err = json.Unmarshal(jsonData, &testData) + if err != nil { + log.Fatal(err) + } + + mockSession := NewMockConn() + db := mockSession.DB("admin") + db.(*MockMongoDatabase).RunFunc = func(dbName, cmd string) ([]byte, error) { + if cmd == "replSetGetConfig" { + return bson.Marshal(testData) + } + + return nil, errors.New("no such cmd: " + cmd) + } + + type args struct { + s Session + } + + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must parse an output of \" + replSetGetConfig + \"command", + args: args{ + s: mockSession, + }, + want: strings.TrimSpace(string(jsonData)), + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := replSetConfigHandler(tt.args.s, nil) + if !errors.Is(err, tt.wantErr) { + t.Errorf("replSetConfigHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("replSetConfigHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_replset_status.go b/src/go/plugins/mongodb/handler_replset_status.go new file mode 100644 index 00000000000..ef0a7135dfe --- /dev/null +++ b/src/go/plugins/mongodb/handler_replset_status.go @@ -0,0 +1,162 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + "errors" + "strings" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +const ( + statePrimary = 1 + stateSecondary = 2 +) + +const nodeHealthy = 1 + +type Member struct { + health int + name string + optime int + ptr interface{} + state int +} + +type rawMember = map[string]interface{} + +var errUnknownStructure = errors.New("failed to parse the members structure") + +func parseMembers(raw []interface{}) (result []Member, err error) { + var ( + members []Member + primaryNode Member + ) + + for _, m := range raw { + member := Member{} + ok := true + + if v, ok := m.(rawMember)["name"].(string); ok { + member.name = v + } + + if v, ok := m.(rawMember)["health"].(float64); ok { + member.health = int(v) + } + + if v, ok := m.(rawMember)["optime"].(map[string]interface{}); ok { + if ts, ok := v["ts"].(bson.MongoTimestamp); ok { + member.optime = int(ts >> 32) + } else { + member.optime = int(int64(v["ts"].(float64)) >> 32) + } + } + + if v, ok := m.(rawMember)["state"].(int); ok { + member.state = v + } + + if !ok { + return nil, errUnknownStructure + } + + member.ptr = m + + if member.state == statePrimary { + primaryNode = member + } else { + members = append(members, member) + } + } + + result = append([]Member{primaryNode}, members...) + if len(result) == 0 { + return nil, errUnknownStructure + } + + return result, nil +} + +func injectExtendedMembersStats(raw []interface{}) error { + members, err := parseMembers(raw) + if err != nil { + return err + } + + unhealthyNodes := []string{} + unhealthyCount := 0 + primary := members[0] + + for _, node := range members { + node.ptr.(rawMember)["lag"] = primary.optime - node.optime + + if node.state == stateSecondary && node.health != nodeHealthy { + unhealthyNodes = append(unhealthyNodes, node.name) + unhealthyCount++ + } + } + + primary.ptr.(rawMember)["unhealthyNodes"] = unhealthyNodes + primary.ptr.(rawMember)["unhealthyCount"] = unhealthyCount + primary.ptr.(rawMember)["totalNodes"] = len(members) - 1 + + return nil +} + +// replSetStatusHandler +// https://docs.mongodb.com/manual/reference/command/replSetGetStatus/index.html +func replSetStatusHandler(s Session, _ map[string]string) (interface{}, error) { + var replSetGetStatus map[string]interface{} + + err := s.DB("admin").Run(&bson.D{ + bson.DocElem{ + Name: "replSetGetStatus", + Value: 1, + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: s.GetMaxTimeMS(), + }, + }, &replSetGetStatus) + + if err != nil { + if strings.Contains(err.Error(), "not running with --replSet") { + return "{}", nil + } + + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + err = injectExtendedMembersStats(replSetGetStatus["members"].([]interface{})) + if err != nil { + return nil, zbxerr.ErrorCannotParseResult.Wrap(err) + } + + jsonRes, err := json.Marshal(replSetGetStatus) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_server_status.go b/src/go/plugins/mongodb/handler_server_status.go new file mode 100644 index 00000000000..c946ed857e6 --- /dev/null +++ b/src/go/plugins/mongodb/handler_server_status.go @@ -0,0 +1,58 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +// serverStatusHandler +// https://docs.mongodb.com/manual/reference/command/serverStatus/#dbcmd.serverStatus +func serverStatusHandler(s Session, _ map[string]string) (interface{}, error) { + serverStatus := &bson.M{} + err := s.DB("admin").Run(&bson.D{ + bson.DocElem{ + Name: "serverStatus", + Value: 1, + }, + bson.DocElem{ + Name: "recordStats", + Value: 0, + }, + bson.DocElem{ + Name: "maxTimeMS", + Value: s.GetMaxTimeMS(), + }, + }, serverStatus) + + if err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + jsonRes, err := json.Marshal(serverStatus) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonRes), nil +} diff --git a/src/go/plugins/mongodb/handler_server_status_test.go b/src/go/plugins/mongodb/handler_server_status_test.go new file mode 100644 index 00000000000..601d7743c3c --- /dev/null +++ b/src/go/plugins/mongodb/handler_server_status_test.go @@ -0,0 +1,71 @@ +package mongodb + +import ( + "encoding/json" + "errors" + "io/ioutil" + "log" + "reflect" + "strings" + "testing" + + "gopkg.in/mgo.v2/bson" +) + +func Test_serverStatusHandler(t *testing.T) { + var testData map[string]interface{} + + jsonData, err := ioutil.ReadFile("testdata/serverStatus.json") + if err != nil { + log.Fatal(err) + } + + err = json.Unmarshal(jsonData, &testData) + if err != nil { + log.Fatal(err) + } + + mockSession := NewMockConn() + db := mockSession.DB("admin") + db.(*MockMongoDatabase).RunFunc = func(dbName, cmd string) ([]byte, error) { + if cmd == "serverStatus" { + return bson.Marshal(testData) + } + + return nil, errors.New("no such cmd: " + cmd) + } + + type args struct { + s Session + } + + tests := []struct { + name string + args args + want interface{} + wantErr error + }{ + { + name: "Must parse an output of \" + serverStatus + \"command", + args: args{ + s: mockSession, + }, + want: strings.TrimSpace(string(jsonData)), + wantErr: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := serverStatusHandler(tt.args.s, nil) + if !errors.Is(err, tt.wantErr) { + t.Errorf("serverStatusHandler() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("serverStatusHandler() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/src/go/plugins/mongodb/handler_shards_discovery.go b/src/go/plugins/mongodb/handler_shards_discovery.go new file mode 100644 index 00000000000..177390aeae1 --- /dev/null +++ b/src/go/plugins/mongodb/handler_shards_discovery.go @@ -0,0 +1,87 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "encoding/json" + "fmt" + "net" + "strings" + "time" + + "zabbix.com/pkg/zbxerr" +) + +type lldShEntity struct { + ID string `json:"{#ID}"` + Hostname string `json:"{#HOSTNAME}"` + MongodURI string `json:"{#MONGOD_URI}"` + State string `json:"{#STATE}"` +} + +type shEntry struct { + ID string `bson:"_id"` + Host string `bson:"host"` + State json.Number `bson:"state"` +} + +// shardsDiscoveryHandler +// https://docs.mongodb.com/manual/reference/method/sh.status/#sh.status +func shardsDiscoveryHandler(s Session, _ map[string]string) (interface{}, error) { + var shards []shEntry + + if err := s.DB("config").C("shards").Find(nil).Sort(sortAsc). + SetMaxTime(time.Duration(s.GetMaxTimeMS()) * time.Millisecond). + All(&shards); err != nil { + return nil, zbxerr.ErrorCannotFetchData.Wrap(err) + } + + lld := make([]lldShEntity, 0) + + for _, sh := range shards { + hosts := sh.Host + + h := strings.SplitN(sh.Host, "/", 2) + if len(h) > 1 { + hosts = h[1] + } + + for _, hostport := range strings.Split(hosts, ",") { + host, _, err := net.SplitHostPort(hostport) + if err != nil { + return nil, zbxerr.ErrorCannotParseResult.Wrap(err) + } + + lld = append(lld, lldShEntity{ + ID: sh.ID, + Hostname: host, + MongodURI: fmt.Sprintf("%s://%s", uriDefaults.Scheme, hostport), + State: sh.State.String(), + }) + } + } + + jsonLLD, err := json.Marshal(lld) + if err != nil { + return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) + } + + return string(jsonLLD), nil +} diff --git a/src/go/plugins/mongodb/metrics.go b/src/go/plugins/mongodb/metrics.go new file mode 100644 index 00000000000..f1cd553b989 --- /dev/null +++ b/src/go/plugins/mongodb/metrics.go @@ -0,0 +1,159 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "zabbix.com/pkg/metric" + "zabbix.com/pkg/plugin" + "zabbix.com/pkg/uri" +) + +// handlerFunc defines an interface must be implemented by handlers. +type handlerFunc func(s Session, params map[string]string) (res interface{}, err error) + +// getHandlerFunc returns a handlerFunc related to a given key. +func getHandlerFunc(key string) handlerFunc { + switch key { + case keyConfigDiscovery: + return configDiscoveryHandler + + case keyCollectionStats: + return collectionStatsHandler + + case keyCollectionsDiscovery: + return collectionsDiscoveryHandler + + case keyCollectionsUsage: + return collectionsUsageHandler + + case keyConnPoolStats: + return connPoolStatsHandler + + case keyDatabaseStats: + return databaseStatsHandler + + case keyDatabasesDiscovery: + return databasesDiscoveryHandler + + case keyJumboChunks: + return jumboChunksHandler + + case keyOplogStats: + return oplogStatsHandler + + case keyPing: + return pingHandler + + case keyReplSetConfig: + return replSetConfigHandler + + case keyReplSetStatus: + return replSetStatusHandler + + case keyServerStatus: + return serverStatusHandler + + case keyShardsDiscovery: + return shardsDiscoveryHandler + + default: + return nil + } +} + +const ( + keyConfigDiscovery = "mongodb.cfg.discovery" + keyCollectionStats = "mongodb.collection.stats" + keyCollectionsDiscovery = "mongodb.collections.discovery" + keyCollectionsUsage = "mongodb.collections.usage" + keyConnPoolStats = "mongodb.connpool.stats" + keyDatabaseStats = "mongodb.db.stats" + keyDatabasesDiscovery = "mongodb.db.discovery" + keyJumboChunks = "mongodb.jumbo_chunks.count" + keyOplogStats = "mongodb.oplog.stats" + keyPing = "mongodb.ping" + keyReplSetConfig = "mongodb.rs.config" + keyReplSetStatus = "mongodb.rs.status" + keyServerStatus = "mongodb.server.status" + keyShardsDiscovery = "mongodb.sh.discovery" +) + +var uriDefaults = &uri.Defaults{Scheme: "tcp", Port: "27017"} + +// Common params: [URI|Session][,User][,Password] +var ( + paramURI = metric.NewConnParam("URI", "URI to connect or session name."). + WithDefault(uriDefaults.Scheme + "://localhost:" + uriDefaults.Port).WithSession(). + WithValidator(uri.URIValidator{Defaults: uriDefaults, AllowedSchemes: []string{"tcp"}}) + paramUser = metric.NewConnParam("User", "MongoDB user.") + paramPassword = metric.NewConnParam("Password", "User's password.") + paramDatabase = metric.NewParam("Database", "Database name.").WithDefault("admin") + paramCollection = metric.NewParam("Collection", "Collection name.").SetRequired() +) + +var metrics = metric.MetricSet{ + keyConfigDiscovery: metric.New("Returns a list of discovered config servers.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyCollectionStats: metric.New("Returns a variety of storage statistics for a given collection.", + []*metric.Param{paramURI, paramUser, paramPassword, paramDatabase, paramCollection}, false), + + keyCollectionsDiscovery: metric.New("Returns a list of discovered collections.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyCollectionsUsage: metric.New("Returns usage statistics for collections.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyConnPoolStats: metric.New("Returns information regarding the open outgoing connections from the "+ + "current database instance to other members of the sharded cluster or replica set.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyDatabaseStats: metric.New("Returns statistics reflecting a given database system’s state.", + []*metric.Param{paramURI, paramUser, paramPassword, paramDatabase}, false), + + keyDatabasesDiscovery: metric.New("Returns a list of discovered databases.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyJumboChunks: metric.New("Returns count of jumbo chunks.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyOplogStats: metric.New("Returns a status of the replica set, using data polled from the oplog.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyPing: metric.New("Test if connection is alive or not.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyReplSetConfig: metric.New("Returns a current configuration of the replica set.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyReplSetStatus: metric.New("Returns a replica set status from the point of view of the member "+ + "where the method is run.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyServerStatus: metric.New("Returns a database’s state.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), + + keyShardsDiscovery: metric.New("Returns a list of discovered shards present in the cluster.", + []*metric.Param{paramURI, paramUser, paramPassword}, false), +} + +func init() { + plugin.RegisterMetrics(&impl, pluginName, metrics.List()...) +} diff --git a/src/go/plugins/mongodb/mockconn.go b/src/go/plugins/mongodb/mockconn.go new file mode 100644 index 00000000000..aad8053a55f --- /dev/null +++ b/src/go/plugins/mongodb/mockconn.go @@ -0,0 +1,190 @@ +package mongodb + +import ( + "errors" + "fmt" + "time" + + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" + "zabbix.com/pkg/zbxerr" +) + +const ( + mustFail = "mustFail" +) + +type MockConn struct { + dbs map[string]*MockMongoDatabase +} + +func NewMockConn() *MockConn { + return &MockConn{ + dbs: make(map[string]*MockMongoDatabase), + } +} + +func (conn *MockConn) DB(name string) Database { + if db, ok := conn.dbs[name]; ok { + return db + } + + conn.dbs[name] = &MockMongoDatabase{ + name: name, + collections: make(map[string]*MockMongoCollection), + } + + return conn.dbs[name] +} + +func (conn *MockConn) DatabaseNames() (names []string, err error) { + for _, db := range conn.dbs { + if db.name == mustFail { + return nil, zbxerr.ErrorCannotFetchData + } + + names = append(names, db.name) + } + + return +} + +func (conn *MockConn) Ping() error { + return nil +} + +func (conn *MockConn) GetMaxTimeMS() int64 { + return 3000 +} + +type MockSession interface { + DB(name string) Database + DatabaseNames() (names []string, err error) + GetMaxTimeMS() int64 + Ping() error +} + +type MockMongoDatabase struct { + name string + collections map[string]*MockMongoCollection + RunFunc func(dbName, cmd string) ([]byte, error) +} + +func (d *MockMongoDatabase) C(name string) Collection { + if col, ok := d.collections[name]; ok { + return col + } + + d.collections[name] = &MockMongoCollection{ + name: name, + queries: make(map[interface{}]*MockMongoQuery), + } + + return d.collections[name] +} + +func (d *MockMongoDatabase) CollectionNames() (names []string, err error) { + for _, col := range d.collections { + if col.name == mustFail { + return nil, errors.New("fail") + } + + names = append(names, col.name) + } + + return +} + +func (d *MockMongoDatabase) Run(cmd, result interface{}) error { + if d.RunFunc == nil { + d.RunFunc = func(dbName, _ string) ([]byte, error) { + if dbName == mustFail { + return nil, errors.New("fail") + } + + return bson.Marshal(map[string]int{"ok": 1}) + } + } + + if result == nil { + return nil + } + + bsonDcmd := *(cmd.(*bson.D)) + cmdName := bsonDcmd[0].Name + + data, err := d.RunFunc(d.name, cmdName) + if err != nil { + return err + } + + return bson.Unmarshal(data, result) +} + +type MockMongoCollection struct { + name string + queries map[interface{}]*MockMongoQuery +} + +func (c *MockMongoCollection) Find(query interface{}) Query { + queryHash := fmt.Sprintf("%v", query) + if q, ok := c.queries[queryHash]; ok { + return q + } + + c.queries[queryHash] = &MockMongoQuery{ + collection: c.name, + query: query, + } + + return c.queries[queryHash] +} + +type MockMongoQuery struct { + collection string + query interface{} + sortFields []string + DataFunc func(collection string, query interface{}, sortFields ...string) ([]byte, error) +} + +func (q *MockMongoQuery) retrieve(result interface{}) error { + if q.DataFunc == nil { + return mgo.ErrNotFound + } + + if result == nil { + return nil + } + + data, err := q.DataFunc(q.collection, q.query, q.sortFields...) + if err != nil { + return err + } + + return bson.Unmarshal(data, result) +} + +func (q *MockMongoQuery) All(result interface{}) error { + return q.retrieve(result) +} + +func (q *MockMongoQuery) Count() (n int, err error) { + return 1, nil +} + +func (q *MockMongoQuery) Limit(n int) Query { + return q +} + +func (q *MockMongoQuery) One(result interface{}) error { + return q.retrieve(result) +} + +func (q *MockMongoQuery) SetMaxTime(_ time.Duration) Query { + return q +} + +func (q *MockMongoQuery) Sort(fields ...string) Query { + q.sortFields = fields + return q +} diff --git a/src/go/plugins/mongodb/mongodb.go b/src/go/plugins/mongodb/mongodb.go new file mode 100644 index 00000000000..ad4e988c6e4 --- /dev/null +++ b/src/go/plugins/mongodb/mongodb.go @@ -0,0 +1,114 @@ +/* +** Zabbix +** Copyright (C) 2001-2021 Zabbix SIA +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation; either version 2 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**/ + +package mongodb + +import ( + "time" + + "gopkg.in/mgo.v2" + + "zabbix.com/pkg/uri" + "zabbix.com/pkg/zbxerr" + + "zabbix.com/pkg/plugin" +) + +const pluginName = "Mongo" + +const hkInterval = 10 + +// Plugin inherits plugin.Base and store plugin-specific data. +type Plugin struct { + plugin.Base + connMgr *ConnManager + options PluginOptions +} + +// impl is the pointer to the plugin implementation. +var impl Plugin + +// Export implements the Exporter interface. +func (p *Plugin) Export(key string, rawParams []string, _ plugin.ContextProvider) (result interface{}, err error) { + params, err := metrics[key].EvalParams(rawParams, p.options.Sessions) + if err != nil { + return nil, err + } + + uri, err := uri.NewWithCreds(params["URI"], params["User"], params["Password"], uriDefaults) + if err != nil { + return nil, err + } + + handleMetric := getHandlerFunc(key) + if handleMetric == nil { + return nil, zbxerr.ErrorUnsupportedMetric + } + + conn, err := p.connMgr.GetConnection(*uri) + if err != nil { + // Special logic of processing connection errors should be used if mongodb.ping is requested + // because it must return pingFailed if any error occurred. + if key == keyPing { + return pingFailed, nil + } + + p.Errf(err.Error()) + + return nil, err + } + + result, err = handleMetric(conn, params) + if err != nil { + p.Errf(err.Error()) + } + + return result, err +} + +// Start implements the Runner interface and performs initialization when plugin is activated. +func (p *Plugin) Start() { + p.connMgr = NewConnManager( + time.Duration(p.options.KeepAlive)*time.Second, + time.Duration(p.options.Timeout)*time.Second, + hkInterval*time.Second, + ) +} + +// Stop implements the Runner interface and frees resources when plugin is deactivated. +func (p *Plugin) Stop() { + p.connMgr.Destroy() + p.connMgr = nil +} + +type MongoLogger struct { + Debugf func(format string, args ...interface{}) +} + +func (l MongoLogger) Output(_ int, msg string) error { + l.Debugf(msg) + return nil +} + +func init() { + logger := MongoLogger{Debugf: impl.Tracef} + + mgo.SetDebug(true) + mgo.SetLogger(logger) +} diff --git a/src/go/plugins/mongodb/testdata/collStats.json b/src/go/plugins/mongodb/testdata/collStats.json new file mode 100644 index 00000000000..efb07b16c9a --- /dev/null +++ b/src/go/plugins/mongodb/testdata/collStats.json @@ -0,0 +1 @@ +{"capped":false,"count":0,"indexDetails":{"_id_":{"LSM":{"bloom filter false positives":0,"bloom filter hits":0,"bloom filter misses":0,"bloom filter pages evicted from cache":0,"bloom filter pages read into cache":0,"bloom filters in the LSM tree":0,"chunks in the LSM tree":0,"highest merge generation in the LSM tree":0,"queries that could have benefited from a Bloom filter that did not exist":0,"sleep for LSM checkpoint throttle":0,"sleep for LSM merge throttle":0,"total size of bloom filters":0},"block-manager":{"allocations requiring file extension":0,"blocks allocated":0,"blocks freed":0,"checkpoint size":0,"file allocation unit size":4096,"file bytes available for reuse":0,"file magic number":120897,"file major version number":1,"file size in bytes":4096,"minor version number":0},"btree":{"btree checkpoint generation":70825,"column-store fixed-size leaf pages":0,"column-store internal pages":0,"column-store variable-size RLE encoded values":0,"column-store variable-size deleted values":0,"column-store variable-size leaf pages":0,"fixed-record size":0,"maximum internal page key size":1474,"maximum internal page size":16384,"maximum leaf page key size":1474,"maximum leaf page size":16384,"maximum leaf page value size":7372,"maximum tree depth":0,"number of key/value pairs":0,"overflow pages":0,"pages rewritten by compaction":0,"row-store internal pages":0,"row-store leaf pages":0},"cache":{"bytes currently in the cache":182,"bytes dirty in the cache cumulative":0,"bytes read into cache":0,"bytes written from cache":0,"checkpoint blocked page eviction":0,"data source pages selected for eviction unable to be evicted":0,"eviction walk passes of a file":0,"eviction walk target pages histogram - 0-9":0,"eviction walk target pages histogram - 10-31":0,"eviction walk target pages histogram - 128 and higher":0,"eviction walk target pages histogram - 32-63":0,"eviction walk target pages histogram - 64-128":0,"eviction walks abandoned":0,"eviction walks gave up because they restarted their walk twice":0,"eviction walks gave up because they saw too many pages and found no candidates":0,"eviction walks gave up because they saw too many pages and found too few candidates":0,"eviction walks reached end of tree":0,"eviction walks started from root of tree":0,"eviction walks started from saved location in tree":0,"hazard pointer blocked page eviction":0,"in-memory page passed criteria to be split":0,"in-memory page splits":0,"internal pages evicted":0,"internal pages split during eviction":0,"leaf pages split during eviction":0,"modified pages evicted":0,"overflow pages read into cache":0,"page split during eviction deepened the tree":0,"page written requiring cache overflow records":0,"pages read into cache":0,"pages read into cache after truncate":0,"pages read into cache after truncate in prepare state":0,"pages read into cache requiring cache overflow entries":0,"pages requested from the cache":0,"pages seen by eviction walk":0,"pages written from cache":0,"pages written requiring in-memory restoration":0,"tracked dirty bytes in the cache":0,"unmodified pages evicted":0},"cache_walk":{"Average difference between current eviction generation when the page was last considered":0,"Average on-disk page image size seen":0,"Average time in cache for pages that have been visited by the eviction server":0,"Average time in cache for pages that have not been visited by the eviction server":0,"Clean pages currently in cache":0,"Current eviction generation":0,"Dirty pages currently in cache":0,"Entries in the root page":0,"Internal pages currently in cache":0,"Leaf pages currently in cache":0,"Maximum difference between current eviction generation when the page was last considered":0,"Maximum page size seen":0,"Minimum on-disk page image size seen":0,"Number of pages never visited by eviction server":0,"On-disk page image sizes smaller than a single allocation unit":0,"Pages created in memory and never written":0,"Pages currently queued for eviction":0,"Pages that could not be queued for eviction":0,"Refs skipped during cache traversal":0,"Size of the root page":0,"Total number of pages currently in cache":0},"compression":{"compressed pages read":0,"compressed pages written":0,"page written failed to compress":0,"page written was too small to compress":0},"creationString":"access_pattern_hint=none,allocation_size=4KB,app_metadata=(formatVersion=8,infoObj={ \"v\" : 2, \"key\" : { \"_id\" : 1 }, \"name\" : \"_id_\", \"ns\" : \"MyDatabase.MyCollection\" }),assert=(commit_timestamp=none,read_timestamp=none),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,colgroups=,collator=,columns=,dictionary=0,encryption=(keyid=,name=),exclusive=false,extractor=,format=btree,huffman_key=,huffman_value=,ignore_in_memory_cache_size=false,immutable=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=16k,key_format=u,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=16k,leaf_value_max=0,log=(enabled=false),lsm=(auto_throttle=true,bloom=true,bloom_bit_count=16,bloom_config=,bloom_hash_count=8,bloom_oldest=false,chunk_count_limit=0,chunk_max=5GB,chunk_size=10MB,merge_custom=(prefix=,start_generation=0,suffix=),merge_max=15,merge_min=0),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=true,prefix_compression_min=4,source=,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,type=file,value_format=u","cursor":{"bulk-loaded cursor-insert calls":0,"close calls that result in cache":0,"create calls":0,"cursor operation restarted":0,"cursor-insert key and value bytes inserted":0,"cursor-remove key bytes removed":0,"cursor-update value bytes updated":0,"cursors reused from cache":0,"insert calls":0,"modify calls":0,"next calls":0,"open cursor count":0,"prev calls":0,"remove calls":0,"reserve calls":0,"reset calls":0,"search calls":0,"search near calls":0,"truncate calls":0,"update calls":0},"metadata":{"formatVersion":8,"infoObj":"{ \"v\" : 2, \"key\" : { \"_id\" : 1 }, \"name\" : \"_id_\", \"ns\" : \"MyDatabase.MyCollection\" }"},"reconciliation":{"dictionary matches":0,"fast-path pages deleted":0,"internal page key bytes discarded using suffix compression":0,"internal page multi-block writes":0,"internal-page overflow keys":0,"leaf page key bytes discarded using prefix compression":0,"leaf page multi-block writes":0,"leaf-page overflow keys":0,"maximum blocks required for a page":0,"overflow values written":0,"page checksum matches":0,"page reconciliation calls":0,"page reconciliation calls for eviction":0,"pages deleted":0},"session":{"object compaction":0},"transaction":{"update conflicts":0},"type":"file","uri":"statistics:table:index-28--7017182801192397941"},"supplierId_hashed":{"LSM":{"bloom filter false positives":0,"bloom filter hits":0,"bloom filter misses":0,"bloom filter pages evicted from cache":0,"bloom filter pages read into cache":0,"bloom filters in the LSM tree":0,"chunks in the LSM tree":0,"highest merge generation in the LSM tree":0,"queries that could have benefited from a Bloom filter that did not exist":0,"sleep for LSM checkpoint throttle":0,"sleep for LSM merge throttle":0,"total size of bloom filters":0},"block-manager":{"allocations requiring file extension":0,"blocks allocated":0,"blocks freed":0,"checkpoint size":0,"file allocation unit size":4096,"file bytes available for reuse":0,"file magic number":120897,"file major version number":1,"file size in bytes":4096,"minor version number":0},"btree":{"btree checkpoint generation":70825,"column-store fixed-size leaf pages":0,"column-store internal pages":0,"column-store variable-size RLE encoded values":0,"column-store variable-size deleted values":0,"column-store variable-size leaf pages":0,"fixed-record size":0,"maximum internal page key size":1474,"maximum internal page size":16384,"maximum leaf page key size":1474,"maximum leaf page size":16384,"maximum leaf page value size":7372,"maximum tree depth":0,"number of key/value pairs":0,"overflow pages":0,"pages rewritten by compaction":0,"row-store internal pages":0,"row-store leaf pages":0},"cache":{"bytes currently in the cache":182,"bytes dirty in the cache cumulative":0,"bytes read into cache":0,"bytes written from cache":0,"checkpoint blocked page eviction":0,"data source pages selected for eviction unable to be evicted":0,"eviction walk passes of a file":0,"eviction walk target pages histogram - 0-9":0,"eviction walk target pages histogram - 10-31":0,"eviction walk target pages histogram - 128 and higher":0,"eviction walk target pages histogram - 32-63":0,"eviction walk target pages histogram - 64-128":0,"eviction walks abandoned":0,"eviction walks gave up because they restarted their walk twice":0,"eviction walks gave up because they saw too many pages and found no candidates":0,"eviction walks gave up because they saw too many pages and found too few candidates":0,"eviction walks reached end of tree":0,"eviction walks started from root of tree":0,"eviction walks started from saved location in tree":0,"hazard pointer blocked page eviction":0,"in-memory page passed criteria to be split":0,"in-memory page splits":0,"internal pages evicted":0,"internal pages split during eviction":0,"leaf pages split during eviction":0,"modified pages evicted":0,"overflow pages read into cache":0,"page split during eviction deepened the tree":0,"page written requiring cache overflow records":0,"pages read into cache":0,"pages read into cache after truncate":0,"pages read into cache after truncate in prepare state":0,"pages read into cache requiring cache overflow entries":0,"pages requested from the cache":0,"pages seen by eviction walk":0,"pages written from cache":0,"pages written requiring in-memory restoration":0,"tracked dirty bytes in the cache":0,"unmodified pages evicted":0},"cache_walk":{"Average difference between current eviction generation when the page was last considered":0,"Average on-disk page image size seen":0,"Average time in cache for pages that have been visited by the eviction server":0,"Average time in cache for pages that have not been visited by the eviction server":0,"Clean pages currently in cache":0,"Current eviction generation":0,"Dirty pages currently in cache":0,"Entries in the root page":0,"Internal pages currently in cache":0,"Leaf pages currently in cache":0,"Maximum difference between current eviction generation when the page was last considered":0,"Maximum page size seen":0,"Minimum on-disk page image size seen":0,"Number of pages never visited by eviction server":0,"On-disk page image sizes smaller than a single allocation unit":0,"Pages created in memory and never written":0,"Pages currently queued for eviction":0,"Pages that could not be queued for eviction":0,"Refs skipped during cache traversal":0,"Size of the root page":0,"Total number of pages currently in cache":0},"compression":{"compressed pages read":0,"compressed pages written":0,"page written failed to compress":0,"page written was too small to compress":0},"creationString":"access_pattern_hint=none,allocation_size=4KB,app_metadata=(formatVersion=8,infoObj={ \"v\" : 2, \"key\" : { \"supplierId\" : \"hashed\" }, \"name\" : \"supplierId_hashed\", \"ns\" : \"MyDatabase.MyCollection\" }),assert=(commit_timestamp=none,read_timestamp=none),block_allocation=best,block_compressor=,cache_resident=false,checksum=on,colgroups=,collator=,columns=,dictionary=0,encryption=(keyid=,name=),exclusive=false,extractor=,format=btree,huffman_key=,huffman_value=,ignore_in_memory_cache_size=false,immutable=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=16k,key_format=u,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=16k,leaf_value_max=0,log=(enabled=false),lsm=(auto_throttle=true,bloom=true,bloom_bit_count=16,bloom_config=,bloom_hash_count=8,bloom_oldest=false,chunk_count_limit=0,chunk_max=5GB,chunk_size=10MB,merge_custom=(prefix=,start_generation=0,suffix=),merge_max=15,merge_min=0),memory_page_image_max=0,memory_page_max=5MB,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=true,prefix_compression_min=4,source=,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,type=file,value_format=u","cursor":{"bulk-loaded cursor-insert calls":0,"close calls that result in cache":0,"create calls":0,"cursor operation restarted":0,"cursor-insert key and value bytes inserted":0,"cursor-remove key bytes removed":0,"cursor-update value bytes updated":0,"cursors reused from cache":0,"insert calls":0,"modify calls":0,"next calls":0,"open cursor count":0,"prev calls":0,"remove calls":0,"reserve calls":0,"reset calls":0,"search calls":0,"search near calls":0,"truncate calls":0,"update calls":0},"metadata":{"formatVersion":8,"infoObj":"{ \"v\" : 2, \"key\" : { \"supplierId\" : \"hashed\" }, \"name\" : \"supplierId_hashed\", \"ns\" : \"MyDatabase.MyCollection\" }"},"reconciliation":{"dictionary matches":0,"fast-path pages deleted":0,"internal page key bytes discarded using suffix compression":0,"internal page multi-block writes":0,"internal-page overflow keys":0,"leaf page key bytes discarded using prefix compression":0,"leaf page multi-block writes":0,"leaf-page overflow keys":0,"maximum blocks required for a page":0,"overflow values written":0,"page checksum matches":0,"page reconciliation calls":0,"page reconciliation calls for eviction":0,"pages deleted":0},"session":{"object compaction":0},"transaction":{"update conflicts":0},"type":"file","uri":"statistics:table:index-29--7017182801192397941"}},"indexSizes":{"_id_":4096,"supplierId_hashed":4096},"nindexes":2,"ns":"MyDatabase.MyCollection","ok":1,"size":0,"storageSize":4096,"totalIndexSize":8192,"wiredTiger":{"LSM":{"bloom filter false positives":0,"bloom filter hits":0,"bloom filter misses":0,"bloom filter pages evicted from cache":0,"bloom filter pages read into cache":0,"bloom filters in the LSM tree":0,"chunks in the LSM tree":0,"highest merge generation in the LSM tree":0,"queries that could have benefited from a Bloom filter that did not exist":0,"sleep for LSM checkpoint throttle":0,"sleep for LSM merge throttle":0,"total size of bloom filters":0},"block-manager":{"allocations requiring file extension":0,"blocks allocated":0,"blocks freed":0,"checkpoint size":0,"file allocation unit size":4096,"file bytes available for reuse":0,"file magic number":120897,"file major version number":1,"file size in bytes":4096,"minor version number":0},"btree":{"btree checkpoint generation":70825,"column-store fixed-size leaf pages":0,"column-store internal pages":0,"column-store variable-size RLE encoded values":0,"column-store variable-size deleted values":0,"column-store variable-size leaf pages":0,"fixed-record size":0,"maximum internal page key size":368,"maximum internal page size":4096,"maximum leaf page key size":2867,"maximum leaf page size":32768,"maximum leaf page value size":67108864,"maximum tree depth":0,"number of key/value pairs":0,"overflow pages":0,"pages rewritten by compaction":0,"row-store internal pages":0,"row-store leaf pages":0},"cache":{"bytes currently in the cache":182,"bytes dirty in the cache cumulative":0,"bytes read into cache":0,"bytes written from cache":0,"checkpoint blocked page eviction":0,"data source pages selected for eviction unable to be evicted":0,"eviction walk passes of a file":0,"eviction walk target pages histogram - 0-9":0,"eviction walk target pages histogram - 10-31":0,"eviction walk target pages histogram - 128 and higher":0,"eviction walk target pages histogram - 32-63":0,"eviction walk target pages histogram - 64-128":0,"eviction walks abandoned":0,"eviction walks gave up because they restarted their walk twice":0,"eviction walks gave up because they saw too many pages and found no candidates":0,"eviction walks gave up because they saw too many pages and found too few candidates":0,"eviction walks reached end of tree":0,"eviction walks started from root of tree":0,"eviction walks started from saved location in tree":0,"hazard pointer blocked page eviction":0,"in-memory page passed criteria to be split":0,"in-memory page splits":0,"internal pages evicted":0,"internal pages split during eviction":0,"leaf pages split during eviction":0,"modified pages evicted":0,"overflow pages read into cache":0,"page split during eviction deepened the tree":0,"page written requiring cache overflow records":0,"pages read into cache":0,"pages read into cache after truncate":0,"pages read into cache after truncate in prepare state":0,"pages read into cache requiring cache overflow entries":0,"pages requested from the cache":0,"pages seen by eviction walk":0,"pages written from cache":0,"pages written requiring in-memory restoration":0,"tracked dirty bytes in the cache":0,"unmodified pages evicted":0},"cache_walk":{"Average difference between current eviction generation when the page was last considered":0,"Average on-disk page image size seen":0,"Average time in cache for pages that have been visited by the eviction server":0,"Average time in cache for pages that have not been visited by the eviction server":0,"Clean pages currently in cache":0,"Current eviction generation":0,"Dirty pages currently in cache":0,"Entries in the root page":0,"Internal pages currently in cache":0,"Leaf pages currently in cache":0,"Maximum difference between current eviction generation when the page was last considered":0,"Maximum page size seen":0,"Minimum on-disk page image size seen":0,"Number of pages never visited by eviction server":0,"On-disk page image sizes smaller than a single allocation unit":0,"Pages created in memory and never written":0,"Pages currently queued for eviction":0,"Pages that could not be queued for eviction":0,"Refs skipped during cache traversal":0,"Size of the root page":0,"Total number of pages currently in cache":0},"compression":{"compressed pages read":0,"compressed pages written":0,"page written failed to compress":0,"page written was too small to compress":0},"creationString":"access_pattern_hint=none,allocation_size=4KB,app_metadata=(formatVersion=1),assert=(commit_timestamp=none,read_timestamp=none),block_allocation=best,block_compressor=snappy,cache_resident=false,checksum=on,colgroups=,collator=,columns=,dictionary=0,encryption=(keyid=,name=),exclusive=false,extractor=,format=btree,huffman_key=,huffman_value=,ignore_in_memory_cache_size=false,immutable=false,internal_item_max=0,internal_key_max=0,internal_key_truncate=true,internal_page_max=4KB,key_format=q,key_gap=10,leaf_item_max=0,leaf_key_max=0,leaf_page_max=32KB,leaf_value_max=64MB,log=(enabled=false),lsm=(auto_throttle=true,bloom=true,bloom_bit_count=16,bloom_config=,bloom_hash_count=8,bloom_oldest=false,chunk_count_limit=0,chunk_max=5GB,chunk_size=10MB,merge_custom=(prefix=,start_generation=0,suffix=),merge_max=15,merge_min=0),memory_page_image_max=0,memory_page_max=10m,os_cache_dirty_max=0,os_cache_max=0,prefix_compression=false,prefix_compression_min=4,source=,split_deepen_min_child=0,split_deepen_per_child=0,split_pct=90,type=file,value_format=u","cursor":{"bulk-loaded cursor-insert calls":0,"close calls that result in cache":0,"create calls":0,"cursor operation restarted":0,"cursor-insert key and value bytes inserted":0,"cursor-remove key bytes removed":0,"cursor-update value bytes updated":0,"cursors reused from cache":0,"insert calls":0,"modify calls":0,"next calls":0,"open cursor count":0,"prev calls":0,"remove calls":0,"reserve calls":0,"reset calls":0,"search calls":0,"search near calls":0,"truncate calls":0,"update calls":0},"metadata":{"formatVersion":1},"reconciliation":{"dictionary matches":0,"fast-path pages deleted":0,"internal page key bytes discarded using suffix compression":0,"internal page multi-block writes":0,"internal-page overflow keys":0,"leaf page key bytes discarded using prefix compression":0,"leaf page multi-block writes":0,"leaf-page overflow keys":0,"maximum blocks required for a page":0,"overflow values written":0,"page checksum matches":0,"page reconciliation calls":0,"page reconciliation calls for eviction":0,"pages deleted":0},"session":{"object compaction":0},"transactiozn":{"update conflicts":0},"type":"file","uri":"statistics:table:collection-27--7017182801192397941"}} diff --git a/src/go/plugins/mongodb/testdata/connPoolStats.json b/src/go/plugins/mongodb/testdata/connPoolStats.json new file mode 100644 index 00000000000..c96fc3c69f1 --- /dev/null +++ b/src/go/plugins/mongodb/testdata/connPoolStats.json @@ -0,0 +1 @@ +{"hosts":{"configsvr01:27017":{"available":2,"created":10,"inUse":0,"refreshing":0},"configsvr02:27017":{"available":2,"created":2,"inUse":0,"refreshing":0},"configsvr03:27017":{"available":2,"created":2,"inUse":0,"refreshing":0},"shard01-a:27017":{"available":2,"created":2,"inUse":0,"refreshing":0},"shard01-b:27017":{"available":2,"created":2,"inUse":0,"refreshing":0},"shard01-c:27017":{"available":2,"created":2,"inUse":0,"refreshing":0},"shard02-a:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard02-b:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard02-c:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard03-a:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard03-b:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard03-c:27017":{"available":1,"created":1,"inUse":0,"refreshing":0}},"lastCommittedOpTime":6926878449889968000,"numAScopedConnections":0,"numClientConnections":12,"ok":1,"operationTime":6926878449889968000,"pools":{"NetworkInterfaceTL-Replication":{"poolAvailable":2,"poolCreated":2,"poolInUse":0,"poolRefreshing":0,"shard01-b:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard01-c:27017":{"available":1,"created":1,"inUse":0,"refreshing":0}},"NetworkInterfaceTL-ShardRegistry":{"configsvr01:27017":{"available":1,"created":9,"inUse":0,"refreshing":0},"configsvr02:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"configsvr03:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"poolAvailable":3,"poolCreated":11,"poolInUse":0,"poolRefreshing":0},"NetworkInterfaceTL-TaskExecutorPool-0":{"poolAvailable":1,"poolCreated":1,"poolInUse":0,"poolRefreshing":0,"shard01-a:27017":{"available":1,"created":1,"inUse":0,"refreshing":0}},"global":{"configsvr01:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"configsvr02:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"configsvr03:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"poolAvailable":12,"poolCreated":12,"poolInUse":0,"poolRefreshing":0,"shard01-a:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard01-b:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard01-c:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard02-a:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard02-b:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard02-c:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard03-a:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard03-b:27017":{"available":1,"created":1,"inUse":0,"refreshing":0},"shard03-c:27017":{"available":1,"created":1,"inUse":0,"refreshing":0}}},"replicaSets":{"rs-config-server":{"hosts":[{"addr":"configsvr01:27017","hidden":false,"ismaster":true,"ok":true,"pingTimeMillis":0,"secondary":false},{"addr":"configsvr02:27017","hidden":false,"ismaster":false,"ok":true,"pingTimeMillis":0,"secondary":true},{"addr":"configsvr03:27017","hidden":false,"ismaster":false,"ok":true,"pingTimeMillis":0,"secondary":true}]},"rs-shard-01":{"hosts":[{"addr":"shard01-a:27017","hidden":false,"ismaster":true,"ok":true,"pingTimeMillis":0,"secondary":false},{"addr":"shard01-b:27017","hidden":false,"ismaster":false,"ok":true,"pingTimeMillis":0,"secondary":true},{"addr":"shard01-c:27017","hidden":false,"ismaster":false,"ok":true,"pingTimeMillis":0,"secondary":true}]},"rs-shard-02":{"hosts":[{"addr":"shard02-a:27017","hidden":false,"ismaster":true,"ok":true,"pingTimeMillis":0,"secondary":false},{"addr":"shard02-b:27017","hidden":false,"ismaster":false,"ok":true,"pingTimeMillis":0,"secondary":true},{"addr":"shard02-c:27017","hidden":false,"ismaster":false,"ok":true,"pingTimeMillis":0,"secondary":true}]},"rs-shard-03":{"hosts":[{"addr":"shard03-a:27017","hidden":false,"ismaster":true,"ok":true,"pingTimeMillis":0,"secondary":false},{"addr":"shard03-b:27017","hidden":false,"ismaster":false,"ok":true,"pingTimeMillis":0,"secondary":true},{"addr":"shard03-c:27017","hidden":false,"ismaster":false,"ok":true,"pingTimeMillis":0,"secondary":true}]}},"totalAvailable":18,"totalCreated":26,"totalInUse":0,"totalRefreshing":0} diff --git a/src/go/plugins/mongodb/testdata/dbStats.json b/src/go/plugins/mongodb/testdata/dbStats.json new file mode 100644 index 00000000000..211ddd600f5 --- /dev/null +++ b/src/go/plugins/mongodb/testdata/dbStats.json @@ -0,0 +1 @@ +{"avgObjSize":59,"collections":1,"dataSize":59,"db":"admin","fsTotalSize":67371577344,"fsUsedSize":8687353856,"indexSize":32768,"indexes":1,"objects":1,"ok":1,"scaleFactor":1,"storageSize":32768,"totalSize":65536,"views":0} diff --git a/src/go/plugins/mongodb/testdata/replSetGetConfig.json b/src/go/plugins/mongodb/testdata/replSetGetConfig.json new file mode 100644 index 00000000000..3224800eea4 --- /dev/null +++ b/src/go/plugins/mongodb/testdata/replSetGetConfig.json @@ -0,0 +1 @@ +{"config":{"_id":"rs-shard-01","members":[{"_id":0,"arbiterOnly":false,"buildIndexes":true,"hidden":false,"host":"shard01-a:27017","priority":1,"slaveDelay":0,"tags":{},"votes":1},{"_id":1,"arbiterOnly":false,"buildIndexes":true,"hidden":false,"host":"shard01-b:27017","priority":1,"slaveDelay":0,"tags":{},"votes":1},{"_id":2,"arbiterOnly":false,"buildIndexes":true,"hidden":false,"host":"shard01-c:27017","priority":1,"slaveDelay":0,"tags":{},"votes":1}],"protocolVersion":1,"settings":{"catchUpTakeoverDelayMillis":30000,"catchUpTimeoutMillis":-1,"chainingAllowed":true,"electionTimeoutMillis":10000,"getLastErrorDefaults":{"w":1,"wtimeout":0},"getLastErrorModes":{},"heartbeatIntervalMillis":2000,"heartbeatTimeoutSecs":10,"replicaSetId":"5fe0628084064df4684b5e4d"},"version":1,"writeConcernMajorityJournalDefault":true}} diff --git a/src/go/plugins/mongodb/testdata/serverStatus.json b/src/go/plugins/mongodb/testdata/serverStatus.json new file mode 100644 index 00000000000..5a22d85f2eb --- /dev/null +++ b/src/go/plugins/mongodb/testdata/serverStatus.json @@ -0,0 +1 @@ +{"connections":{"active":1,"available":838859,"awaitingTopologyChanges":0,"current":1,"exhaustHello":0,"exhaustIsMaster":0,"totalCreated":8},"electionMetrics":{"averageCatchUpOps":0,"catchUpTakeover":{"called":0,"successful":0},"electionTimeout":{"called":0,"successful":0},"freezeTimeout":{"called":0,"successful":0},"numCatchUps":0,"numCatchUpsAlreadyCaughtUp":0,"numCatchUpsFailedWithError":0,"numCatchUpsFailedWithNewTerm":0,"numCatchUpsFailedWithReplSetAbortPrimaryCatchUpCmd":0,"numCatchUpsSkipped":0,"numCatchUpsSucceeded":0,"numCatchUpsTimedOut":0,"numStepDownsCausedByHigherTerm":0,"priorityTakeover":{"called":0,"successful":0},"stepUpCmd":{"called":0,"successful":0}},"extra_info":{"input_blocks":0,"involuntary_context_switches":8133,"maximum_resident_set_kb":97780,"note":"fields vary by platform","output_blocks":22096,"page_faults":0,"page_reclaims":17360,"system_time_us":18191069,"user_time_us":17360886,"voluntary_context_switches":152109},"flowControl":{"enabled":true,"isLagged":false,"isLaggedCount":0,"isLaggedTimeMicros":0,"locksPerKiloOp":0,"sustainerRate":0,"targetRateLimit":1000000000,"timeAcquiringMicros":4111},"freeMonitoring":{"state":"undecided"},"globalLock":{"activeClients":{"readers":0,"total":0,"writers":0},"currentQueue":{"readers":0,"total":0,"writers":0},"totalTime":4307530000},"host":"1f00645c8ab3","localTime":"2021-02-08T14:45:24.784+02:00","locks":{"Collection":{"acquireCount":{"W":2,"r":151,"w":71}},"Database":{"acquireCount":{"W":4,"r":126,"w":71}},"Global":{"acquireCount":{"W":4,"r":13049,"w":75}},"Mutex":{"acquireCount":{"r":200}},"ParallelBatchWriterMode":{"acquireCount":{"r":159}},"ReplicationStateTransition":{"acquireCount":{"w":13128}}},"logicalSessionRecordCache":{"activeSessionsCount":0,"lastSessionsCollectionJobCursorsClosed":0,"lastSessionsCollectionJobDurationMillis":10,"lastSessionsCollectionJobEntriesEnded":0,"lastSessionsCollectionJobEntriesRefreshed":0,"lastSessionsCollectionJobTimestamp":"2021-02-08T14:43:38.435+02:00","lastTransactionReaperJobDurationMillis":11,"lastTransactionReaperJobEntriesCleanedUp":0,"lastTransactionReaperJobTimestamp":"2021-02-08T14:43:38.435+02:00","sessionCatalogSize":0,"sessionsCollectionJobCount":15,"transactionReaperJobCount":15},"mem":{"bits":64,"resident":93,"supported":true,"virtual":1550},"metrics":{"aggStageCounters":{"$_internalInhibitOptimization":0,"$_internalSplitPipeline":0,"$addFields":0,"$bucket":0,"$bucketAuto":0,"$changeStream":0,"$collStats":0,"$count":0,"$currentOp":0,"$facet":0,"$geoNear":0,"$graphLookup":0,"$group":0,"$indexStats":0,"$limit":0,"$listLocalSessions":0,"$listSessions":0,"$lookup":0,"$match":0,"$merge":0,"$mergeCursors":0,"$out":0,"$planCacheStats":0,"$project":0,"$redact":0,"$replaceRoot":0,"$replaceWith":0,"$sample":0,"$set":0,"$skip":0,"$sort":0,"$sortByCount":0,"$unionWith":0,"$unset":0,"$unwind":0},"commands":{"\u003cUNKNOWN\u003e":0,"_addShard":{"failed":0,"total":0},"_cloneCollectionOptionsFromPrimaryShard":{"failed":0,"total":0},"_configsvrAddShard":{"failed":0,"total":0},"_configsvrAddShardToZone":{"failed":0,"total":0},"_configsvrBalancerCollectionStatus":{"failed":0,"total":0},"_configsvrBalancerStart":{"failed":0,"total":0},"_configsvrBalancerStatus":{"failed":0,"total":0},"_configsvrBalancerStop":{"failed":0,"total":0},"_configsvrClearJumboFlag":{"failed":0,"total":0},"_configsvrCommitChunkMerge":{"failed":0,"total":0},"_configsvrCommitChunkMigration":{"failed":0,"total":0},"_configsvrCommitChunkSplit":{"failed":0,"total":0},"_configsvrCommitMovePrimary":{"failed":0,"total":0},"_configsvrCreateCollection":{"failed":0,"total":0},"_configsvrCreateDatabase":{"failed":0,"total":0},"_configsvrDropCollection":{"failed":0,"total":0},"_configsvrDropDatabase":{"failed":0,"total":0},"_configsvrEnableSharding":{"failed":0,"total":0},"_configsvrEnsureChunkVersionIsGreaterThan":{"failed":0,"total":0},"_configsvrMoveChunk":{"failed":0,"total":0},"_configsvrMovePrimary":{"failed":0,"total":0},"_configsvrRefineCollectionShardKey":{"failed":0,"total":0},"_configsvrRemoveShard":{"failed":0,"total":0},"_configsvrRemoveShardFromZone":{"failed":0,"total":0},"_configsvrShardCollection":{"failed":0,"total":0},"_configsvrUpdateZoneKeyRange":{"failed":0,"total":0},"_flushDatabaseCacheUpdates":{"failed":0,"total":0},"_flushRoutingTableCacheUpdates":{"failed":0,"total":0},"_getNextSessionMods":{"failed":0,"total":0},"_getUserCacheGeneration":{"failed":0,"total":0},"_isSelf":{"failed":0,"total":0},"_killOperations":{"failed":0,"total":0},"_mergeAuthzCollections":{"failed":0,"total":0},"_migrateClone":{"failed":0,"total":0},"_recvChunkAbort":{"failed":0,"total":0},"_recvChunkCommit":{"failed":0,"total":0},"_recvChunkStart":{"failed":0,"total":0},"_recvChunkStatus":{"failed":0,"total":0},"_shardsvrCloneCatalogData":{"failed":0,"total":0},"_shardsvrMovePrimary":{"failed":0,"total":0},"_shardsvrShardCollection":{"failed":0,"total":0},"_transferMods":{"failed":0,"total":0},"abortTransaction":{"failed":0,"total":0},"aggregate":{"failed":0,"total":0},"appendOplogNote":{"failed":0,"total":0},"applyOps":{"failed":0,"total":0},"authenticate":{"failed":0,"total":0},"availableQueryOptions":{"failed":0,"total":0},"buildInfo":{"failed":0,"total":0},"checkShardingIndex":{"failed":0,"total":0},"cleanupOrphaned":{"failed":0,"total":0},"cloneCollectionAsCapped":{"failed":0,"total":0},"collMod":{"failed":0,"total":0},"collStats":{"failed":0,"total":0},"commitTransaction":{"failed":0,"total":0},"compact":{"failed":0,"total":0},"connPoolStats":{"failed":0,"total":0},"connPoolSync":{"failed":0,"total":0},"connectionStatus":{"failed":0,"total":0},"convertToCapped":{"failed":0,"total":0},"coordinateCommitTransaction":{"failed":0,"total":0},"count":{"failed":0,"total":0},"create":{"failed":0,"total":0},"createIndexes":{"failed":0,"total":0},"createRole":{"failed":0,"total":0},"createUser":{"failed":0,"total":0},"currentOp":{"failed":0,"total":0},"dataSize":{"failed":0,"total":0},"dbHash":{"failed":0,"total":0},"dbStats":{"failed":0,"total":0},"delete":{"failed":0,"total":0},"distinct":{"failed":0,"total":0},"driverOIDTest":{"failed":0,"total":0},"drop":{"failed":0,"total":0},"dropAllRolesFromDatabase":{"failed":0,"total":0},"dropAllUsersFromDatabase":{"failed":0,"total":0},"dropConnections":{"failed":0,"total":0},"dropDatabase":{"failed":0,"total":0},"dropIndexes":{"failed":0,"total":0},"dropRole":{"failed":0,"total":0},"dropUser":{"failed":0,"total":0},"endSessions":{"failed":0,"total":0},"explain":{"failed":0,"total":0},"features":{"failed":0,"total":0},"filemd5":{"failed":0,"total":0},"find":{"failed":0,"total":16},"findAndModify":{"arrayFilters":0,"failed":0,"pipeline":0,"total":0},"flushRouterConfig":{"failed":0,"total":0},"fsync":{"failed":0,"total":0},"fsyncUnlock":{"failed":0,"total":0},"geoSearch":{"failed":0,"total":0},"getCmdLineOpts":{"failed":0,"total":0},"getDatabaseVersion":{"failed":0,"total":0},"getDefaultRWConcern":{"failed":0,"total":0},"getDiagnosticData":{"failed":0,"total":0},"getFreeMonitoringStatus":{"failed":0,"total":0},"getLastError":{"failed":0,"total":0},"getLog":{"failed":0,"total":0},"getMore":{"failed":0,"total":0},"getParameter":{"failed":0,"total":0},"getShardMap":{"failed":0,"total":0},"getShardVersion":{"failed":0,"total":0},"getnonce":{"failed":0,"total":8},"grantPrivilegesToRole":{"failed":0,"total":0},"grantRolesToRole":{"failed":0,"total":0},"grantRolesToUser":{"failed":0,"total":0},"hello":{"failed":0,"total":0},"hostInfo":{"failed":0,"total":0},"insert":{"failed":0,"total":0},"internalRenameIfOptionsAndIndexesMatch":{"failed":0,"total":0},"invalidateUserCache":{"failed":0,"total":0},"isMaster":{"failed":0,"total":17},"killAllSessions":{"failed":0,"total":0},"killAllSessionsByPattern":{"failed":0,"total":0},"killCursors":{"failed":0,"total":0},"killOp":{"failed":0,"total":0},"killSessions":{"failed":0,"total":0},"listCollections":{"failed":0,"total":0},"listCommands":{"failed":0,"total":0},"listDatabases":{"failed":0,"total":0},"listIndexes":{"failed":0,"total":30},"lockInfo":{"failed":0,"total":0},"logRotate":{"failed":0,"total":0},"logout":{"failed":0,"total":0},"mapReduce":{"failed":0,"total":0},"mapreduce":{"shardedfinish":{"failed":0,"total":0}},"mergeChunks":{"failed":0,"total":0},"moveChunk":{"failed":0,"total":0},"ping":{"failed":0,"total":39},"planCacheClear":{"failed":0,"total":0},"planCacheClearFilters":{"failed":0,"total":0},"planCacheListFilters":{"failed":0,"total":0},"planCacheSetFilter":{"failed":0,"total":0},"prepareTransaction":{"failed":0,"total":0},"profile":{"failed":0,"total":0},"reIndex":{"failed":0,"total":0},"refreshSessions":{"failed":0,"total":0},"renameCollection":{"failed":0,"total":0},"repairDatabase":{"failed":0,"total":0},"replSetAbortPrimaryCatchUp":{"failed":0,"total":0},"replSetFreeze":{"failed":0,"total":0},"replSetGetConfig":{"failed":0,"total":0},"replSetGetRBID":{"failed":0,"total":0},"replSetGetStatus":{"failed":0,"total":0},"replSetHeartbeat":{"failed":0,"total":0},"replSetInitiate":{"failed":0,"total":0},"replSetMaintenance":{"failed":0,"total":0},"replSetReconfig":{"failed":0,"total":0},"replSetRequestVotes":{"failed":0,"total":0},"replSetResizeOplog":{"failed":0,"total":0},"replSetStepDown":{"failed":0,"total":0},"replSetStepDownWithForce":{"failed":0,"total":0},"replSetStepUp":{"failed":0,"total":0},"replSetSyncFrom":{"failed":0,"total":0},"replSetUpdatePosition":{"failed":0,"total":0},"resetError":{"failed":0,"total":0},"revokePrivilegesFromRole":{"failed":0,"total":0},"revokeRolesFromRole":{"failed":0,"total":0},"revokeRolesFromUser":{"failed":0,"total":0},"rolesInfo":{"failed":0,"total":0},"saslContinue":{"failed":0,"total":0},"saslStart":{"failed":0,"total":0},"serverStatus":{"failed":0,"total":7},"setDefaultRWConcern":{"failed":0,"total":0},"setFeatureCompatibilityVersion":{"failed":0,"total":0},"setFreeMonitoring":{"failed":0,"total":0},"setIndexCommitQuorum":{"failed":0,"total":0},"setParameter":{"failed":0,"total":0},"setShardVersion":{"failed":0,"total":0},"shardConnPoolStats":{"failed":0,"total":0},"shardingState":{"failed":0,"total":0},"shutdown":{"failed":0,"total":0},"splitChunk":{"failed":0,"total":0},"splitVector":{"failed":0,"total":0},"startRecordingTraffic":{"failed":0,"total":0},"startSession":{"failed":0,"total":0},"stopRecordingTraffic":{"failed":0,"total":0},"top":{"failed":0,"total":0},"unsetSharding":{"failed":0,"total":0},"update":{"arrayFilters":0,"failed":0,"pipeline":0,"total":0},"updateRole":{"failed":0,"total":0},"updateUser":{"failed":0,"total":0},"usersInfo":{"failed":0,"total":0},"validate":{"failed":0,"total":0},"voteCommitIndexBuild":{"failed":0,"total":0},"waitForFailPoint":{"failed":0,"total":0},"whatsmyuri":{"failed":0,"total":0}},"cursor":{"open":{"noTimeout":0,"pinned":0,"total":0},"timedOut":0},"document":{"deleted":0,"inserted":0,"returned":0,"updated":0},"getLastError":{"default":{"unsatisfiable":0,"wtimeouts":0},"wtime":{"num":0,"totalMillis":0},"wtimeouts":0},"operation":{"scanAndOrder":0,"writeConflicts":0},"query":{"planCacheTotalSizeEstimateBytes":0,"updateOneOpStyleBroadcastWithExactIDCount":0},"queryExecutor":{"collectionScans":{"nonTailable":0,"total":0},"scanned":0,"scannedObjects":0},"record":{"moves":0},"repl":{"apply":{"attemptsToBecomeSecondary":0,"batchSize":0,"batches":{"num":0,"totalMillis":0},"ops":0},"buffer":{"count":0,"maxSizeBytes":0,"sizeBytes":0},"executor":{"networkInterface":"DEPRECATED: getDiagnosticString is deprecated in NetworkInterfaceTL","pool":{"inProgressCount":0},"queues":{"networkInProgress":0,"sleepers":0},"shuttingDown":false,"unsignaledEvents":0},"initialSync":{"completed":0,"failedAttempts":0,"failures":0},"network":{"bytes":0,"getmores":{"num":0,"numEmptyBatches":0,"totalMillis":0},"notMasterLegacyUnacknowledgedWrites":0,"notMasterUnacknowledgedWrites":0,"oplogGetMoresProcessed":{"num":0,"totalMillis":0},"ops":0,"readersCreated":0,"replSetUpdatePosition":{"num":0}},"stateTransition":{"lastStateTransition":"","userOperationsKilled":0,"userOperationsRunning":0},"syncSource":{"numSelections":0,"numTimesChoseDifferent":0,"numTimesChoseSame":0,"numTimesCouldNotFind":0}},"ttl":{"deletedDocuments":0,"passes":71}},"network":{"bytesIn":4394,"bytesOut":261528,"compression":{"snappy":{"compressor":{"bytesIn":0,"bytesOut":0},"decompressor":{"bytesIn":0,"bytesOut":0}},"zlib":{"compressor":{"bytesIn":0,"bytesOut":0},"decompressor":{"bytesIn":0,"bytesOut":0}},"zstd":{"compressor":{"bytesIn":0,"bytesOut":0},"decompressor":{"bytesIn":0,"bytesOut":0}}},"numRequests":71,"numSlowDNSOperations":0,"numSlowSSLOperations":0,"physicalBytesIn":4394,"physicalBytesOut":261528,"serviceExecutorTaskStats":{"executor":"passthrough","threadsRunning":1},"tcpFastOpen":{"accepted":0,"clientSupported":true,"kernelSetting":1,"serverSupported":true}},"ok":1,"opLatencies":{"commands":{"latency":8911,"ops":70},"reads":{"latency":0,"ops":0},"transactions":{"latency":0,"ops":0},"writes":{"latency":0,"ops":0}},"opReadConcernCounters":{"available":0,"linearizable":0,"local":0,"majority":0,"none":16,"snapshot":0},"opcounters":{"command":101,"delete":0,"getmore":0,"insert":0,"query":16,"update":0},"opcountersRepl":{"command":0,"delete":0,"getmore":0,"insert":0,"query":0,"update":0},"pid":1,"process":"mongod","security":{"authentication":{"mechanisms":{"MONGODB-X509":{"authenticate":{"received":0,"successful":0},"speculativeAuthenticate":{"received":0,"successful":0}},"SCRAM-SHA-1":{"authenticate":{"received":0,"successful":0},"speculativeAuthenticate":{"received":0,"successful":0}},"SCRAM-SHA-256":{"authenticate":{"received":0,"successful":0},"speculativeAuthenticate":{"received":0,"successful":0}}}}},"storageEngine":{"backupCursorOpen":false,"dropPendingIdents":0,"name":"wiredTiger","oldestRequiredTimestampForCrashRecovery":0,"persistent":true,"readOnly":false,"supportsCommittedReads":true,"supportsPendingDrops":true,"supportsSnapshotReadConcern":true,"supportsTwoPhaseIndexBuild":true},"tcmalloc":{"generic":{"current_allocated_bytes":84765480,"heap_size":89341952},"tcmalloc":{"aggressive_memory_decommit":0,"central_cache_free_bytes":224512,"current_total_thread_cache_bytes":729944,"formattedString":"------------------------------------------------\nMALLOC: 84766056 ( 80.8 MiB) Bytes in use by application\nMALLOC: + 3330048 ( 3.2 MiB) Bytes in page heap freelist\nMALLOC: + 224512 ( 0.2 MiB) Bytes in central cache freelist\nMALLOC: + 291968 ( 0.3 MiB) Bytes in transfer cache freelist\nMALLOC: + 729368 ( 0.7 MiB) Bytes in thread cache freelists\nMALLOC: + 2752512 ( 2.6 MiB) Bytes in malloc metadata\nMALLOC: ------------\nMALLOC: = 92094464 ( 87.8 MiB) Actual memory used (physical + swap)\nMALLOC: + 0 ( 0.0 MiB) Bytes released to OS (aka unmapped)\nMALLOC: ------------\nMALLOC: = 92094464 ( 87.8 MiB) Virtual address space used\nMALLOC:\nMALLOC: 674 Spans in use\nMALLOC: 31 Thread heaps in use\nMALLOC: 4096 Tcmalloc page size\n------------------------------------------------\nCall ReleaseFreeMemory() to release freelist memory to the OS (via madvise()).\nBytes released to the OS take up virtual address space but no physical memory.\n","max_total_thread_cache_bytes":260046848,"pageheap_commit_count":62,"pageheap_committed_bytes":89341952,"pageheap_decommit_count":1,"pageheap_free_bytes":3330048,"pageheap_reserve_count":47,"pageheap_scavenge_count":1,"pageheap_total_commit_bytes":92364800,"pageheap_total_decommit_bytes":3022848,"pageheap_total_reserve_bytes":89341952,"pageheap_unmapped_bytes":0,"release_rate":1,"spinlock_total_delay_ns":0,"thread_cache_free_bytes":729944,"total_free_bytes":1246424,"transfer_cache_free_bytes":291968}},"trafficRecording":{"running":false},"transactions":{"currentActive":0,"currentInactive":0,"currentOpen":0,"currentPrepared":0,"retriedCommandsCount":0,"retriedStatementsCount":0,"totalAborted":0,"totalCommitted":0,"totalPrepared":0,"totalPreparedThenAborted":0,"totalPreparedThenCommitted":0,"totalStarted":0,"transactionsCollectionWriteCount":0},"transportSecurity":{"1.0":0,"1.1":0,"1.2":0,"1.3":0,"unknown":0},"twoPhaseCommitCoordinator":{"currentInSteps":{"deletingCoordinatorDoc":0,"waitingForDecisionAcks":0,"waitingForVotes":0,"writingDecision":0,"writingParticipantList":0},"totalAbortedTwoPhaseCommit":0,"totalCommittedTwoPhaseCommit":0,"totalCreated":0,"totalStartedTwoPhaseCommit":0},"uptime":4307,"uptimeEstimate":4307,"uptimeMillis":4307533,"version":"4.4.2","wiredTiger":{"block-manager":{"blocks pre-loaded":7,"blocks read":106,"blocks written":393,"bytes read":454656,"bytes read via memory map API":0,"bytes read via system call API":0,"bytes written":2793472,"bytes written for checkpoint":2793472,"bytes written via memory map API":0,"bytes written via system call API":0,"mapped blocks read":0,"mapped bytes read":0,"number of times the file was remapped because it changed size via fallocate or truncate":0,"number of times the region was remapped via write":0},"cache":{"application threads page read from disk to cache count":6,"application threads page read from disk to cache time (usecs)":63,"application threads page write from cache to disk count":148,"application threads page write from cache to disk time (usecs)":9767,"bytes allocated for updates":23075,"bytes belonging to page images in the cache":76829,"bytes belonging to the history store table in the cache":847,"bytes currently in the cache":106686,"bytes dirty in the cache cumulative":2938265,"bytes not belonging to page images in the cache":29857,"bytes read into cache":71138,"bytes written from cache":1275601,"cache overflow score":0,"checkpoint blocked page eviction":0,"eviction calls to get a page":376,"eviction calls to get a page found queue empty":302,"eviction calls to get a page found queue empty after locking":2,"eviction currently operating in aggressive mode":0,"eviction empty score":0,"eviction passes of a file":0,"eviction server candidate queue empty when topping up":0,"eviction server candidate queue not empty when topping up":0,"eviction server evicting pages":0,"eviction server slept, because we did not make progress with eviction":71,"eviction server unable to reach eviction goal":0,"eviction server waiting for a leaf page":3,"eviction state":64,"eviction walk target pages histogram - 0-9":0,"eviction walk target pages histogram - 10-31":0,"eviction walk target pages histogram - 128 and higher":0,"eviction walk target pages histogram - 32-63":0,"eviction walk target pages histogram - 64-128":0,"eviction walk target strategy both clean and dirty pages":0,"eviction walk target strategy only clean pages":0,"eviction walk target strategy only dirty pages":0,"eviction walks abandoned":0,"eviction walks gave up because they restarted their walk twice":0,"eviction walks gave up because they saw too many pages and found no candidates":0,"eviction walks gave up because they saw too many pages and found too few candidates":0,"eviction walks reached end of tree":0,"eviction walks restarted":0,"eviction walks started from root of tree":0,"eviction walks started from saved location in tree":0,"eviction worker thread active":4,"eviction worker thread created":0,"eviction worker thread evicting pages":71,"eviction worker thread removed":0,"eviction worker thread stable number":0,"files with active eviction walks":0,"files with new eviction walks started":0,"force re-tuning of eviction workers once in a while":0,"forced eviction - history store pages failed to evict while session has history store cursor open":0,"forced eviction - history store pages selected while session has history store cursor open":0,"forced eviction - history store pages successfully evicted while session has history store cursor open":0,"forced eviction - pages evicted that were clean count":0,"forced eviction - pages evicted that were clean time (usecs)":0,"forced eviction - pages evicted that were dirty count":0,"forced eviction - pages evicted that were dirty time (usecs)":0,"forced eviction - pages selected because of too many deleted items count":0,"forced eviction - pages selected count":0,"forced eviction - pages selected unable to be evicted count":0,"forced eviction - pages selected unable to be evicted time":0,"forced eviction - session returned rollback error while force evicting due to being oldest":0,"hazard pointer blocked page eviction":0,"hazard pointer check calls":71,"hazard pointer check entries walked":2,"hazard pointer maximum array length":2,"history store score":0,"history store table insert calls":0,"history store table insert calls that returned restart":0,"history store table max on-disk size":0,"history store table on-disk size":0,"history store table out-of-order resolved updates that lose their durable timestamp":0,"history store table out-of-order updates that were fixed up by moving existing records":0,"history store table out-of-order updates that were fixed up during insertion":0,"history store table reads":0,"history store table reads missed":0,"history store table reads requiring squashed modifies":0,"history store table truncation by rollback to stable to remove an unstable update":0,"history store table truncation by rollback to stable to remove an update":0,"history store table truncation to remove an update":0,"history store table truncation to remove range of updates due to key being removed from the data page during reconciliation":0,"history store table truncation to remove range of updates due to mixed timestamps":0,"history store table writes requiring squashed modifies":0,"in-memory page passed criteria to be split":0,"in-memory page splits":0,"internal pages evicted":0,"internal pages queued for eviction":0,"internal pages seen by eviction walk":0,"internal pages seen by eviction walk that are already queued":0,"internal pages split during eviction":0,"leaf pages split during eviction":0,"maximum bytes configured":505413632,"maximum page size at eviction":352,"modified pages evicted":71,"modified pages evicted by application threads":0,"operations timed out waiting for space in cache":0,"overflow pages read into cache":0,"page split during eviction deepened the tree":0,"page written requiring history store records":0,"pages currently held in the cache":19,"pages evicted by application threads":0,"pages evicted in parallel with checkpoint":71,"pages queued for eviction":0,"pages queued for eviction post lru sorting":0,"pages queued for urgent eviction":71,"pages queued for urgent eviction during walk":0,"pages read into cache":14,"pages read into cache after truncate":72,"pages read into cache after truncate in prepare state":0,"pages requested from the cache":1968,"pages seen by eviction walk":0,"pages seen by eviction walk that are already queued":0,"pages selected for eviction unable to be evicted":0,"pages selected for eviction unable to be evicted as the parent page has overflow items":0,"pages selected for eviction unable to be evicted because of active children on an internal page":0,"pages selected for eviction unable to be evicted because of failure in reconciliation":0,"pages walked for eviction":0,"pages written from cache":156,"pages written requiring in-memory restoration":0,"percentage overhead":8,"tracked bytes belonging to internal pages in the cache":5470,"tracked bytes belonging to leaf pages in the cache":101216,"tracked dirty bytes in the cache":467,"tracked dirty pages in the cache":1,"unmodified pages evicted":0},"capacity":{"background fsync file handles considered":0,"background fsync file handles synced":0,"background fsync time (msecs)":0,"bytes read":77824,"bytes written for checkpoint":1223343,"bytes written for eviction":0,"bytes written for log":1258339200,"bytes written total":1259562543,"threshold to call fsync":0,"time waiting due to total capacity (usecs)":0,"time waiting during checkpoint (usecs)":0,"time waiting during eviction (usecs)":0,"time waiting during logging (usecs)":0,"time waiting during read (usecs)":0},"checkpoint-cleanup":{"pages added for eviction":71,"pages removed":0,"pages skipped during tree walk":0,"pages visited":145},"concurrentTransactions":{"read":{"available":127,"out":1,"totalTickets":128},"write":{"available":128,"out":0,"totalTickets":128}},"connection":{"auto adjusting condition resets":378,"auto adjusting condition wait calls":26641,"auto adjusting condition wait raced to update timeout and skipped updating":0,"detected system time went backwards":0,"files currently open":14,"hash bucket array size for data handles":512,"hash bucket array size general":512,"memory allocations":173468,"memory frees":172614,"memory re-allocations":16623,"pthread mutex condition wait calls":69351,"pthread mutex shared lock read-lock calls":84645,"pthread mutex shared lock write-lock calls":4920,"total fsync I/Os":461,"total read I/Os":1439,"total write I/Os":550},"cursor":{"Total number of entries skipped by cursor next calls":0,"Total number of entries skipped by cursor prev calls":0,"Total number of entries skipped to position the history store cursor":0,"cached cursor count":15,"cursor bulk loaded cursor insert calls":0,"cursor close calls that result in cache":35885,"cursor create calls":70,"cursor insert calls":159,"cursor insert key and value bytes":102523,"cursor modify calls":0,"cursor modify key and value bytes affected":0,"cursor modify value bytes modified":0,"cursor next calls":196,"cursor next calls that skip due to a globally visible history store tombstone":0,"cursor next calls that skip due to a globally visible history store tombstone in rollback to stable":0,"cursor next calls that skip greater than or equal to 100 entries":0,"cursor next calls that skip less than 100 entries":194,"cursor operation restarted":0,"cursor prev calls":81,"cursor prev calls that skip due to a globally visible history store tombstone":0,"cursor prev calls that skip due to a globally visible history store tombstone in rollback to stable":0,"cursor prev calls that skip greater than or equal to 100 entries":0,"cursor prev calls that skip less than 100 entries":81,"cursor remove calls":0,"cursor remove key bytes removed":0,"cursor reserve calls":0,"cursor reset calls":37595,"cursor search calls":839,"cursor search history store calls":0,"cursor search near calls":86,"cursor sweep buckets":6156,"cursor sweep cursors closed":0,"cursor sweep cursors examined":5,"cursor sweeps":1026,"cursor truncate calls":0,"cursor update calls":0,"cursor update key and value bytes":0,"cursor update value size change":0,"cursors reused from cache":35863,"open cursor count":7},"data-handle":{"connection data handle size":432,"connection data handles currently active":19,"connection sweep candidate became referenced":0,"connection sweep dhandles closed":0,"connection sweep dhandles removed from hash list":84,"connection sweep time-of-death sets":681,"connection sweeps":426,"session dhandles swept":139,"session sweep attempts":102},"lock":{"checkpoint lock acquisitions":72,"checkpoint lock application thread wait time (usecs)":0,"checkpoint lock internal thread wait time (usecs)":0,"dhandle lock application thread time waiting (usecs)":0,"dhandle lock internal thread time waiting (usecs)":0,"dhandle read lock acquisitions":17668,"dhandle write lock acquisitions":187,"durable timestamp queue lock application thread time waiting (usecs)":0,"durable timestamp queue lock internal thread time waiting (usecs)":0,"durable timestamp queue read lock acquisitions":0,"durable timestamp queue write lock acquisitions":0,"metadata lock acquisitions":72,"metadata lock application thread wait time (usecs)":100,"metadata lock internal thread wait time (usecs)":0,"read timestamp queue lock application thread time waiting (usecs)":0,"read timestamp queue lock internal thread time waiting (usecs)":0,"read timestamp queue read lock acquisitions":0,"read timestamp queue write lock acquisitions":0,"schema lock acquisitions":85,"schema lock application thread wait time (usecs)":0,"schema lock internal thread wait time (usecs)":0,"table lock application thread time waiting for the table lock (usecs)":0,"table lock internal thread time waiting for the table lock (usecs)":0,"table read lock acquisitions":0,"table write lock acquisitions":9,"txn global lock application thread time waiting (usecs)":0,"txn global lock internal thread time waiting (usecs)":0,"txn global read lock acquisitions":336,"txn global write lock acquisitions":218},"log":{"busy returns attempting to switch slots":0,"force archive time sleeping (usecs)":0,"log bytes of payload data":80873,"log bytes written":112512,"log files manually zero-filled":0,"log flush operations":41972,"log force write operations":46637,"log force write operations skipped":46629,"log records compressed":72,"log records not compressed":0,"log records too small to compress":290,"log release advances write LSN":73,"log scan operations":6,"log scan records requiring two reads":0,"log server thread advances write LSN":8,"log server thread write LSN walk skipped":5236,"log sync operations":81,"log sync time duration (usecs)":150305,"log sync_dir operations":1,"log sync_dir time duration (usecs)":4788,"log write operations":362,"logging bytes consolidated":112000,"maximum log file size":104857600,"number of pre-allocated log files to create":2,"pre-allocated log files not ready and missed":1,"pre-allocated log files prepared":2,"pre-allocated log files used":0,"records processed by log scan":15,"slot close lost race":0,"slot close unbuffered waits":0,"slot closures":81,"slot join atomic update races":0,"slot join calls atomic updates raced":0,"slot join calls did not yield":362,"slot join calls found active slot closed":0,"slot join calls slept":0,"slot join calls yielded":0,"slot join found active slot closed":0,"slot joins yield time (usecs)":0,"slot transitions unable to find free slot":0,"slot unbuffered writes":0,"total in-memory size of compressed records":92785,"total log buffer size":33554432,"total size of compressed records":66331,"written slots coalesced":0,"yields waiting for previous log file close":0},"oplog":{"visibility timestamp":0},"perf":{"file system read latency histogram (bucket 1) - 10-49ms":0,"file system read latency histogram (bucket 2) - 50-99ms":0,"file system read latency histogram (bucket 3) - 100-249ms":0,"file system read latency histogram (bucket 4) - 250-499ms":0,"file system read latency histogram (bucket 5) - 500-999ms":0,"file system read latency histogram (bucket 6) - 1000ms+":0,"file system write latency histogram (bucket 1) - 10-49ms":0,"file system write latency histogram (bucket 2) - 50-99ms":0,"file system write latency histogram (bucket 3) - 100-249ms":0,"file system write latency histogram (bucket 4) - 250-499ms":0,"file system write latency histogram (bucket 5) - 500-999ms":0,"file system write latency histogram (bucket 6) - 1000ms+":0,"operation read latency histogram (bucket 1) - 100-249us":1,"operation read latency histogram (bucket 2) - 250-499us":1,"operation read latency histogram (bucket 3) - 500-999us":0,"operation read latency histogram (bucket 4) - 1000-9999us":0,"operation read latency histogram (bucket 5) - 10000us+":0,"operation write latency histogram (bucket 1) - 100-249us":1,"operation write latency histogram (bucket 2) - 250-499us":0,"operation write latency histogram (bucket 3) - 500-999us":0,"operation write latency histogram (bucket 4) - 1000-9999us":0,"operation write latency histogram (bucket 5) - 10000us+":0},"reconciliation":{"approximate byte size of timestamps in pages written":0,"approximate byte size of transaction IDs in pages written":1184,"fast-path pages deleted":0,"maximum seconds spent in a reconciliation call":0,"page reconciliation calls":374,"page reconciliation calls for eviction":71,"page reconciliation calls that resulted in values with prepared transaction metadata":0,"page reconciliation calls that resulted in values with timestamps":0,"page reconciliation calls that resulted in values with transaction ids":68,"pages deleted":218,"pages written including an aggregated newest start durable timestamp ":0,"pages written including an aggregated newest stop durable timestamp ":0,"pages written including an aggregated newest stop timestamp ":0,"pages written including an aggregated newest stop transaction ID":0,"pages written including an aggregated newest transaction ID ":0,"pages written including an aggregated oldest start timestamp ":0,"pages written including an aggregated prepare":0,"pages written including at least one prepare state":0,"pages written including at least one start durable timestamp":0,"pages written including at least one start timestamp":0,"pages written including at least one start transaction ID":68,"pages written including at least one stop durable timestamp":0,"pages written including at least one stop timestamp":0,"pages written including at least one stop transaction ID":0,"records written including a prepare state":0,"records written including a start durable timestamp":0,"records written including a start timestamp":0,"records written including a start transaction ID":148,"records written including a stop durable timestamp":0,"records written including a stop timestamp":0,"records written including a stop transaction ID":0,"split bytes currently awaiting free":0,"split objects currently awaiting free":0},"session":{"open session count":14,"session query timestamp calls":0,"table alter failed calls":0,"table alter successful calls":0,"table alter unchanged and skipped":0,"table compact failed calls":0,"table compact successful calls":0,"table create failed calls":0,"table create successful calls":1,"table drop failed calls":0,"table drop successful calls":0,"table rename failed calls":0,"table rename successful calls":0,"table salvage failed calls":0,"table salvage successful calls":0,"table truncate failed calls":0,"table truncate successful calls":0,"table verify failed calls":0,"table verify successful calls":0},"snapshot-window-settings":{"cache pressure percentage threshold":95,"current available snapshots window size in seconds":0,"current cache pressure percentage":0,"latest majority snapshot timestamp available":"Jan 1 00:00:00:0","max target available snapshots window size in seconds":5,"oldest majority snapshot timestamp available":"Jan 1 00:00:00:0","target available snapshots window size in seconds":5,"total number of SnapshotTooOld errors":0},"thread-state":{"active filesystem fsync calls":0,"active filesystem read calls":0,"active filesystem write calls":0},"thread-yield":{"application thread time evicting (usecs)":0,"application thread time waiting for cache (usecs)":0,"connection close blocked waiting for transaction state stabilization":0,"connection close yielded for lsm manager shutdown":0,"data handle lock yielded":0,"get reference for page index and slot time sleeping (usecs)":0,"log server sync yielded for log write":0,"page access yielded due to prepare state change":0,"page acquire busy blocked":0,"page acquire eviction blocked":0,"page acquire locked blocked":0,"page acquire read blocked":0,"page acquire time sleeping (usecs)":0,"page delete rollback time sleeping for state change (usecs)":0,"page reconciliation yielded due to child modification":0},"transaction":{"Number of prepared updates":0,"durable timestamp queue entries walked":0,"durable timestamp queue insert to empty":0,"durable timestamp queue inserts to head":0,"durable timestamp queue inserts total":0,"durable timestamp queue length":0,"prepared transactions":0,"prepared transactions committed":0,"prepared transactions currently active":0,"prepared transactions rolled back":0,"query timestamp calls":4301,"race to read prepared update retry":0,"read timestamp queue entries walked":0,"read timestamp queue insert to empty":0,"read timestamp queue inserts to head":0,"read timestamp queue inserts total":0,"read timestamp queue length":0,"rollback to stable calls":0,"rollback to stable hs records with stop timestamps older than newer records":0,"rollback to stable keys removed":0,"rollback to stable keys restored":0,"rollback to stable pages visited":1,"rollback to stable restored tombstones from history store":0,"rollback to stable sweeping history store keys":0,"rollback to stable tree walk skipping pages":0,"rollback to stable updates aborted":0,"rollback to stable updates removed from history store":0,"set timestamp calls":0,"set timestamp durable calls":0,"set timestamp durable updates":0,"set timestamp oldest calls":0,"set timestamp oldest updates":0,"set timestamp stable calls":0,"set timestamp stable updates":0,"transaction begins":180,"transaction checkpoint currently running":0,"transaction checkpoint generation":73,"transaction checkpoint history store file duration (usecs)":2,"transaction checkpoint max time (msecs)":45,"transaction checkpoint min time (msecs)":8,"transaction checkpoint most recent duration for gathering all handles (usecs)":356,"transaction checkpoint most recent duration for gathering applied handles (usecs)":147,"transaction checkpoint most recent duration for gathering skipped handles (usecs)":33,"transaction checkpoint most recent handles applied":1,"transaction checkpoint most recent handles skipped":9,"transaction checkpoint most recent handles walked":20,"transaction checkpoint most recent time (msecs)":12,"transaction checkpoint prepare currently running":0,"transaction checkpoint prepare max time (msecs)":1,"transaction checkpoint prepare min time (msecs)":0,"transaction checkpoint prepare most recent time (msecs)":0,"transaction checkpoint prepare total time (msecs)":1,"transaction checkpoint scrub dirty target":0,"transaction checkpoint scrub time (msecs)":0,"transaction checkpoint total time (msecs)":1373,"transaction checkpoints":72,"transaction checkpoints skipped because database was clean":0,"transaction failures due to history store":0,"transaction fsync calls for checkpoint after allocating the transaction ID":72,"transaction fsync duration for checkpoint after allocating the transaction ID (usecs)":4259,"transaction range of IDs currently pinned":0,"transaction range of IDs currently pinned by a checkpoint":0,"transaction range of timestamps currently pinned":0,"transaction range of timestamps pinned by a checkpoint":0,"transaction range of timestamps pinned by the oldest active read timestamp":0,"transaction range of timestamps pinned by the oldest timestamp":0,"transaction read timestamp of the oldest active reader":0,"transaction sync calls":0,"transactions committed":2,"transactions rolled back":178,"update conflicts":0},"uri":"statistics:"}} diff --git a/src/go/plugins/mongodb/testdata/top.json b/src/go/plugins/mongodb/testdata/top.json new file mode 100644 index 00000000000..ec30dbda42f --- /dev/null +++ b/src/go/plugins/mongodb/testdata/top.json @@ -0,0 +1 @@ +{"ok":1,"totals":{"admin.system.version":{"commands":{"count":0,"time":0},"getmore":{"count":0,"time":0},"insert":{"count":0,"time":0},"queries":{"count":0,"time":0},"readLock":{"count":1,"time":162},"remove":{"count":0,"time":0},"total":{"count":1,"time":162},"update":{"count":0,"time":0},"writeLock":{"count":0,"time":0}},"note":"all times in microseconds"}} diff --git a/src/go/plugins/plugins_darwin.go b/src/go/plugins/plugins_darwin.go index 32199d66c4d..f546922e7b5 100644 --- a/src/go/plugins/plugins_darwin.go +++ b/src/go/plugins/plugins_darwin.go @@ -25,6 +25,7 @@ import ( _ "zabbix.com/plugins/log" _ "zabbix.com/plugins/memcached" _ "zabbix.com/plugins/modbus" + _ "zabbix.com/plugins/mongodb" _ "zabbix.com/plugins/mysql" _ "zabbix.com/plugins/net/tcp" _ "zabbix.com/plugins/oracle" diff --git a/src/go/plugins/plugins_linux.go b/src/go/plugins/plugins_linux.go index ad750bbf5dc..59b6bfa2093 100644 --- a/src/go/plugins/plugins_linux.go +++ b/src/go/plugins/plugins_linux.go @@ -26,6 +26,7 @@ import ( _ "zabbix.com/plugins/log" _ "zabbix.com/plugins/memcached" _ "zabbix.com/plugins/modbus" + _ "zabbix.com/plugins/mongodb" _ "zabbix.com/plugins/mqtt" _ "zabbix.com/plugins/mysql" _ "zabbix.com/plugins/net/netif" diff --git a/src/go/plugins/plugins_windows.go b/src/go/plugins/plugins_windows.go index 61d352f6fcd..b8a0143492f 100644 --- a/src/go/plugins/plugins_windows.go +++ b/src/go/plugins/plugins_windows.go @@ -24,6 +24,7 @@ import ( _ "zabbix.com/plugins/log" _ "zabbix.com/plugins/memcached" _ "zabbix.com/plugins/modbus" + _ "zabbix.com/plugins/mongodb" _ "zabbix.com/plugins/mqtt" _ "zabbix.com/plugins/mysql" _ "zabbix.com/plugins/net/netif" |