diff --git a/src/go/pt-mongodb-summary/README.rst b/src/go/pt-mongodb-summary/README.rst index 1bfc4041f..151127280 100644 --- a/src/go/pt-mongodb-summary/README.rst +++ b/src/go/pt-mongodb-summary/README.rst @@ -59,6 +59,12 @@ Output example .. code-block:: none + # Mongos ################################################################################################# + Host LastPing Version Uptime (sec) + my-cluster-name-mongos-0:27017 2026-02-16T13:01:22Z 8.0.17-6 3553 + my-cluster-name-mongos-1:27017 2026-02-16T13:01:26Z 8.0.17-6 3543 + my-cluster-name-mongos-2:27017 2026-02-16T13:01:28Z 8.0.17-6 3533 + # Instances #################################################################################### ID Host Type ReplSet 0 localhost:17001 PRIMARY r1 diff --git a/src/go/pt-mongodb-summary/main.go b/src/go/pt-mongodb-summary/main.go index 66bff3343..9c7a1345a 100644 --- a/src/go/pt-mongodb-summary/main.go +++ b/src/go/pt-mongodb-summary/main.go @@ -15,6 +15,7 @@ package main import ( "bytes" + "cmp" "context" "crypto/tls" "crypto/x509" @@ -26,6 +27,7 @@ import ( "os" "os/user" "path/filepath" + "slices" "strings" "time" @@ -35,6 +37,7 @@ import ( "github.com/pkg/errors" "github.com/shirou/gopsutil/process" log "github.com/sirupsen/logrus" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -159,6 +162,29 @@ type clusterwideInfo struct { Chunks []proto.ChunksByCollection } +type mongosInstance struct { + Name string `bson:"_id"` + LastPing time.Time `bson:"ping"` + UpTime int `bson:"up"` + Version string `bson:"mongoVersion"` +} + +type mongosInfo struct { + Instances []mongosInstance `bson:"instances"` +} + +func (t mongosInfo) MaxNameLen() int { + if len(t.Instances) == 0 { + return 0 + } + + maxInst := slices.MaxFunc(t.Instances, func(a, b mongosInstance) int { + return cmp.Compare(len(a.Name), len(b.Name)) + }) + + return len(maxInst.Name) +} + type cliOptions struct { Host string User string @@ -184,6 +210,7 @@ type collectedInfo struct { RunningOps *opCounters SecuritySettings *security HostInfo *hostInfo + MongosInfo *mongosInfo Errors []string } @@ -256,6 +283,11 @@ func main() { ci := &collectedInfo{} + ci.MongosInfo, err = getMongosInfo(ctx, client) + if err != nil { + log.Warnf("[Warning] cannot get mongos info: %v\n", err) + } + ci.HostInfo, err = getHostInfo(ctx, client) if err != nil { log.Errorf("Cannot get host info for %q: %s", opts.Host, err) @@ -263,7 +295,7 @@ func main() { } if ci.ReplicaMembers, err = util.GetReplicasetMembers(ctx, clientOptions); err != nil { - log.Warnf("[Error] cannot get replicaset members: %v\n", err) + log.Warnf("[Warning] cannot get replicaset members: %v\n", err) } log.Debugf("replicaMembers:\n%+v\n", ci.ReplicaMembers) @@ -332,7 +364,12 @@ func formatResults(ci *collectedInfo, format string) ([]byte, error) { default: buf = new(bytes.Buffer) - t := template.Must(template.New("replicas").Parse(templates.Replicas)) + t := template.Must(template.New("mongos").Parse(templates.MongosInfo)) + if err := t.Execute(buf, ci.MongosInfo); err != nil { + return nil, errors.Wrap(err, "cannot parse mongos section of the output template") + } + + t = template.Must(template.New("replicas").Parse(templates.Replicas)) if err := t.Execute(buf, ci.ReplicaMembers); err != nil { return nil, errors.Wrap(err, "cannot parse replicas section of the output template") } @@ -454,6 +491,29 @@ func countMongodProcesses() (int, error) { return count, nil } +func getMongosInfo(ctx context.Context, client *mongo.Client) (*mongosInfo, error) { + threshold := time.Now().Add(-300 * time.Second) + + filter := bson.M{ + "ping": bson.M{ + "$gt": threshold, + }, + } + + cursor, err := client.Database("config").Collection("mongos").Find(ctx, filter) + if err != nil { + return nil, fmt.Errorf("failed to find mongos: %w", err) + } + defer cursor.Close(ctx) + + var instances []mongosInstance + if err := cursor.All(ctx, &instances); err != nil { + return nil, fmt.Errorf("failed to decode mongos: %w", err) + } + + return &mongosInfo{Instances: instances}, nil +} + func getClusterwideInfo(ctx context.Context, client *mongo.Client) (*clusterwideInfo, error) { var databases databases diff --git a/src/go/pt-mongodb-summary/main_test.go b/src/go/pt-mongodb-summary/main_test.go index 0f1a9e69e..1e4b485ff 100644 --- a/src/go/pt-mongodb-summary/main_test.go +++ b/src/go/pt-mongodb-summary/main_test.go @@ -151,3 +151,23 @@ func TestParseArgs(t *testing.T) { os.Stdout = old } + +func TestGetMongosInfo(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + client, err := tu.TestClient(ctx, tu.MongoDBMongosPort) + require.NoError(t, err) + + info, err := getMongosInfo(ctx, client) + require.NoError(t, err) + require.NotNil(t, info) + require.NotEmpty(t, info.Instances) + + for _, m := range info.Instances { + require.NotEmpty(t, m.Name) + require.NotEmpty(t, m.Version) + require.NotEqual(t, 0, m.UpTime) + require.False(t, m.LastPing.IsZero()) + } +} diff --git a/src/go/pt-mongodb-summary/templates/mongos.go b/src/go/pt-mongodb-summary/templates/mongos.go new file mode 100644 index 000000000..896f09f43 --- /dev/null +++ b/src/go/pt-mongodb-summary/templates/mongos.go @@ -0,0 +1,37 @@ +// This program is copyright 2016-2026 Percona LLC and/or its affiliates. +// +// THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +// +// 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, version 2. +// +// You should have received a copy of the GNU General Public License, version 2 +// along with this program; if not, see . + +package templates + +const MongosInfo = ` +# Mongos ################################################################################################# +{{ "" }} +{{- $padding := " " -}} +{{- $timeWidth := 20 -}} +{{- $hostWidth := .MaxNameLen -}} +{{- $versionWidth := 15 -}} + +{{ printf "%-*s" $hostWidth "Host" }}{{ $padding }} +{{- printf "%-*s" $timeWidth "LastPing" }}{{ $padding }} +{{- printf "%-*s" $versionWidth "Version" }}{{ $padding }}Uptime (sec) +{{ if .Instances -}} +{{- range .Instances -}} +{{ printf "%-*s" $hostWidth .Name }}{{ $padding }} +{{- printf "%-*s" $timeWidth (.LastPing.Format "2006-01-02T15:04:05Z07:00") }}{{ $padding }} +{{- printf "%-*s" $versionWidth .Version }}{{ $padding }} +{{- printf "%-15d" .UpTime }} +{{ end }} +{{- else -}} + no mongos instances found +{{- end }} +`