Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/zabbix/zabbix.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog.d/feature/ZBXNEXT-64081
-rw-r--r--src/go/conf/zabbix_agent2.conf90
-rw-r--r--src/go/conf/zabbix_agent2.win.conf42
-rw-r--r--src/go/go.mod1
-rw-r--r--src/go/go.sum4
-rw-r--r--src/go/plugins/mongodb/README.md143
-rw-r--r--src/go/plugins/mongodb/config.go57
-rw-r--r--src/go/plugins/mongodb/conn.go293
-rw-r--r--src/go/plugins/mongodb/handler_collection_stats.go54
-rw-r--r--src/go/plugins/mongodb/handler_collection_stats_test.go84
-rw-r--r--src/go/plugins/mongodb/handler_collections_discovery.go69
-rw-r--r--src/go/plugins/mongodb/handler_collections_discovery_test.go78
-rw-r--r--src/go/plugins/mongodb/handler_collections_usage.go55
-rw-r--r--src/go/plugins/mongodb/handler_collections_usage_test.go70
-rw-r--r--src/go/plugins/mongodb/handler_config_discovery.go96
-rw-r--r--src/go/plugins/mongodb/handler_connPool_stats.go54
-rw-r--r--src/go/plugins/mongodb/handler_connPool_stats_test.go90
-rw-r--r--src/go/plugins/mongodb/handler_database_stats.go54
-rw-r--r--src/go/plugins/mongodb/handler_database_stats_test.go91
-rw-r--r--src/go/plugins/mongodb/handler_databases_discovery.go55
-rw-r--r--src/go/plugins/mongodb/handler_databases_discovery_test.go60
-rw-r--r--src/go/plugins/mongodb/handler_jumbo_chunks.go36
-rw-r--r--src/go/plugins/mongodb/handler_oplog_stats.go92
-rw-r--r--src/go/plugins/mongodb/handler_oplog_stats_test.go95
-rw-r--r--src/go/plugins/mongodb/handler_ping.go35
-rw-r--r--src/go/plugins/mongodb/handler_replset_config.go58
-rw-r--r--src/go/plugins/mongodb/handler_replset_config_test.go71
-rw-r--r--src/go/plugins/mongodb/handler_replset_status.go162
-rw-r--r--src/go/plugins/mongodb/handler_server_status.go58
-rw-r--r--src/go/plugins/mongodb/handler_server_status_test.go71
-rw-r--r--src/go/plugins/mongodb/handler_shards_discovery.go87
-rw-r--r--src/go/plugins/mongodb/metrics.go159
-rw-r--r--src/go/plugins/mongodb/mockconn.go190
-rw-r--r--src/go/plugins/mongodb/mongodb.go114
-rw-r--r--src/go/plugins/mongodb/testdata/collStats.json1
-rw-r--r--src/go/plugins/mongodb/testdata/connPoolStats.json1
-rw-r--r--src/go/plugins/mongodb/testdata/dbStats.json1
-rw-r--r--src/go/plugins/mongodb/testdata/replSetGetConfig.json1
-rw-r--r--src/go/plugins/mongodb/testdata/serverStatus.json1
-rw-r--r--src/go/plugins/mongodb/testdata/top.json1
-rw-r--r--src/go/plugins/plugins_darwin.go1
-rw-r--r--src/go/plugins/plugins_linux.go1
-rw-r--r--src/go/plugins/plugins_windows.go1
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"