Files
percona-toolkit/src/go/pt-mongodb-index-check/main.go
Sveta Smirnova 156372fb73 PT-2235 - pt-mongodb-index-check does not support option --version (#653)
* PT-2235 - pt-mongodb-index-check does not support option --version

Changed command version to flag version

* PT-2235 - pt-mongodb-index-check does not support option --version

Updated documentation
2023-08-01 17:56:17 +03:00

184 lines
5.4 KiB
Go

package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"text/template"
"time"
"github.com/alecthomas/kong"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/percona/percona-toolkit/src/go/pt-mongodb-index-check/indexes"
"github.com/percona/percona-toolkit/src/go/pt-mongodb-index-check/templates"
)
type cmdlineArgs struct {
CheckUnused struct{} `cmd:"" name:"check-unused" help:"Check for unused indexes."`
CheckDuplicated struct{} `cmd:"" name:"check-duplicates" help:"Check for duplicated indexes."`
CheckAll struct{} `cmd:"" name:"check-all" help:"Check for unused and duplicated indexes."`
ShowHelp struct{} `cmd:"" default:"1"`
Version kong.VersionFlag
AllDatabases bool `name:"all-databases" xor:"db" help:"Check in all databases excluding system dbs"`
Databases []string `name:"databases" xor:"db" help:"Comma separated list of databases to check"`
AllCollections bool `name:"all-collections" xor:"colls" help:"Check in all collections in the selected databases."`
Collections []string `name:"collections" xor:"colls" help:"Comma separated list of collections to check"`
URI string `name:"mongodb.uri" required:"" placeholder:"mongodb://host:port/admindb?options" help:"Connection URI"`
JSON bool `name:"json" help:"Show output as JSON"`
}
type response struct {
Unused []indexes.IndexStat
Duplicated []indexes.Duplicate
}
const (
toolname = "pt-mongodb-index-check"
)
// We do not set anything here, these variables are defined by the Makefile
var (
Build string //nolint
GoVersion string //nolint
Version string //nolint
Commit string //nolint
)
func main() {
var args cmdlineArgs
kongctx := kong.Parse(&args, kong.UsageOnError(),
kong.Vars{"version": fmt.Sprintf("%s\nVersion %s\nBuild: %s using %s\nCommit: %s",
toolname, Version, Build, GoVersion, Commit)})
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
if !strings.HasPrefix(args.URI, "mongodb") && !strings.HasPrefix(args.URI, "mongodb+srv") {
args.URI = "mongodb://" + args.URI
}
client, err := mongo.Connect(ctx, options.Client().ApplyURI(args.URI))
if err != nil {
log.Fatalf("Cannot connect to the database: %q", err)
}
if args.AllDatabases {
args.Databases, err = client.ListDatabaseNames(context.TODO(), primitive.D{})
if err != nil {
log.Fatalf("cannot list all databases: %s", err)
}
}
if args.AllCollections {
args.Collections = nil
}
resp := response{}
switch kongctx.Command() {
case "check-unused":
resp.Unused = findUnused(ctx, client, args.Databases, args.Collections)
case "check-duplicates":
resp.Duplicated = findDuplicated(ctx, client, args.Databases, args.Collections)
case "check-all":
resp.Unused = findUnused(ctx, client, args.Databases, args.Collections)
resp.Duplicated = findDuplicated(ctx, client, args.Databases, args.Collections)
default:
kong.DefaultHelpPrinter(kong.HelpOptions{}, kongctx)
}
fmt.Println(output(resp, args.JSON))
}
func output(resp response, asJson bool) string {
if asJson {
jsonStr, err := json.MarshalIndent(resp, "", "\t")
if err != nil {
log.Fatal("cannot encode the response as json")
}
return string(jsonStr)
}
buf := new(bytes.Buffer)
t := template.Must(template.New("duplicated").Parse(templates.Duplicated))
if err := t.Execute(buf, resp.Duplicated); err != nil {
log.Fatal(errors.Wrap(err, "cannot parse clusterwide section of the output template"))
}
t = template.Must(template.New("unused").Parse(templates.Unused))
if err := t.Execute(buf, resp.Unused); err != nil {
log.Fatal(errors.Wrap(err, "cannot parse clusterwide section of the output template"))
}
return buf.String()
}
func findUnused(ctx context.Context, client *mongo.Client, databases []string, collections []string) []indexes.IndexStat {
unused := []indexes.IndexStat{}
var err error
colls := make([]string, len(collections))
copy(colls, collections)
for _, database := range databases {
if len(collections) == 0 {
colls, err = client.Database(database).ListCollectionNames(ctx, primitive.D{})
if err != nil {
log.Errorf("cannot get the list of collections for the database %s", database)
continue
}
}
for _, collection := range colls {
idx, err := indexes.FindUnused(ctx, client, database, collection)
if err != nil {
log.Errorf("error while checking unused indexes in %s.%s: %s", database, collection, err)
continue
}
unused = append(unused, idx...)
}
}
return unused
}
func findDuplicated(ctx context.Context, client *mongo.Client, databases []string, collections []string) []indexes.Duplicate {
duplicated := []indexes.Duplicate{}
var err error
colls := make([]string, len(collections))
copy(colls, collections)
for _, database := range databases {
if len(collections) == 0 {
colls, err = client.Database(database).ListCollectionNames(ctx, primitive.D{})
if err != nil {
log.Errorf("cannot get the list of collections for the database %s", database)
continue
}
}
for _, collection := range colls {
dups, err := indexes.FindDuplicated(ctx, client, database, collection)
if err != nil {
log.Errorf("error while checking duplicated indexes in %s.%s: %s", database, collection, err)
continue
}
duplicated = append(duplicated, dups...)
}
}
return duplicated
}