From 45dd6b7cf03d1c2d3a13d4d24ae6a67d0b377543 Mon Sep 17 00:00:00 2001 From: Tobias Bauriedel Date: Fri, 28 Jun 2024 11:09:31 +0200 Subject: [PATCH] Introduce run metrics (#122) --- .golangci.yml | 2 +- internal/collection/collection.go | 19 +++++- internal/metrics/metrics.go | 37 +++++++++++ main.go | 100 ++++++++++++++++++------------ modules/icinga2/collector.go | 6 +- version.go | 6 +- 6 files changed, 128 insertions(+), 42 deletions(-) create mode 100644 internal/metrics/metrics.go diff --git a/.golangci.yml b/.golangci.yml index 0cace13..f486843 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -42,6 +42,6 @@ linters: linters-settings: funlen: ignore-comments: true - lines: 75 + lines: 80 nestif: min-complexity: 5 diff --git a/internal/collection/collection.go b/internal/collection/collection.go index 1ce75a1..7666e18 100644 --- a/internal/collection/collection.go +++ b/internal/collection/collection.go @@ -3,6 +3,7 @@ package collection import ( "archive/zip" "bytes" + "encoding/json" "fmt" "io" "strings" @@ -23,6 +24,7 @@ type Collection struct { JournalLoggingInterval string } +// New initializes new collection func New(w io.Writer) (c *Collection) { c = &Collection{ Output: zip.NewWriter(w), @@ -141,7 +143,22 @@ func (c *Collection) AddFileYAML(fileName string, data interface{}) { } func (c *Collection) AddFileJSON(fileName string, data []byte) { - c.AddFileDataRaw(fileName, data) + var jsonData interface{} + + err := json.Unmarshal(data, &jsonData) + if err != nil { + c.Log.Debugf("could not unmarshal JSON data for '%s': %s", fileName, err) + } + + prettyJSON, err := json.MarshalIndent(jsonData, "", "") + if err != nil { + c.Log.Debugf("could not marshal JSON data for '%s': %s", fileName, err) + } + + file := NewFile(fileName) + file.Data = prettyJSON + + _ = c.AddFileToOutput(file) } func (c *Collection) AddFiles(prefix, source string) { diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 0000000..e6e9912 --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,37 @@ +package metrics + +import ( + "github.com/NETWAYS/support-collector/internal/obfuscate" + "os" + "strings" + "time" +) + +type Metrics struct { + Command string `json:"command"` + Version string `json:"version"` + Timings map[string]time.Duration `json:"timings"` +} + +// New creates new Metrics +func New(version string) (m *Metrics) { + return &Metrics{ + Command: getCommand(), + Version: version, + Timings: make(map[string]time.Duration), + } +} + +// getCommand returns the executed command and obfusactes *--icinga2* arguments +func getCommand() string { + args := os.Args + + // Obfuscate icinga 2 api user and password + for i, arg := range args { + if strings.Contains(arg, "--icinga2") && i+1 < len(args) { + args[i+1] = obfuscate.Replacement + } + } + + return strings.Join(args, " ") +} diff --git a/main.go b/main.go index 44af6c1..66d9bdf 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main import ( + "encoding/json" "fmt" + "github.com/NETWAYS/support-collector/internal/metrics" "os" "path/filepath" "strings" @@ -111,16 +113,31 @@ var ( outputFile string commandTimeout = 60 * time.Second noDetailedCollection bool + startTime = time.Now() + metric *metrics.Metrics ) func main() { handleArguments() - // set locale to C, to avoid translations in command output + // Set locale to C, to avoid translations in command output _ = os.Setenv("LANG", "C") - c, cleanup := NewCollection(outputFile) - defer cleanup() + c, closeCollection := NewCollection(outputFile) + // Close collection + defer closeCollection() + + // Initialize new metrics and defer function to save it to json + metric = metrics.New(getVersion()) + defer func() { + // Save metrics to file + body, err := json.Marshal(metric) + if err != nil { + c.Log.Warn("cant unmarshal metrics: %w", err) + } + + c.AddFileJSON("metrics.json", body) + }() if noDetailedCollection { c.Detailed = false @@ -133,37 +150,16 @@ func main() { c.Log.Warn("This tool should be run as a privileged user (root) to collect all necessary information") } - var ( - startTime = time.Now() - timings = map[string]time.Duration{} - ) - // Set command Timeout from argument c.ExecTimeout = commandTimeout - // Call all enabled modules - for _, name := range moduleOrder { - switch { - case util.StringInSlice(name, disabledModules): - c.Log.Debugf("Module %s is disabled", name) - case !util.StringInSlice(name, enabledModules): - c.Log.Debugf("Module %s is not enabled", name) - default: - moduleStart := time.Now() + // Collect modules + collectModules(c) - c.Log.Debugf("Start collecting data for module %s", name) + // Save overall timing + metric.Timings["total"] = time.Since(startTime) - for _, o := range extraObfuscators { - c.Log.Debugf("Adding custom obfuscator for '%s' to module %s", o, name) - c.RegisterObfuscator(obfuscate.NewAny(o)) - } - - modules[name](c) - - timings[name] = time.Since(moduleStart) - c.Log.Debugf("Finished with module %s in %.3f seconds", name, timings[name].Seconds()) - } - } + c.Log.Infof("Collection complete, took us %.3f seconds", metric.Timings["total"].Seconds()) // Collect obfuscation info var files, count uint @@ -178,12 +174,7 @@ func main() { c.Log.Infof("Obfuscation replaced %d token in %d files (%d definitions)", count, files, len(c.Obfuscators)) } - // Save timings - timings["total"] = time.Since(startTime) - c.Log.Infof("Collection complete, took us %.3f seconds", timings["total"].Seconds()) - - c.AddFileYAML("timing.yml", timings) - + // get absolute path of outputFile path, err := filepath.Abs(outputFile) if err != nil { c.Log.Debug(err) @@ -224,7 +215,7 @@ func handleArguments() { flag.Parse() if printVersion { - fmt.Println(Product, "version", buildVersion()) //nolint:forbidigo + fmt.Println(Product, "version", getBuildInfo()) //nolint:forbidigo os.Exit(0) } @@ -241,6 +232,9 @@ func buildFileName() string { return util.GetHostnameWithoutDomain() + "-" + FilePrefix + "-" + time.Now().Format("20060102-1504") + ".zip" } +// NewCollection starts a new collection. outputFile will be created. +// +// Collection and cleanup function to defer are returned func NewCollection(outputFile string) (*collection.Collection, func()) { file, err := os.Create(outputFile) if err != nil { @@ -263,9 +257,8 @@ func NewCollection(outputFile string) (*collection.Collection, func()) { Level: consoleLevel, }) - versionString := buildVersion() + versionString := getBuildInfo() c.Log.Infof("Starting %s version %s", Product, versionString) - c.AddFileDataRaw("version", []byte(versionString+"\n")) return c, func() { // Close all open outputs in order, but only log errors @@ -281,3 +274,34 @@ func NewCollection(outputFile string) (*collection.Collection, func()) { } } } + +func collectModules(c *collection.Collection) { + // Check if module is enabled / disabled and call it + for _, name := range moduleOrder { + switch { + case util.StringInSlice(name, disabledModules): + c.Log.Debugf("Module %s is disabled", name) + case !util.StringInSlice(name, enabledModules): + c.Log.Debugf("Module %s is not enabled", name) + default: + // Save current time + moduleStart := time.Now() + + c.Log.Debugf("Start collecting data for module %s", name) + + // Register custom obfuscators + for _, o := range extraObfuscators { + c.Log.Debugf("Adding custom obfuscator for '%s' to module %s", o, name) + c.RegisterObfuscator(obfuscate.NewAny(o)) + } + + // Call collection function for module + modules[name](c) + + // Save runtime of module + metric.Timings[name] = time.Since(moduleStart) + + c.Log.Debugf("Finished with module %s in %.3f seconds", name, metric.Timings[name].Seconds()) + } + } +} diff --git a/modules/icinga2/collector.go b/modules/icinga2/collector.go index 5fbd713..304f132 100644 --- a/modules/icinga2/collector.go +++ b/modules/icinga2/collector.go @@ -125,8 +125,12 @@ func Collect(c *collection.Collection) { } // With Icinga 2 >= 2.14 the icinga2.debug cache is no longer built automatically on every reload. To retrieve a current state we build it manually (only possible from 2.14.0) + // Needs to be done before commands are collected if icinga2version >= "2.14.0" { - c.AddCommandOutput("dump-objects.txt", "icinga2", "daemon", "-C", "--dump-objects") + _, err = collection.LoadCommandOutput("icinga2", "daemon", "-C", "--dump-objects") + if err != nil { + c.Log.Warn(err) + } } for name, cmd := range commands { diff --git a/version.go b/version.go index b48422d..c73c4e8 100644 --- a/version.go +++ b/version.go @@ -10,7 +10,7 @@ var ( ) //goland:noinspection GoBoolExpressions -func buildVersion() string { +func getBuildInfo() string { result := version if commit != "" { @@ -23,3 +23,7 @@ func buildVersion() string { return result } + +func getVersion() string { + return version +}