Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce run metrics #122

Merged
merged 1 commit into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ linters:
linters-settings:
funlen:
ignore-comments: true
lines: 75
lines: 80
nestif:
min-complexity: 5
19 changes: 18 additions & 1 deletion internal/collection/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package collection
import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
Expand All @@ -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),
Expand Down Expand Up @@ -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) {
Expand Down
37 changes: 37 additions & 0 deletions internal/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -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, " ")
}
100 changes: 62 additions & 38 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package main

import (
"encoding/json"
"fmt"
"github.com/NETWAYS/support-collector/internal/metrics"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}

Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -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())
}
}
}
6 changes: 5 additions & 1 deletion modules/icinga2/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion version.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ var (
)

//goland:noinspection GoBoolExpressions
func buildVersion() string {
func getBuildInfo() string {
result := version

if commit != "" {
Expand All @@ -23,3 +23,7 @@ func buildVersion() string {

return result
}

func getVersion() string {
return version
}