Files
kopia/tests/robustness/engine/stats.go
Nathan Baulch 657fda216a chore(ci): upgrade to golangci-lint 2.6.1 (#4973)
- upgrade to golangci-lint 2.6.1
- updates for gosec
- updates for govet
- updates for perfsprint
- updates modernize

Leaves out modernize:omitempty due to conflicts with tests
2025-11-11 21:27:10 -08:00

221 lines
6.2 KiB
Go

//go:build darwin || (linux && amd64)
package engine
import (
"fmt"
"log"
"strings"
"time"
"github.com/kopia/kopia/internal/clock"
)
var (
repoBuildTime = "unknown"
repoGitRevision = "unknown"
repoGitBranch = "unknown"
testBuildTime = "unknown"
testGitRevision = "unknown"
testGitBranch = "unknown"
)
// Stats prints the engine stats, cumulative and from the current run.
func (e *Engine) Stats() string {
b := &strings.Builder{}
fmt.Fprintln(b, "==================================")
fmt.Fprintln(b, "Build Info")
fmt.Fprintln(b, "==================================")
fmt.Fprintf(b, " Repo build time: %25v\n", repoBuildTime)
fmt.Fprintf(b, " Repo git revision: %25v\n", repoGitRevision)
fmt.Fprintf(b, " Repo git branch: %25v\n", repoGitBranch)
fmt.Fprintln(b, "")
fmt.Fprintf(b, " Engine build time: %25v\n", testBuildTime)
fmt.Fprintf(b, " Engine git revision: %25v\n", testGitRevision)
fmt.Fprintf(b, " Engine git branch: %25v\n", testGitBranch)
fmt.Fprintln(b, "")
fmt.Fprintln(b, "==================================")
fmt.Fprintln(b, "Engine Action Summary (Cumulative)")
fmt.Fprintln(b, "==================================")
fmt.Fprintf(b, " Engine runtime: %10vs\n", e.getRuntimeSeconds())
fmt.Fprintln(b, "")
fmt.Fprint(b, e.CumulativeStats.Stats())
fmt.Fprintln(b, "")
fmt.Fprintln(b, "==================================")
fmt.Fprintln(b, "Engine Action Summary (This Run)")
fmt.Fprintln(b, "==================================")
fmt.Fprint(b, e.RunStats.Stats())
fmt.Fprintln(b, "")
return b.String()
}
// Stats tracks statistics during engine runtime.
type Stats struct {
RunCounter int64
ActionCounter int64
CreationTime time.Time
RunTime time.Duration
PerActionStats map[ActionKey]*ActionStats
DataRestoreCount int64
DataPurgeCount int64
ErrorRecoveryCount int64
NoOpCount int64
}
// Stats returns a string report of the engine's stats.
func (stats *Stats) Stats() string {
b := &strings.Builder{}
fmt.Fprintln(b, "=============")
fmt.Fprintln(b, "Stat summary")
fmt.Fprintln(b, "=============")
fmt.Fprintf(b, " Number of runs: %10v\n", stats.RunCounter)
fmt.Fprintf(b, " Engine lifetime: %10vs\n", stats.getLifetimeSeconds())
fmt.Fprintf(b, " Actions run: %10v\n", stats.ActionCounter)
fmt.Fprintf(b, " Errors recovered: %10v\n", stats.ErrorRecoveryCount)
fmt.Fprintf(b, " Data dir restores: %10v\n", stats.DataRestoreCount)
fmt.Fprintf(b, " Data dir purges: %10v\n", stats.DataPurgeCount)
fmt.Fprintf(b, " NoOp count: %10v\n", stats.NoOpCount)
fmt.Fprintln(b, "")
fmt.Fprintln(b, "=============")
fmt.Fprintln(b, "Action stats")
fmt.Fprintln(b, "=============")
for actionKey, actionStat := range stats.PerActionStats {
fmt.Fprintf(b, "%s:\n", actionKey)
fmt.Fprintf(b, " Count: %10d\n", actionStat.Count)
fmt.Fprintf(b, " Avg Runtime: %10v\n", actionStat.avgRuntimeString())
fmt.Fprintf(b, " Max Runtime: %10vs\n", durationToSec(actionStat.MaxRuntime))
fmt.Fprintf(b, " Min Runtime: %10vs\n", durationToSec(actionStat.MinRuntime))
fmt.Fprintf(b, " Error Count: %10v\n", actionStat.ErrorCount)
fmt.Fprintln(b, "")
}
return b.String()
}
// ActionStats tracks runtime statistics for an action.
type ActionStats struct {
Count int64
TotalRuntime time.Duration
MinRuntime time.Duration
MaxRuntime time.Duration
ErrorCount int64
}
// AverageRuntime returns the average run time for the action.
func (s *ActionStats) AverageRuntime() time.Duration {
return time.Duration(int64(s.TotalRuntime) / s.Count)
}
// Record records the current time against the provided start time
// and updates the stats accordingly.
func (s *ActionStats) Record(st time.Time, err error) {
thisRuntime := clock.Now().Sub(st)
s.TotalRuntime += thisRuntime
if thisRuntime > s.MaxRuntime {
s.MaxRuntime = thisRuntime
}
if s.Count == 0 || thisRuntime < s.MinRuntime {
s.MinRuntime = thisRuntime
}
s.Count++
if err != nil {
s.ErrorCount++
}
}
func (stats *Stats) getLifetimeSeconds() int64 {
return durationToSec(clock.Now().Sub(stats.CreationTime))
}
func durationToSec(dur time.Duration) int64 {
return int64(dur.Round(time.Second).Seconds())
}
func (s *ActionStats) avgRuntimeString() string {
if s.Count == 0 {
return "--"
}
return fmt.Sprintf("%vs", durationToSec(s.AverageRuntime()))
}
func (e *Engine) getTimestampS() int64 {
return e.getRuntimeSeconds()
}
func (e *Engine) getRuntimeSeconds() int64 {
return durationToSec(e.CumulativeStats.RunTime + clock.Now().Sub(e.RunStats.CreationTime))
}
type statsUpdatetype int
const (
statsIncrNoOp statsUpdatetype = iota
statsIncrDataPurge
statsIncrDataRestore
statsIncrErrorRecovery
)
func (e *Engine) statsUpdateCounters(updateType statsUpdatetype) {
e.statsMux.Lock()
defer e.statsMux.Unlock()
switch updateType {
case statsIncrNoOp:
e.RunStats.NoOpCount++
e.CumulativeStats.NoOpCount++
case statsIncrDataPurge:
e.RunStats.DataPurgeCount++
e.CumulativeStats.DataPurgeCount++
case statsIncrDataRestore:
e.RunStats.DataRestoreCount++
e.CumulativeStats.DataRestoreCount++
case statsIncrErrorRecovery:
e.RunStats.ErrorRecoveryCount++
e.CumulativeStats.ErrorRecoveryCount++
}
}
func (e *Engine) statsIncrActionCountAndLog(actionKey ActionKey) {
e.statsMux.Lock()
defer e.statsMux.Unlock()
e.RunStats.ActionCounter++
e.CumulativeStats.ActionCounter++
log.Printf(
"Engine executing ACTION: name=%q actionCount=%v totActCount=%v t=%vs (%vs)",
actionKey,
e.RunStats.ActionCounter,
e.CumulativeStats.ActionCounter,
e.RunStats.getLifetimeSeconds(),
e.getRuntimeSeconds(),
)
}
func (e *Engine) statsUpdatePerAction(actionKey ActionKey, st time.Time, err error) {
e.statsMux.Lock()
defer e.statsMux.Unlock()
if e.RunStats.PerActionStats != nil && e.RunStats.PerActionStats[actionKey] == nil {
e.RunStats.PerActionStats[actionKey] = new(ActionStats)
}
if e.CumulativeStats.PerActionStats != nil && e.CumulativeStats.PerActionStats[actionKey] == nil {
e.CumulativeStats.PerActionStats[actionKey] = new(ActionStats)
}
e.RunStats.PerActionStats[actionKey].Record(st, err)
e.CumulativeStats.PerActionStats[actionKey].Record(st, err)
}