Skip to content

Commit

Permalink
Merge pull request #31 from mackerelio-labs/statusas
Browse files Browse the repository at this point in the history
introduce --status-as option
  • Loading branch information
kmuto authored Nov 6, 2023
2 parents d129f3e + acd381e commit 8e7575e
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 33 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ command = ["check-mackerel-metric", "-s", "SERVICE_NAME", "-n", "METRIC_NAME", "
- `--name METRIC_NAME, -n METRIC_NAME`: target metric name
- `--warning MINUTE, -w MINUTE`: minute to be WARNING (MINUTE: 1-1441)
- `--critical MINUTE, -c MINUTE`: minute to be CRITICAL (MINUTE: 1-1441)
- `--status-as STATUS=NEWSTATUS,[STATUS=NEWSTATUS,...]`: override the status
- `--help, -h`: display the help and exit
- `--version`: display version and exit
- `--host` is for host metrics and `--service` is for service metrics. Choose one of these.
- HOST_ID is displayed at the top of the Mackerel host screen, like `4Hkc5RWzXXX`.
- METRIC_NAME can be looked up with `mkr metric-names -H HOST_ID`.
- The API key is taken from the existing mackerel-agent.conf. If you want to use a different API key, you can specify it in the environment variable `MACKEREL_APIKEY`.
- If `--warning` and `--critical` are set to the same value, only critical alert is enabled.
- `--status-as` overrides the state to be reported. It is set to return the actual state of `ok`, `warning`, `critical` or `unknown` as the specified state, in the format `STATUS=NEWSTATUS`. For example, with `--status-as critical=warning,unknown=ok`, a CRITICAL alert will be reported as a WARNING alert, and an UNKNOWN alert will be treated as OK.

## License
© 2023 Hatena Co., Ltd.
Expand Down Expand Up @@ -94,13 +96,15 @@ command = ["check-mackerel-metric", "-s", "SERVICE_NAME", "-n", "METRIC_NAME", "
- `--name METRIC_NAME, -n METRIC_NAME`: 対象のメトリック名
- `--warning MINUTE, -w MINUTE`: 指定の分数内にメトリックがなければWARNING(MINUTEは1〜1441)
- `--critical MINUTE, -c MINUTE`: 指定の分数内にメトリックがなければCRITICAL(MINUTEは1〜1441)
- `--status-as STATUS=NEWSTATUS,[STATUS=NEWSTATUS,...]`: 状態の書き換え
- `--help, -h`: ヘルプの表示と終了
- `--version`: バージョンの表示と終了
- `--host`はホストメトリック用、`--service`はサービスメトリック用です。どちらか1つを選んでください。
- HOST_ID (ホストID) はMackerelのホスト画面の上部に `4Hkc5RWzXXX` のように表示されています。
- METRIC_NAME (メトリック名) は `mkr metric-names -H HOST_ID` で調べることができます。
- APIキーは既存のmackerel-agent.confから取得されます。別のAPIキーを利用したいときには、環境変数`MACKEREL_APIKEY`で指定できます。
- `--warning``--critical` の値を同じ値に設定すると、CRITICALアラートのみが発報されます。
- `--status-as` は発報する状態を書き換えます。`STATUS=NEWSTATUS` の書式で、`ok``warning``critical``unknown` のそれぞれの実際の状態を別の状態として返すよう設定します。たとえば `--status-as critical=warning,unknown=ok` とすると、CRITICALアラートはWARNINGアラートとして発報され、UNKNOWNアラートは正常として扱われます。

## ライセンス
© 2023 Hatena Co., Ltd.
Expand Down
17 changes: 12 additions & 5 deletions checkmackerelmetric/checkmackerelmetric.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type mackerelMetricOpts struct {
Metric string `arg:"-n,--name,required" help:"target metric name" placeholder:"METRIC_NAME"`
Warning uint `arg:"-w,--warning,required" help:"minute to be WARNING (MINUTE: 1-1441)" placeholder:"MINUTE"`
Critical uint `arg:"-c,--critical,required" help:"minute to be CRITICAL (MINUTE: 1-1441)" placeholder:"MINUTE"`
StatusAs string `arg:"--status-as" help:"overwrite status=new_status, support multiple comma separates"`
}

var version string
Expand All @@ -29,18 +30,18 @@ func (mackerelMetricOpts) Version() string {
}

func Do() {
opts, err := parseArgs(os.Args[1:])
opts, maps, err := parseArgs(os.Args[1:])
if err != nil {
fmt.Println(err)
os.Exit(1)
}

ckr := opts.run()
ckr.Name = "MackerelMetric"
ckr.Exit()
ckr.ExitStatusAs(maps)
}

func parseArgs(args []string) (*mackerelMetricOpts, error) {
func parseArgs(args []string) (*mackerelMetricOpts, map[checkers.Status]checkers.Status, error) {
var mo mackerelMetricOpts
p, _ := arg.NewParser(arg.Config{}, &mo)
err := p.Parse(args)
Expand All @@ -53,7 +54,13 @@ func parseArgs(args []string) (*mackerelMetricOpts, error) {
fmt.Println(mo.Version())
os.Exit(0)
case err != nil:
return &mo, err
return &mo, nil, err
}

// parse status-as option
maps, err := checkers.ParseStatusMap(mo.StatusAs)
if err != nil {
return &mo, nil, err
}

// Set internal limit: 24h1m
Expand All @@ -70,7 +77,7 @@ func parseArgs(args []string) (*mackerelMetricOpts, error) {
if mo.Critical < mo.Warning {
err = fmt.Errorf("critical minute must be equal or greater than warning minute")
}
return &mo, err
return &mo, maps, err
}

func (opts *mackerelMetricOpts) run() *checkers.Checker {
Expand Down
58 changes: 33 additions & 25 deletions checkmackerelmetric/checkmackerelmetric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,86 +15,94 @@ import (
)

func TestParseArgs(t *testing.T) {
opts, err := parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 60", " "))
opts, _, err := parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 60", " "))
assert.Equal(t, nil, err, "parameters with a host should be passed")
assert.Equal(t, "HOSTID", opts.Host, "host is passed correctly")
assert.Equal(t, "METRIC", opts.Metric, "metric is passed correctly")
assert.Equal(t, uint(30), opts.Warning, "warning is passed correctly")
assert.Equal(t, uint(60), opts.Critical, "critical is passed correctly")

opts, err = parseArgs(strings.Split("-s SERVICE -n METRIC -w 30 -c 60", " "))
opts, _, err = parseArgs(strings.Split("-s SERVICE -n METRIC -w 30 -c 60", " "))
assert.Equal(t, nil, err, "parameters with a service should be passed")
assert.Equal(t, "SERVICE", opts.Service, "service is passed correctly")

opts, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 1441", " "))
opts, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 1441", " "))
assert.Equal(t, nil, err, "parmeters with max minute should be passed")
assert.Equal(t, uint(1441), opts.Critical, "1441 minutes (= max minute) is passed correctly")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 60 -c 60", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 60 -c 60", " "))
assert.Equal(t, nil, err, "it is acceptable for warning and critical to have the same value")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 1441 -c 60", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 1441 -c 60", " "))
assert.Equal(t, fmt.Errorf("critical minute must be equal or greater than warning minute"), err, "warning can't over critical")

_, err = parseArgs(strings.Split("-H HOSTID", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID", " "))
assert.Equal(t, fmt.Errorf("--name is required"), err, "needs metric name")

_, err = parseArgs(strings.Split("-H HOSTID -w 30", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -w 30", " "))
assert.Equal(t, fmt.Errorf("--name is required"), err, "needs metric name")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC", " "))
assert.Equal(t, fmt.Errorf("--warning is required"), err, "needs warning metric")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30", " "))
assert.Equal(t, fmt.Errorf("--critical is required"), err, "needs critical metric")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -c 30", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -c 30", " "))
assert.Equal(t, fmt.Errorf("--warning is required"), err, "needs warning metric")

_, err = parseArgs(strings.Split("-n METRIC -c 60 -w 30", " "))
_, _, err = parseArgs(strings.Split("-n METRIC -c 60 -w 30", " "))
assert.Equal(t, fmt.Errorf("either --host or --service is required"), err, "needs host or service")

_, err = parseArgs(strings.Split("-s SERVICE", " "))
_, _, err = parseArgs(strings.Split("-s SERVICE", " "))
assert.Equal(t, fmt.Errorf("--name is required"), err, "needs metric name")

_, err = parseArgs(strings.Split("-s SERVICE -w 30", " "))
_, _, err = parseArgs(strings.Split("-s SERVICE -w 30", " "))
assert.Equal(t, fmt.Errorf("--name is required"), err, "needs metric name")

_, err = parseArgs(strings.Split("-s SERVICE -n METRIC", " "))
_, _, err = parseArgs(strings.Split("-s SERVICE -n METRIC", " "))
assert.Equal(t, fmt.Errorf("--warning is required"), err, "needs warning metric")

_, err = parseArgs(strings.Split("-s SERVICE -n METRIC -w 30", " "))
_, _, err = parseArgs(strings.Split("-s SERVICE -n METRIC -w 30", " "))
assert.Equal(t, fmt.Errorf("--critical is required"), err, "needs critical metric")

_, err = parseArgs(strings.Split("-s SERVICE -n METRIC -c 30", " "))
_, _, err = parseArgs(strings.Split("-s SERVICE -n METRIC -c 30", " "))
assert.Equal(t, fmt.Errorf("--warning is required"), err, "needs warning metric")

_, err = parseArgs(strings.Split("-H HOSTID -s SERVICE -n METRIC -w 30 -c 60", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -s SERVICE -n METRIC -w 30 -c 60", " "))
assert.Equal(t, fmt.Errorf("both --host and --service cannot be specified"), err, "one of host or service")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 0 -c 60", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 0 -c 60", " "))
assert.Equal(t, fmt.Errorf("specified minute is out of range (1-1441)"), err, "0 minute is invalid")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w \"-10\" -c 60", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w \"-10\" -c 60", " "))
assert.Equal(t, fmt.Errorf("error processing -w: strconv.ParseUint: parsing \"\\\"-10\\\"\": invalid syntax"), err, "negative minute is invalid")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30.1 -c 60", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30.1 -c 60", " "))
assert.Equal(t, fmt.Errorf("error processing -w: strconv.ParseUint: parsing \"30.1\": invalid syntax"), err, "float minute is invalid")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c \"-10\"", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c \"-10\"", " "))
assert.Equal(t, fmt.Errorf("error processing -c: strconv.ParseUint: parsing \"\\\"-10\\\"\": invalid syntax"), err, "negative minute is invalid")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 60.1", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 60.1", " "))
assert.Equal(t, fmt.Errorf("error processing -c: strconv.ParseUint: parsing \"60.1\": invalid syntax"), err, "float minute is invalid")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 1442", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 1442", " "))
assert.Equal(t, fmt.Errorf("specified minute is out of range (1-1441)"), err, "over 1441 minute is invalid")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w true -c 60", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w true -c 60", " "))
assert.Equal(t, fmt.Errorf("error processing -w: strconv.ParseUint: parsing \"true\": invalid syntax"), err, "string minute is invalid")

_, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c true", " "))
_, _, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c true", " "))
assert.Equal(t, fmt.Errorf("error processing -c: strconv.ParseUint: parsing \"true\": invalid syntax"), err, "string minute is invalid")

var maps map[checkers.Status]checkers.Status
_, maps, err = parseArgs(strings.Split("-H HOSTID -n METRIC -w 30 -c 60 --status-as ok=warning,warning=ok,critical=unknown,unknown=critical", " "))
assert.Equal(t, nil, err, "status modification parameter is valid")
assert.Equal(t, checkers.WARNING, maps[checkers.OK], "ok is replaced as warning")
assert.Equal(t, checkers.OK, maps[checkers.WARNING], "warning is replaced as ok")
assert.Equal(t, checkers.UNKNOWN, maps[checkers.CRITICAL], "critical is replaced as unknown")
assert.Equal(t, checkers.CRITICAL, maps[checkers.UNKNOWN], "unknown is replaced as critical")
}

func TestCheckMetric(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.21

require (
github.com/alexflint/go-arg v1.4.3
github.com/mackerelio/checkers v0.0.4
github.com/mackerelio/checkers v0.2.0
github.com/mackerelio/mackerel-agent v0.77.1
github.com/mackerelio/mackerel-client-go v0.26.0
github.com/stretchr/testify v1.7.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mackerelio/checkers v0.0.4 h1:dLxl3szIA1uW/+pFefamBPaFT9MCKkdH3uQND7c64bk=
github.com/mackerelio/checkers v0.0.4/go.mod h1:VEf9gFHvpvH7Zvcwjuj7x3ozQg5w3En6ww9UcWoWHeE=
github.com/mackerelio/checkers v0.2.0 h1:YBOQjpU2Qno66eUrUEH6DjWn+Wna5BXCKMdekz50XWs=
github.com/mackerelio/checkers v0.2.0/go.mod h1:CW3k/5bvHhxDrfKgWvMvNH0R51zco141ZVxlI7o/KAc=
github.com/mackerelio/golib v1.2.1 h1:SDcDn6Jw3p9bi1N0bg1Z/ilG5qcBB23qL8xNwrU0gg4=
github.com/mackerelio/golib v1.2.1/go.mod h1:b8ZaapsHGH1FlEJlCqfD98CqafLeyMevyATDlID2BsM=
github.com/mackerelio/mackerel-agent v0.77.1 h1:O6mQbTW2X5dolNrML/i8bPObr6m4kN3y5/Q+uek/ej4=
Expand Down

0 comments on commit 8e7575e

Please sign in to comment.