github.com/netdata/go.d.plugin@v0.58.1/modules/mongodb/client.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package mongo 4 5 import ( 6 "context" 7 "fmt" 8 "time" 9 10 "go.mongodb.org/mongo-driver/bson" 11 "go.mongodb.org/mongo-driver/mongo" 12 "go.mongodb.org/mongo-driver/mongo/options" 13 ) 14 15 const ( 16 mongos = "mongos" 17 ) 18 19 type mongoConn interface { 20 serverStatus() (*documentServerStatus, error) 21 listDatabaseNames() ([]string, error) 22 dbStats(name string) (*documentDBStats, error) 23 isReplicaSet() bool 24 isMongos() bool 25 replSetGetStatus() (*documentReplSetStatus, error) 26 shardNodes() (*documentShardNodesResult, error) 27 shardDatabasesPartitioning() (*documentPartitionedResult, error) 28 shardCollectionsPartitioning() (*documentPartitionedResult, error) 29 shardChunks() (map[string]int64, error) 30 initClient(uri string, timeout time.Duration) error 31 close() error 32 } 33 34 type mongoClient struct { 35 client *mongo.Client 36 timeout time.Duration 37 replicaSetFlag *bool 38 mongosFlag *bool 39 } 40 41 func (c *mongoClient) serverStatus() (*documentServerStatus, error) { 42 ctx, cancel := context.WithTimeout(context.Background(), time.Second*c.timeout) 43 defer cancel() 44 45 cmd := bson.D{ 46 {Key: "serverStatus", Value: 1}, 47 {Key: "repl", Value: 1}, 48 {Key: "metrics", 49 Value: bson.D{ 50 {Key: "document", Value: true}, 51 {Key: "cursor", Value: true}, 52 {Key: "queryExecutor", Value: true}, 53 {Key: "apiVersions", Value: false}, 54 {Key: "aggStageCounters", Value: false}, 55 {Key: "commands", Value: false}, 56 {Key: "dotsAndDollarsFields", Value: false}, 57 {Key: "getLastError", Value: false}, 58 {Key: "mongos", Value: false}, 59 {Key: "operation", Value: false}, 60 {Key: "operatorCounters", Value: false}, 61 {Key: "query", Value: false}, 62 {Key: "record", Value: false}, 63 {Key: "repl", Value: false}, 64 {Key: "storage", Value: false}, 65 {Key: "ttl", Value: false}, 66 }, 67 }, 68 } 69 var status *documentServerStatus 70 71 err := c.client.Database("admin").RunCommand(ctx, cmd).Decode(&status) 72 if err != nil { 73 return nil, err 74 } 75 76 isReplSet := status.Repl != nil 77 c.replicaSetFlag = &isReplSet 78 79 isMongos := status.Process == mongos 80 c.mongosFlag = &isMongos 81 82 return status, err 83 } 84 85 func (c *mongoClient) listDatabaseNames() ([]string, error) { 86 ctx, cancel := context.WithTimeout(context.Background(), time.Second*c.timeout) 87 defer cancel() 88 89 return c.client.ListDatabaseNames(ctx, bson.M{}) 90 } 91 92 func (c *mongoClient) dbStats(name string) (*documentDBStats, error) { 93 ctx, cancel := context.WithTimeout(context.Background(), time.Second*c.timeout) 94 defer cancel() 95 96 cmd := bson.M{"dbStats": 1} 97 var stats documentDBStats 98 99 if err := c.client.Database(name).RunCommand(ctx, cmd).Decode(&stats); err != nil { 100 return nil, err 101 } 102 103 return &stats, nil 104 } 105 106 func (c *mongoClient) isReplicaSet() bool { 107 if c.replicaSetFlag != nil { 108 return *c.replicaSetFlag 109 } 110 111 status, err := c.serverStatus() 112 if err != nil { 113 return false 114 } 115 116 return status.Repl != nil 117 } 118 119 func (c *mongoClient) isMongos() bool { 120 if c.mongosFlag != nil { 121 return *c.mongosFlag 122 } 123 124 status, err := c.serverStatus() 125 if err != nil { 126 return false 127 } 128 129 return status.Process == mongos 130 } 131 132 func (c *mongoClient) replSetGetStatus() (*documentReplSetStatus, error) { 133 ctx, cancel := context.WithTimeout(context.Background(), time.Second*c.timeout) 134 defer cancel() 135 136 var status *documentReplSetStatus 137 cmd := bson.M{"replSetGetStatus": 1} 138 139 err := c.client.Database("admin").RunCommand(ctx, cmd).Decode(&status) 140 if err != nil { 141 return nil, err 142 } 143 144 return status, err 145 } 146 147 func (c *mongoClient) shardNodes() (*documentShardNodesResult, error) { 148 collection := "shards" 149 groupStage := bson.D{{Key: "$sortByCount", Value: "$state"}} 150 151 nodesByState, err := c.shardCollectAggregation(collection, []bson.D{groupStage}) 152 if err != nil { 153 return nil, err 154 } 155 156 return &documentShardNodesResult{nodesByState.True, nodesByState.False}, nil 157 } 158 159 func (c *mongoClient) shardDatabasesPartitioning() (*documentPartitionedResult, error) { 160 collection := "databases" 161 groupStage := bson.D{{Key: "$sortByCount", Value: "$partitioned"}} 162 163 partitioning, err := c.shardCollectAggregation(collection, []bson.D{groupStage}) 164 if err != nil { 165 return nil, err 166 } 167 168 return &documentPartitionedResult{partitioning.True, partitioning.False}, nil 169 } 170 171 func (c *mongoClient) shardCollectionsPartitioning() (*documentPartitionedResult, error) { 172 collection := "collections" 173 matchStage := bson.D{{Key: "$match", Value: bson.D{{Key: "dropped", Value: false}}}} 174 countStage := bson.D{{Key: "$sortByCount", Value: bson.D{{Key: "$eq", Value: bson.A{"$distributionMode", "sharded"}}}}} 175 176 partitioning, err := c.shardCollectAggregation(collection, []bson.D{matchStage, countStage}) 177 if err != nil { 178 return nil, err 179 } 180 181 return &documentPartitionedResult{partitioning.True, partitioning.False}, nil 182 } 183 184 func (c *mongoClient) shardCollectAggregation(collection string, aggr []bson.D) (*documentAggrResult, error) { 185 rows, err := c.dbAggregate(collection, aggr) 186 if err != nil { 187 return nil, err 188 } 189 190 result := &documentAggrResult{} 191 192 for _, row := range rows { 193 if row.Bool { 194 result.True = row.Count 195 } else { 196 result.False = row.Count 197 } 198 } 199 200 return result, err 201 } 202 203 func (c *mongoClient) shardChunks() (map[string]int64, error) { 204 ctx, cancel := context.WithTimeout(context.Background(), time.Second*c.timeout) 205 defer cancel() 206 207 col := c.client.Database("config").Collection("chunks") 208 209 cursor, err := col.Aggregate(ctx, mongo.Pipeline{bson.D{{Key: "$sortByCount", Value: "$shard"}}}) 210 if err != nil { 211 return nil, err 212 } 213 214 var shards []bson.M 215 if err = cursor.All(ctx, &shards); err != nil { 216 return nil, err 217 } 218 219 defer func() { _ = cursor.Close(ctx) }() 220 221 result := map[string]int64{} 222 223 for _, row := range shards { 224 k, ok := row["_id"].(string) 225 if !ok { 226 return nil, fmt.Errorf("shard name is not a string: %v", row["_id"]) 227 } 228 v, ok := row["count"].(int32) 229 if !ok { 230 return nil, fmt.Errorf("shard chunk count is not a int32: %v", row["count"]) 231 } 232 result[k] = int64(v) 233 } 234 235 return result, err 236 } 237 238 func (c *mongoClient) initClient(uri string, timeout time.Duration) error { 239 if c.client != nil { 240 return nil 241 } 242 243 c.timeout = timeout 244 245 client, err := mongo.NewClient(options.Client().ApplyURI(uri)) 246 if err != nil { 247 return err 248 } 249 250 ctxConn, cancelConn := context.WithTimeout(context.Background(), c.timeout*time.Second) 251 defer cancelConn() 252 253 if err := client.Connect(ctxConn); err != nil { 254 return err 255 } 256 257 ctxPing, cancelPing := context.WithTimeout(context.Background(), c.timeout*time.Second) 258 defer cancelPing() 259 260 if err := client.Ping(ctxPing, nil); err != nil { 261 return err 262 } 263 264 c.client = client 265 266 return nil 267 } 268 269 func (c *mongoClient) close() error { 270 if c.client == nil { 271 return nil 272 } 273 274 ctx, cancel := context.WithTimeout(context.Background(), c.timeout*time.Second) 275 defer cancel() 276 277 if err := c.client.Disconnect(ctx); err != nil { 278 return err 279 } 280 281 c.client = nil 282 283 return nil 284 } 285 286 func (c *mongoClient) dbAggregate(collection string, aggr []bson.D) ([]documentAggrResults, error) { 287 ctx, cancel := context.WithTimeout(context.Background(), time.Second*c.timeout) 288 defer cancel() 289 290 cursor, err := c.client.Database("config").Collection(collection).Aggregate(ctx, aggr) 291 if err != nil { 292 return nil, err 293 } 294 295 defer func() { _ = cursor.Close(ctx) }() 296 297 var rows []documentAggrResults 298 if err := cursor.All(ctx, &rows); err != nil { 299 return nil, err 300 } 301 302 return rows, nil 303 }