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  }