diff --git a/Makefile b/Makefile index 8c8ec287..9ee2e6ea 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ build: vendor go.work snclient.ini server.crt server.key build-watch: vendor ls cmd/*/*.go pkg/*/*.go pkg/*/*/*.go snclient.ini | entr -sr "$(MAKE) && ./snclient $(filter-out $@,$(MAKECMDGOALS))" -# run build watch with other build targets, ex.: make build-watch-any -- build-windows-amd64 +# run build watch with other build targets, ex.: make build-watch-make -- build-windows-amd64 build-watch-make: vendor ls cmd/*/*.go pkg/*/*.go pkg/*/*/*.go snclient.ini | entr -sr "$(MAKE) $(filter-out $@,$(MAKECMDGOALS))" diff --git a/README.md b/README.md index f97beb3b..c4042d7e 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,10 @@ X: completed | check_memory | X | X | X | X | | check_network | W | W | W | W | | check_os_version | X | X | X | X | +| check_pagefile | | | | | | check_process | W | W | W | W | | check_service | X | | | | -| check_snclient_version | X | | | | +| check_snclient_version | X | X | X | X | | check_uptime | X | X | X | X | | check_wmi | W | | | | | check_wrap | W | W | W | W | diff --git a/pkg/snclient/check_cpu.go b/pkg/snclient/check_cpu.go index d81f73e5..28c3f549 100644 --- a/pkg/snclient/check_cpu.go +++ b/pkg/snclient/check_cpu.go @@ -55,7 +55,7 @@ func (l *CheckCPU) Check(snc *Agent, args []string) (*CheckResult, error) { check.listData = append(check.listData, map[string]string{ "time": time, "core": name, - "load": fmt.Sprintf("%f", avg), + "load": fmt.Sprintf("%.0f", utils.ToPrecision(avg, 0)), }) check.result.Metrics = append(check.result.Metrics, &CheckMetric{ ThresholdName: "load", diff --git a/pkg/snclient/check_service_windows.go b/pkg/snclient/check_service_windows.go index 3b510aeb..384cb050 100644 --- a/pkg/snclient/check_service_windows.go +++ b/pkg/snclient/check_service_windows.go @@ -189,36 +189,11 @@ func (l *CheckService) addService(check *CheckData, ctrlMgr *mgr.Mgr, service st check.listData = append(check.listData, listEntry) - if len(services) == 0 { + if len(services) == 0 && !check.showAll { return nil } - check.result.Metrics = append(check.result.Metrics, &CheckMetric{ - Name: service, - Value: float64(statusCode.State), - }) - if mem != nil { - check.result.Metrics = append( - check.result.Metrics, - &CheckMetric{ - Name: fmt.Sprintf("%s rss", service), - Value: float64(mem.RSS), - Unit: "B", - }, - &CheckMetric{ - Name: fmt.Sprintf("%s vms", service), - Value: float64(mem.VMS), - Unit: "B", - }, - ) - } - if cpu != nil { - check.result.Metrics = append(check.result.Metrics, &CheckMetric{ - Name: fmt.Sprintf("%s cpu", service), - Value: utils.ToPrecision(*cpu, 1), - Unit: "%", - }) - } + l.addMetrics(check, service, statusCode, mem, cpu) return nil } @@ -313,3 +288,55 @@ func (l *CheckService) svcStartType(startType uint32, delayed bool) string { return "unknown" } + +func (l *CheckService) addMetrics(check *CheckData, service string, statusCode *svc.Status, mem *process.MemoryInfoStat, cpu *float64) { + check.result.Metrics = append(check.result.Metrics, &CheckMetric{ + Name: service, + Value: float64(statusCode.State), + }) + if mem != nil { + check.result.Metrics = append( + check.result.Metrics, + &CheckMetric{ + Name: fmt.Sprintf("%s rss", service), + Value: float64(mem.RSS), + Unit: "B", + Warning: check.TransformThreshold(check.warnThreshold, "rss", fmt.Sprintf("%s rss", service), "B", "B", 0), + Critical: check.TransformThreshold(check.warnThreshold, "rss", fmt.Sprintf("%s rss", service), "B", "B", 0), + }, + &CheckMetric{ + Name: fmt.Sprintf("%s vms", service), + Value: float64(mem.VMS), + Unit: "B", + Warning: check.TransformThreshold(check.warnThreshold, "vms", fmt.Sprintf("%s vms", service), "B", "B", 0), + Critical: check.TransformThreshold(check.warnThreshold, "vms", fmt.Sprintf("%s vms", service), "B", "B", 0), + }, + ) + } else { + check.result.Metrics = append( + check.result.Metrics, + &CheckMetric{ + Name: fmt.Sprintf("%s rss", service), + Value: "U", + }, + &CheckMetric{ + Name: fmt.Sprintf("%s vms", service), + Value: "U", + }, + ) + } + if cpu != nil { + check.result.Metrics = append(check.result.Metrics, &CheckMetric{ + Name: fmt.Sprintf("%s cpu", service), + Value: utils.ToPrecision(*cpu, 1), + Unit: "%", + Warning: check.warnThreshold, + Critical: check.critThreshold, + }) + } else { + check.result.Metrics = append(check.result.Metrics, &CheckMetric{ + Name: fmt.Sprintf("%s cpu", service), + Value: "U", + }) + } +} diff --git a/pkg/snclient/check_uptime.go b/pkg/snclient/check_uptime.go index d2113e5a..25942234 100644 --- a/pkg/snclient/check_uptime.go +++ b/pkg/snclient/check_uptime.go @@ -43,8 +43,9 @@ func (l *CheckUptime) Check(_ *Agent, args []string) (*CheckResult, error) { uptime := time.Since(time.Unix(int64(bootTime), 0)) check.listData = append(check.listData, map[string]string{ - "uptime": utils.DurationString(uptime.Truncate(time.Minute)), - "boot": time.Unix(int64(bootTime), 0).UTC().Format("2006-01-02 15:04:05"), + "uptime": utils.DurationString(uptime.Truncate(time.Minute)), + "uptime_value": fmt.Sprintf("%.1f", uptime.Seconds()), + "boot": time.Unix(int64(bootTime), 0).UTC().Format("2006-01-02 15:04:05"), }) check.result.Metrics = append(check.result.Metrics, &CheckMetric{ diff --git a/pkg/snclient/check_wrap.go b/pkg/snclient/check_wrap.go index 62278dab..d32cb186 100644 --- a/pkg/snclient/check_wrap.go +++ b/pkg/snclient/check_wrap.go @@ -60,7 +60,7 @@ func (l *CheckWrap) Check(_ *Agent, args []string) (*CheckResult, error) { log.Debugf("executing command: %s %s", winExecutable, "Set-ExecutionPolicy -Scope Process Unrestricted -Force;"+formattedCommand+"; $LASTEXITCODE") scriptOutput, err = exec.Command(winExecutable, "Set-ExecutionPolicy -Scope Process Unrestricted -Force;"+formattedCommand+"; $LASTEXITCODE").CombinedOutput() case "linux": - log.Debugf("executing command: %s", formattedCommand + "; echo $?") + log.Debugf("executing command: %s", formattedCommand+"; echo $?") scriptOutput, err = exec.Command(formattedCommand + "; echo $?").CombinedOutput() } diff --git a/pkg/snclient/checkmetric.go b/pkg/snclient/checkmetric.go index a5fda4a4..647ad61d 100644 --- a/pkg/snclient/checkmetric.go +++ b/pkg/snclient/checkmetric.go @@ -24,6 +24,11 @@ type CheckMetric struct { func (m *CheckMetric) String() string { var res bytes.Buffer + // Unknown value + if fmt.Sprintf("%v", m.Value) == "U" { + return fmt.Sprintf("'%s'=U", m.Name) + } + res.WriteString(fmt.Sprintf("'%s'=%s%s", m.Name, convert.Num2String(m.Value), m.Unit)) res.WriteString(";") diff --git a/pkg/snclient/checkmetric_test.go b/pkg/snclient/checkmetric_test.go new file mode 100644 index 00000000..8f17c69c --- /dev/null +++ b/pkg/snclient/checkmetric_test.go @@ -0,0 +1,21 @@ +package snclient + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheckMetricsString(t *testing.T) { + for _, check := range []struct { + metric CheckMetric + expect string + }{ + {CheckMetric{Name: "val", Value: "13", Unit: "B"}, `'val'=13B`}, + {CheckMetric{Name: "val", Value: "0.5", Unit: ""}, `'val'=0.5`}, + {CheckMetric{Name: "val", Value: "U", Unit: ""}, `'val'=U`}, + } { + res := check.metric.String() + assert.Equalf(t, check.expect, res, "CheckMetric.String() ->> %s", res) + } +} diff --git a/pkg/snclient/condition.go b/pkg/snclient/condition.go index 20a25ea4..77ae152e 100644 --- a/pkg/snclient/condition.go +++ b/pkg/snclient/condition.go @@ -270,7 +270,12 @@ func (c *Condition) getVarValue(data map[string]string) (val string, ok bool) { } } - varStr, ok := data[c.keyword] + varStr, ok := data[c.keyword+"_value"] + if ok { + return varStr, ok + } + + varStr, ok = data[c.keyword] return varStr, ok } @@ -385,6 +390,12 @@ func conditionNext(token []string) (cond *Condition, remaining []string, err err } query := keyword + // trim quotes from keyword + keyword, err = utils.TrimQuotes(keyword) + if err != nil { + return nil, nil, fmt.Errorf("%s", err.Error()) + } + cond = &Condition{ keyword: keyword, } diff --git a/pkg/snclient/condition_test.go b/pkg/snclient/condition_test.go index 976432b2..b11a44ae 100644 --- a/pkg/snclient/condition_test.go +++ b/pkg/snclient/condition_test.go @@ -120,6 +120,8 @@ func TestConditionCompare(t *testing.T) { {"test regex 'a+'", "test", "bbbb", false}, {"test !~ 'a+'", "test", "bbb", true}, {"test !~ 'a+'", "test", "aa", false}, + {"'test space' > 5", "test space", "2", false}, + {"'test space' < 5", "test space", "2", true}, } { threshold, err := NewCondition(check.threshold) assert.NoErrorf(t, err, "parsed threshold") diff --git a/pkg/snclient/listen_web.go b/pkg/snclient/listen_web.go index d3c922c8..9e54d4a4 100644 --- a/pkg/snclient/listen_web.go +++ b/pkg/snclient/listen_web.go @@ -49,6 +49,9 @@ type CheckWebPerfNumber struct { } func (n CheckWebPerfNumber) MarshalJSON() ([]byte, error) { + if fmt.Sprintf("%v", n.num) == "U" { + return []byte(`"U"`), nil + } val, err := convert.Num2StringE(n.num) if err != nil { return nil, fmt.Errorf("num2string: %s", err.Error()) @@ -257,6 +260,10 @@ func (l *HandlerWebLegacy) ServeHTTP(res http.ResponseWriter, req *http.Request) // check clear text password if !l.Handler.verifyPassword(req.Header.Get("Password")) { http.Error(res, http.StatusText(http.StatusForbidden), http.StatusForbidden) + res.Header().Set("Content-Type", "application/json") + LogError(json.NewEncoder(res).Encode(map[string]interface{}{ + "error": "permission denied", + })) return } @@ -310,6 +317,10 @@ func (l *HandlerWebV1) ServeHTTP(res http.ResponseWriter, req *http.Request) { _, password, _ := req.BasicAuth() if !l.Handler.verifyPassword(password) { http.Error(res, http.StatusText(http.StatusForbidden), http.StatusForbidden) + res.Header().Set("Content-Type", "application/json") + LogError(json.NewEncoder(res).Encode(map[string]interface{}{ + "error": "permission denied", + })) return } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 71900ee8..a6e1aff6 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -212,6 +212,29 @@ func TokenizeBy(str, separator string) []string { return tokens } +func TrimQuotes(str string) (res string, err error) { + switch { + case strings.HasPrefix(str, "'"): + if !strings.HasSuffix(str, "'") || len(str) == 1 { + return "", fmt.Errorf("unbalanced quotes in '%s'", str) + } + str = strings.TrimPrefix(str, "'") + str = strings.TrimSuffix(str, "'") + case strings.HasPrefix(str, `"`): + if !strings.HasSuffix(str, `"`) || len(str) == 1 { + return "", fmt.Errorf("unbalanced quotes in '%s'", str) + } + str = strings.TrimPrefix(str, `"`) + str = strings.TrimSuffix(str, `"`) + case strings.HasSuffix(str, "'"): + return "", fmt.Errorf("unbalanced quotes in '%s'", str) + case strings.HasSuffix(str, `"`): + return "", fmt.Errorf("unbalanced quotes in '%s'", str) + } + + return str, nil +} + func ParseVersion(str string) (num float64) { str = strings.TrimPrefix(str, "v") token := strings.Split(str, ".") diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 3217fbba..581937e1 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -119,3 +119,33 @@ func TestDurationString(t *testing.T) { assert.Equalf(t, tst.res, res, "DurationString: %v -> %v", tst.in, res) } } + +func TestTrimQuotes(t *testing.T) { + tests := []struct { + in string + res string + err bool + }{ + {`"test"`, `test`, false}, + {`'test'`, `test`, false}, + {`'test test'`, `test test`, false}, + {`"test test"`, `test test`, false}, + {`"test test`, "", true}, + {`'test test`, "", true}, + {`test"test`, `test"test`, false}, + {`test'test`, `test'test`, false}, + {`test test'`, "", true}, + {`test test"`, "", true}, + } + + for _, tst := range tests { + res, err := TrimQuotes(tst.in) + switch tst.err { + case true: + assert.Errorf(t, err, "TrimQuotes should error on %s", tst.in) + case false: + assert.Nilf(t, err, "TrimQuotes should not error on %s", tst.in) + assert.Equalf(t, tst.res, res, "TrimQuotes: %v -> %v", tst.in, res) + } + } +}