package mongodb import ( "context" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" "bugs.watch/agent/internal/aggregate" ) // Collector queries MongoDB serverStatus and listDatabases each sample interval. // All queries run under a 3-second timeout; any failure is silently skipped. // Metrics mapping: // db_conn_pct → current / (current + available) * 100 // db_threads_running → connections.current // db_size_bytes → totalSize from listDatabases type Collector struct { client *mongo.Client } func New(uri string) (*Collector, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri). SetConnectTimeout(3*time.Second). SetServerSelectionTimeout(3*time.Second)) if err != nil { return nil, err } return &Collector{client: client}, nil } func (c *Collector) Name() string { return "mongodb" } func (c *Collector) Close() { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() c.client.Disconnect(ctx) } func (c *Collector) Collect(window *aggregate.Window) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() admin := c.client.Database("admin") var status bson.M if err := admin.RunCommand(ctx, bson.D{ {Key: "serverStatus", Value: 1}, {Key: "repl", Value: 0}, {Key: "metrics", Value: 0}, {Key: "locks", Value: 0}, }).Decode(&status); err != nil { return } if conns, ok := status["connections"].(bson.M); ok { current := toFloat64(conns["current"]) available := toFloat64(conns["available"]) total := current + available if total > 0 { window.AddDBConnPct(current / total * 100) } if current > 0 { window.AddDBThreadsRunning(current) } } var listResult bson.M if err := admin.RunCommand(ctx, bson.D{ {Key: "listDatabases", Value: 1}, {Key: "nameOnly", Value: false}, }).Decode(&listResult); err == nil { if totalSize := toFloat64(listResult["totalSize"]); totalSize > 0 { window.SetDBSizeBytes(uint64(totalSize)) } } } func toFloat64(v interface{}) float64 { switch x := v.(type) { case int32: return float64(x) case int64: return float64(x) case float64: return x } return 0 }