Merge branch '3.x' into PT-2448-pt-k8s-debug-collector-refactoring

This commit is contained in:
Vladyslav Yurchenko
2026-03-04 14:04:16 +02:00
8 changed files with 144 additions and 5 deletions
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
- name: Build the Docker image
run: echo "FROM oraclelinux:9-slim" > Dockerfile; echo "RUN microdnf -y update" >> Dockerfile; echo "COPY bin/* /usr/bin/" >> Dockerfile; docker build . --file Dockerfile --tag percona-toolkit:${{ github.sha }}
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.33.1
uses: aquasecurity/trivy-action@0.34.0
with:
image-ref: 'percona-toolkit:${{ github.sha }}'
format: 'table'
@@ -36,7 +36,7 @@ jobs:
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
- name: Upload a Build Artifact
uses: actions/upload-artifact@v6.0.0
uses: actions/upload-artifact@v7.0.0
with:
name: binaries
path: bin/*
+1
View File
@@ -62,6 +62,7 @@ extend-ignore-re = [
"RegexISTReceiver" = "RegexISTReceiver"
"RegexISTSender" = "RegexISTSender"
"RegexXtrabackupISTReceived" = "RegexXtrabackupISTReceived"
"SELECTs" = "SELECTs"
"START_ND_SUMMARY" = "START_ND_SUMMARY"
"START_ND_TOOLTIPS" = "START_ND_TOOLTIPS"
"thr" = "thr" # common abbreviation
+2 -1
View File
@@ -105,7 +105,8 @@ func main() {
resp.Unused = findUnused(ctx, client, args.Databases, args.Collections)
resp.Duplicated = findDuplicated(ctx, client, args.Databases, args.Collections)
default:
kong.DefaultHelpPrinter(kong.HelpOptions{}, kongctx)
kongctx.PrintUsage(false)
return
}
fmt.Println(output(resp, args.JSON))
@@ -16,6 +16,7 @@ package main
import (
"os/exec"
"regexp"
"strings"
"testing"
)
@@ -33,3 +34,16 @@ func TestVersionOption(t *testing.T) {
t.Errorf("%s --version returns wrong result:\n%s", toolname, out)
}
}
func TestNoCommand(t *testing.T) {
mockMongo := "mongodb://127.0.0.1:27017"
out, err := exec.Command("../../../bin/"+toolname, "--mongodb.uri", mockMongo).Output()
if err != nil {
t.Errorf("error executing %s with no command: %s", toolname, err.Error())
}
want := "Usage: pt-mongodb-index-check show-help"
if !strings.Contains(string(out), want) {
t.Errorf("Output missmatch. Output %q should contain %q", string(out), want)
}
}
+6
View File
@@ -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
+62 -2
View File
@@ -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
+20
View File
@@ -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())
}
}
@@ -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 }}
`