summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--check.go20
-rw-r--r--execute.go64
-rw-r--r--main.go44
-rw-r--r--state.go63
5 files changed, 108 insertions, 91 deletions
diff --git a/README.md b/README.md
index 4c0ca65..c13799f 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,3 @@
# gogios
Minimalist monitoring system, compatible with the Nagios Check API.
-
-TODO: Only send one email per state (OK, WARNING, CRITICAL,
-
-TODO: Only send one email per state (OK, WARNING, CRITICAL,
-
-TODO: Only send one email per state (OK, WARNING, CRITICAL,
-
-TODO: Only send one email per state (OK, WARNING, CRITICAL,
diff --git a/check.go b/check.go
index 702d46b..77cdd2e 100644
--- a/check.go
+++ b/check.go
@@ -12,7 +12,13 @@ type check struct {
Args []string
}
-func (c check) execute(ctx context.Context) (string, int) {
+type checkResult struct {
+ name string
+ output string
+ status int
+}
+
+func (c check) execute(ctx context.Context, name string) checkResult {
cmd := exec.CommandContext(ctx, c.Plugin, c.Args...)
var bytes bytes.Buffer
@@ -21,9 +27,17 @@ func (c check) execute(ctx context.Context) (string, int) {
if err := cmd.Run(); err != nil {
if ctx.Err() == context.DeadlineExceeded {
- return "Check command timed out", critical
+ return checkResult{
+ name: name,
+ output: "Check command timed out",
+ status: critical,
+ }
}
}
- return strings.TrimSuffix(bytes.String(), "\n"), cmd.ProcessState.ExitCode()
+ return checkResult{
+ name: name,
+ output: strings.TrimSuffix(bytes.String(), "\n"),
+ status: cmd.ProcessState.ExitCode(),
+ }
}
diff --git a/execute.go b/execute.go
new file mode 100644
index 0000000..9f9e3e6
--- /dev/null
+++ b/execute.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "context"
+ "log"
+ "sync"
+ "time"
+)
+
+type executionUnit struct {
+ name string
+ check check
+}
+
+func execute(config config, state state) state {
+ limiterCh := make(chan struct{}, config.CheckConcurrency)
+ executionCh := make(chan executionUnit)
+ resultCh := make(chan checkResult)
+
+ go func() {
+ for name, check := range config.Checks {
+ executionCh <- executionUnit{name, check}
+ }
+ close(executionCh)
+ }()
+
+ var resultWg sync.WaitGroup
+ resultWg.Add(1)
+
+ go func() {
+ for checkResult := range resultCh {
+ state.update(checkResult)
+ }
+ resultWg.Done()
+ }()
+
+ var executionWg sync.WaitGroup
+ executionWg.Add(len(config.Checks))
+
+ for executionUnit := range executionCh {
+ go func(name string, check check) {
+ limiterCh <- struct{}{}
+ defer func() {
+ <-limiterCh
+ executionWg.Done()
+ }()
+
+ ctx, cancel := context.WithTimeout(context.Background(),
+ time.Duration(config.CheckTimeoutS)*time.Second)
+ defer cancel()
+
+ resultCh <- check.execute(ctx, name)
+ }(executionUnit.name, executionUnit.check)
+ }
+
+ executionWg.Wait()
+ log.Println("All checks completed!")
+ close(resultCh)
+
+ resultWg.Wait()
+ log.Println("All results collected!")
+
+ return state
+}
diff --git a/main.go b/main.go
index 5a953ba..6c56095 100644
--- a/main.go
+++ b/main.go
@@ -1,11 +1,7 @@
package main
import (
- "context"
"flag"
- "log"
- "sync"
- "time"
)
func main() {
@@ -22,45 +18,7 @@ func main() {
notifyError(config, err)
}
- type entry struct {
- name string
- check check
- }
-
- limiterCh := make(chan struct{}, config.CheckConcurrency)
- checkCh := make(chan entry)
-
- go func() {
- for name, check := range config.Checks {
- checkCh <- entry{name, check}
- }
- close(checkCh)
- }()
-
- var wg sync.WaitGroup
- wg.Add(len(config.Checks))
-
- for entry := range checkCh {
- go func(name string, check check) {
- limiterCh <- struct{}{}
- defer func() {
- <-limiterCh
- wg.Done()
- }()
-
- ctx, cancel := context.WithTimeout(context.Background(),
- time.Duration(config.CheckTimeoutS)*time.Second)
- defer cancel()
-
- output, status := check.execute(ctx)
- // TODO: Send the results through a channel, so we dont have to put a mutex
- // into state.
- state.update(name, output, status)
- }(entry.name, entry.check)
- }
-
- wg.Wait()
- log.Println("All checks completed!")
+ state = execute(config, state)
if err := state.persist(); err != nil {
notifyError(config, err)
diff --git a/state.go b/state.go
index aab090f..983ad48 100644
--- a/state.go
+++ b/state.go
@@ -7,26 +7,23 @@ import (
"log"
"os"
"strings"
- "sync"
)
type checkState struct {
Status int
PrevStatus int
- Output string
+ output string
}
type state struct {
stateFile string
checks map[string]checkState
- mutex *sync.Mutex
}
func newState(config config) (state, error) {
s := state{
stateFile: fmt.Sprintf("%s/state.json", config.StateDir),
checks: make(map[string]checkState),
- mutex: &sync.Mutex{},
}
if _, err := os.Stat(s.stateFile); err != nil {
@@ -40,18 +37,15 @@ func newState(config config) (state, error) {
}
defer file.Close()
- // Read the file content
bytes, err := ioutil.ReadAll(file)
if err != nil {
return s, err
}
- // Parse the JSON content
if err := json.Unmarshal(bytes, &s.checks); err != nil {
return s, err
}
- // Clean up obsolete state information
var obsolete []string
for name := range s.checks {
if _, ok := config.Checks[name]; !ok {
@@ -67,25 +61,16 @@ func newState(config config) (state, error) {
return s, nil
}
-func (s state) update(name, output string, status int) {
- s.mutex.Lock()
- defer s.mutex.Unlock()
-
- prevState, ok := s.checks[name]
- if !ok {
- log.Printf("State of %s: %d (new)", name, status)
- s.checks[name] = checkState{status, unknown, output}
- return
- }
-
- if prevState.Status != status {
- log.Printf("State of %s: %d -> %d (changed)", name, prevState.Status, status)
- s.checks[name] = checkState{status, prevState.Status, output}
- return
+func (s state) update(result checkResult) {
+ prevStatus := unknown
+ prevState, ok := s.checks[result.name]
+ if ok {
+ prevStatus = prevState.Status
}
- log.Printf("State of %s: %d (unchanged)", name, status)
- s.checks[name] = checkState{status, prevState.Status, output}
+ checkState := checkState{result.status, prevStatus, result.output}
+ s.checks[result.name] = checkState
+ log.Println(result.name, checkState)
}
func (s state) persist() error {
@@ -103,21 +88,25 @@ func (s state) report() (string, string, bool) {
f := func(filter func(i int) bool) int {
var count int
for name, checkState := range s.checks {
- if filter(checkState.Status) {
- count++
- if checkState.Status != checkState.PrevStatus {
- sb.WriteString(codeToString(checkState.PrevStatus))
- sb.WriteString("->")
- changed = true
- }
- sb.WriteString(codeToString(checkState.Status))
- sb.WriteString(": ")
- sb.WriteString(name)
- sb.WriteString(" ==>> ")
- sb.WriteString(checkState.Output)
- sb.WriteString("\n")
+ if !filter(checkState.Status) {
+ continue
}
+ count++
+
+ if checkState.Status != checkState.PrevStatus {
+ sb.WriteString(codeToString(checkState.PrevStatus))
+ sb.WriteString("->")
+ changed = true
+ }
+
+ sb.WriteString(codeToString(checkState.Status))
+ sb.WriteString(": ")
+ sb.WriteString(name)
+ sb.WriteString(" ==>> ")
+ sb.WriteString(checkState.output)
+ sb.WriteString("\n")
}
+
return count
}