diff options
| -rw-r--r-- | README.md | 8 | ||||
| -rw-r--r-- | check.go | 20 | ||||
| -rw-r--r-- | execute.go | 64 | ||||
| -rw-r--r-- | main.go | 44 | ||||
| -rw-r--r-- | state.go | 63 |
5 files changed, 108 insertions, 91 deletions
@@ -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, @@ -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 +} @@ -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) @@ -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 } |
