Files
percona-toolkit/src/go/pt-galera-log-explainer/conflicts.go
Sveta Smirnova a62d1b2ba9 Update copyright year to 2026
- Updated copyright years for Go tools
2026-01-31 03:59:46 +03:00

112 lines
3.0 KiB
Go

// This program is copyright 2023-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 main
import (
"encoding/json"
"fmt"
"strings"
"github.com/percona/percona-toolkit/src/go/pt-galera-log-explainer/regex"
"github.com/percona/percona-toolkit/src/go/pt-galera-log-explainer/utils"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v2"
)
type conflicts struct {
Paths []string `arg:"" name:"paths" help:"paths of the log to use"`
Yaml bool `xor:"format"`
Json bool `xor:"format"`
}
func (c *conflicts) Help() string {
return "Summarize every replication conflicts, from every node's point of view"
}
func (c *conflicts) Run() error {
regexes := regex.IdentsMap.Merge(regex.ApplicativeMap)
timeline, err := timelineFromPaths(c.Paths, regexes)
if err != nil {
return err
}
logCtxs := timeline.GetLatestContextsByNodes()
for _, logCtx := range logCtxs {
if len(logCtx.Conflicts) == 0 {
continue
}
var out string
switch {
case c.Yaml:
tmp, err := yaml.Marshal(logCtx.Conflicts)
if err != nil {
return err
}
out = string(tmp)
case c.Json:
tmp, err := json.Marshal(logCtx.Conflicts)
if err != nil {
return err
}
out = string(tmp)
default:
var b strings.Builder
for _, conflict := range logCtx.Conflicts {
b.WriteString("\n\n")
b.WriteString(utils.Paint(utils.BlueText, "seqno: "))
b.WriteString(conflict.Seqno)
b.WriteString("\n\t")
b.WriteString(utils.Paint(utils.BlueText, "winner: "))
b.WriteString(conflict.Winner)
b.WriteString("\n\t")
b.WriteString(utils.Paint(utils.BlueText, "votes per nodes:"))
nodes := []string{}
for node := range conflict.VotePerNode {
nodes = append(nodes, node)
}
// do not iterate over VotePerNode map
// map accesses are random, it will make regression tests harder
slices.Sort(nodes)
for _, node := range nodes {
vote := conflict.VotePerNode[node]
displayVote := utils.Paint(utils.RedText, vote.MD5)
if vote.MD5 == conflict.Winner {
displayVote = utils.Paint(utils.GreenText, vote.MD5)
}
b.WriteString("\n\t\t")
b.WriteString(utils.Paint(utils.BlueText, node))
b.WriteString(": (")
b.WriteString(displayVote)
b.WriteString(") ")
b.WriteString(vote.Error)
}
b.WriteString("\n\t")
b.WriteString(utils.Paint(utils.BlueText, "initiated by: "))
b.WriteString(fmt.Sprintf("%v", conflict.InitiatedBy))
out = b.String()[2:]
}
}
fmt.Println(out)
return nil
}
return nil
}