Skip to content

Commit

Permalink
feat: support extra report in exec probe
Browse files Browse the repository at this point in the history
  • Loading branch information
macrat committed Apr 24, 2021
1 parent 4938dfa commit 0471773
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 2 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,24 @@ examples:
- `exec:./check.exe`
- `exec:/usr/local/bin/check.sh`

##### Extra report output for exec

In exec, you can set latency of service, and status of service with the output of the command.
Please write output like below.

```
::latency::123.456
::status::failure
hello world
```

This output is reporting latency is `123.456ms`, status is `FAILURE`, and message is `hello world`.

- `::latency::`: Reports the latency of service in milliseconds.
- `::status::`: Reports the status of service in `healthy`, `failure`, or `unknown`.

Ayd uses the last value if found multiple reports in single output.

#### source

This is a special scheme for load targets from a file.
Expand Down
39 changes: 37 additions & 2 deletions probe/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"net/url"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -44,6 +46,36 @@ func (p ExecuteProbe) Target() *url.URL {
return p.target
}

func getLatencyByMessage(message string, default_ time.Duration) (replacedMessage string, latency time.Duration) {
latencyRe := regexp.MustCompile("(?m)^::latency::([0-9]+(?:\\.[0-9]+)?)(?:\n|$)")

if m := latencyRe.FindAllStringSubmatch(message, -1); m != nil {
if l, err := strconv.ParseFloat(m[len(m)-1][1], 64); err == nil {
return strings.Trim(latencyRe.ReplaceAllString(message, ""), "\n"), time.Duration(l * float64(time.Millisecond))
}
}

return message, default_
}

func getStatusByMessage(message string, default_ store.Status) (replacedMessage string, status store.Status) {
statusRe := regexp.MustCompile("(?m)^::status::((?i:healthy|failure|unknown))(?:\n|$)")

if m := statusRe.FindAllStringSubmatch(message, -1); m != nil {
switch strings.ToLower(m[len(m)-1][1]) {
case "healthy":
status = store.STATUS_HEALTHY
case "failure":
status = store.STATUS_FAILURE
case "unknown":
status = store.STATUS_UNKNOWN
}
return strings.Trim(statusRe.ReplaceAllString(message, ""), "\n"), status
}

return message, default_
}

func (p ExecuteProbe) Check(ctx context.Context) []store.Record {
ctx, cancel := context.WithTimeout(ctx, 60*time.Minute)
defer cancel()
Expand All @@ -59,7 +91,7 @@ func (p ExecuteProbe) Check(ctx context.Context) []store.Record {

st := time.Now()
stdout, err := cmd.CombinedOutput()
d := time.Now().Sub(st)
latency := time.Now().Sub(st)

status := store.STATUS_HEALTHY
message := strings.Trim(strings.ReplaceAll(strings.ReplaceAll(string(stdout), "\r\n", "\n"), "\r", "\n"), "\n")
Expand All @@ -85,11 +117,14 @@ func (p ExecuteProbe) Check(ctx context.Context) []store.Record {
}
}

message, latency = getLatencyByMessage(message, latency)
message, status = getStatusByMessage(message, status)

return []store.Record{{
CheckedAt: st,
Target: p.target,
Status: status,
Message: message,
Latency: d,
Latency: latency,
}}
}
68 changes: 68 additions & 0 deletions probe/exec_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package probe

import (
"testing"
"time"

"github.com/macrat/ayd/store"
)

func TestGetLatencyByMessage(t *testing.T) {
tests := []struct {
Input string
Default time.Duration
Output string
Latency time.Duration
}{
{"::latency::123.456", 10 * time.Millisecond, "", 123456 * time.Microsecond},
{
"abc\n::latency::\n::latency::123.456\nsomething",
10 * time.Millisecond,
"abc\n::latency::\nsomething",
123456 * time.Microsecond,
},
{"::latency::abc\n::latency::654.321\n::latency::123", 10 * time.Millisecond, "::latency::abc", 123 * time.Millisecond},
{"::latency::\n::latency::a123", 10 * time.Millisecond, "::latency::\n::latency::a123", 10 * time.Millisecond},
{"", 10 * time.Millisecond, "", 10 * time.Millisecond},
}

for _, tt := range tests {
message, latency := getLatencyByMessage(tt.Input, tt.Default)

if message != tt.Output {
t.Errorf("unexpected message\nexpected: %#v\n but got: %#v", tt.Output, message)
}
if latency != tt.Latency {
t.Errorf("unexpected latency\nexpected: %s\n but got: %s", tt.Latency, latency)
}
}
}

func TestGetStatusByMessage(t *testing.T) {
tests := []struct {
Input string
Default store.Status
Output string
Status store.Status
}{
{"::status::healthy", store.STATUS_UNKNOWN, "", store.STATUS_HEALTHY},
{"::status::Failure", store.STATUS_UNKNOWN, "", store.STATUS_FAILURE},
{"::status::UNKNOWN", store.STATUS_HEALTHY, "", store.STATUS_UNKNOWN},
{"::status::abcdefg", store.STATUS_UNKNOWN, "::status::abcdefg", store.STATUS_UNKNOWN},
{"hello\n::status::FAILURE\nworld", store.STATUS_UNKNOWN, "hello\nworld", store.STATUS_FAILURE},
{"abc\n::status::healthy\n::status::failure", store.STATUS_UNKNOWN, "abc", store.STATUS_FAILURE},
{"hello\nworld", store.STATUS_UNKNOWN, "hello\nworld", store.STATUS_UNKNOWN},
{"", store.STATUS_UNKNOWN, "", store.STATUS_UNKNOWN},
}

for _, tt := range tests {
message, status := getStatusByMessage(tt.Input, tt.Default)

if message != tt.Output {
t.Errorf("unexpected message\nexpected: %#v\n but got: %#v", tt.Output, message)
}
if status != tt.Status {
t.Errorf("unexpected status\nexpected: %s\n but got: %s", tt.Status, status)
}
}
}
2 changes: 2 additions & 0 deletions probe/exec_otheros_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ func TestExecuteProbe(t *testing.T) {
{"exec:./stub/no-permission.sh", store.STATUS_UNKNOWN, `fork/exec ./stub/no-permission.sh: permission denied`},
{"exec:no-such-command", store.STATUS_UNKNOWN, `exec: "no-such-command": executable file not found in \$PATH`},
{"exec:sleep#10", store.STATUS_UNKNOWN, `timeout`},
{"exec:echo#::status::unknown", store.STATUS_UNKNOWN, ``},
{"exec:echo#::status::failure", store.STATUS_FAILURE, ``},
})
}
2 changes: 2 additions & 0 deletions probe/exec_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ func TestExecuteProbe(t *testing.T) {
{`exec:stub\no-such-script`, store.STATUS_UNKNOWN, `exec: "stub\\\\no-such-script": file does not exist`},
{"exec:no-such-command", store.STATUS_UNKNOWN, `exec: "no-such-command": executable file not found in %PATH%`},
{"exec:sleep#10", store.STATUS_UNKNOWN, `timeout`},
{"exec:echo#::status::unknown", store.STATUS_UNKNOWN, ``},
{"exec:echo#::status::failure", store.STATUS_FAILURE, ``},
})
}

0 comments on commit 0471773

Please sign in to comment.