Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/go/pt-mongodb-summary/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 62 additions & 2 deletions src/go/pt-mongodb-summary/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package main

import (
"bytes"
"cmp"
"context"
"crypto/tls"
"crypto/x509"
Expand All @@ -26,6 +27,7 @@ import (
"os"
"os/user"
"path/filepath"
"slices"
"strings"
"time"

Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -184,6 +210,7 @@ type collectedInfo struct {
RunningOps *opCounters
SecuritySettings *security
HostInfo *hostInfo
MongosInfo *mongosInfo
Errors []string
}

Expand Down Expand Up @@ -256,14 +283,19 @@ 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)
os.Exit(cannotGetHostInfo) //nolint:gocritic
}

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)
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions src/go/pt-mongodb-summary/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
37 changes: 37 additions & 0 deletions src/go/pt-mongodb-summary/templates/mongos.go
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

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 }}
`
Loading