From 12f1752bc051dee6c0b46a9238ceb8ac88b99789 Mon Sep 17 00:00:00 2001 From: Florian Bauer Date: Tue, 5 Jul 2022 19:04:27 +0200 Subject: [PATCH 1/6] sort imports; group by goimports --- collector.go | 3 ++- main.go | 2 +- pluginList.go | 1 + plugins/Command.go | 8 +++----- server.go | 3 ++- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/collector.go b/collector.go index 6541e86..728092d 100644 --- a/collector.go +++ b/collector.go @@ -1,8 +1,9 @@ package main import ( - "github.com/Anthrazz/parallel-check/plugins" "time" + + "github.com/Anthrazz/parallel-check/plugins" ) // Collector provides an interface for a single data source (e.g. server) which should be regularly be tested diff --git a/main.go b/main.go index 68a25d5..1e4267e 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,12 @@ package main import ( "flag" "fmt" - "github.com/Anthrazz/parallel-check/plugins" "os" "strconv" "sync" "time" + "github.com/Anthrazz/parallel-check/plugins" "github.com/eiannone/keyboard" "github.com/gosuri/uilive" "github.com/miekg/dns" diff --git a/pluginList.go b/pluginList.go index a002118..12861a3 100644 --- a/pluginList.go +++ b/pluginList.go @@ -2,6 +2,7 @@ package main import ( "errors" + "github.com/Anthrazz/parallel-check/plugins" ) diff --git a/plugins/Command.go b/plugins/Command.go index 47feb21..22ca022 100644 --- a/plugins/Command.go +++ b/plugins/Command.go @@ -1,11 +1,13 @@ package plugins import ( + "context" "errors" "fmt" - "github.com/mattn/go-shellwords" "os/exec" "time" + + "github.com/mattn/go-shellwords" ) /** @@ -15,10 +17,6 @@ its exitcode as the test result. Its still WIP and not ready for use. */ -import ( - "context" -) - type CommandCollector struct { command []string name string diff --git a/server.go b/server.go index d42273e..91dc661 100644 --- a/server.go +++ b/server.go @@ -2,9 +2,10 @@ package main import ( "fmt" + "time" + "github.com/Anthrazz/parallel-check/plugins" "github.com/fatih/color" - "time" ) /* From 1718ce252d810d18bcf30e66fd24b2b1bcf1b474 Mon Sep 17 00:00:00 2001 From: Florian Bauer Date: Tue, 5 Jul 2022 20:01:32 +0200 Subject: [PATCH 2/6] remove unrequired arguments --- main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 1e4267e..d9c729f 100644 --- a/main.go +++ b/main.go @@ -151,7 +151,7 @@ func (gs *GlobalStateType) TogglePause() { } func (gs *GlobalStateType) Reset() { - for i, _ := range gs.Server { + for i := range gs.Server { gs.Server[i].Reset() } @@ -161,7 +161,7 @@ func (gs *GlobalStateType) Reset() { // UpdateTimeouts updates the timeout on each tested instance func (gs *GlobalStateType) UpdateTimeouts() { - for i, _ := range gs.Server { + for i := range gs.Server { gs.Server[i].TestPlugin.SetTimeout(gs.Timeout) } } From 4993df820217633d1f407925b06471c997fba83b Mon Sep 17 00:00:00 2001 From: Florian Bauer Date: Tue, 5 Jul 2022 20:01:56 +0200 Subject: [PATCH 3/6] quit and clear screen on ctrl+c --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index d9c729f..f54d29a 100644 --- a/main.go +++ b/main.go @@ -337,7 +337,8 @@ func keyboardRoutine(chRender chan Command) { GlobalState.TogglePause() } // Quit - if event.Rune == 'q' || event.Rune == 'Q' { + if event.Rune == 'q' || event.Rune == 'Q' || event.Key == keyboard.KeyCtrlC { + chRender <- Command{Command: "clearConsole"} os.Exit(0) } // Reset - Set Variable to do reset between tests From b22902e0088fff273da902a87961a0a4e73e6383 Mon Sep 17 00:00:00 2001 From: Florian Bauer Date: Tue, 5 Jul 2022 21:18:26 +0200 Subject: [PATCH 4/6] unexport global vars; quit all instances on signal or control+c --- main.go | 282 +++++++++++++++++++++++++++++------------------------- server.go | 10 +- 2 files changed, 156 insertions(+), 136 deletions(-) diff --git a/main.go b/main.go index f54d29a..52f220e 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,15 @@ package main import ( + "context" "flag" "fmt" "os" + "os/signal" + "runtime" "strconv" "sync" + "syscall" "time" "github.com/Anthrazz/parallel-check/plugins" @@ -28,7 +32,7 @@ var ( PluginToUse string - GlobalState GlobalStateType // contains different variables and functions for the global state of the program + globalState GlobalStateType // contains different variables and functions for the global state of the program ) /*********/ @@ -155,8 +159,8 @@ func (gs *GlobalStateType) Reset() { gs.Server[i].Reset() } - GlobalState.TestCounter = 0 - GlobalState.WorstResponseDelay = 0 + globalState.TestCounter = 0 + globalState.WorstResponseDelay = 0 } // UpdateTimeouts updates the timeout on each tested instance @@ -183,7 +187,7 @@ type Command struct { // Return true when the max queries are reached func sleep(duration time.Duration) bool { // exit if the maximum query count is reached - if *MaxCount != 0 && GlobalState.TestCounter >= *MaxCount { + if *MaxCount != 0 && globalState.TestCounter >= *MaxCount { return true } @@ -262,62 +266,67 @@ func main() { } func run() int { + ctx, cancelRoutines := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, syscall.SIGINT) + defer cancelRoutines() + registerPlugins() - GlobalState = *parseFlags() + globalState = *parseFlags() // Start rendering routine chRender := make(chan Command) var wgRender sync.WaitGroup wgRender.Add(1) - go renderRoutine(&wgRender, chRender) + go renderRoutine(ctx, &wgRender, chRender) // Start Keyboard listen routine - go keyboardRoutine(chRender) + go keyboardRoutine(ctx, cancelRoutines, chRender) // Clear Console Screen chRender <- Command{Command: "clearConsole"} // Start Main Loop which coordinate queries and rendering -MainLoop: for { - startLoop := time.Now() - - // reset all stats if needed - if GlobalState.ResetState { - GlobalState.Reset() - GlobalState.ResetState = false - } + select { + case <-ctx.Done(): + // End Rendering Thread + close(chRender) + wgRender.Wait() + return 0 + default: + startLoop := time.Now() + + // reset all stats if needed + if globalState.ResetState { + globalState.Reset() + globalState.ResetState = false + } - // execute all tests - if !GlobalState.Pause { - GlobalState.QueryResolver() - } + // execute all tests + if !globalState.Pause { + globalState.QueryResolver() + } - // render the user interface - chRender <- Command{Command: "renderTable"} + // render the user interface + chRender <- Command{Command: "renderTable"} - elapsedTimeSinceStart := time.Since(startLoop) - // calculate how long the tests in the last frame have taken and only wait up to the max wait time - timeToSleep := *WaitTime - elapsedTimeSinceStart - if elapsedTimeSinceStart >= *WaitTime { - timeToSleep = time.Duration(0) - } + elapsedTimeSinceStart := time.Since(startLoop) + // calculate how long the tests in the last frame have taken and only wait up to the max wait time + timeToSleep := *WaitTime - elapsedTimeSinceStart + if elapsedTimeSinceStart >= *WaitTime { + timeToSleep = time.Duration(0) + } - if end := sleep(timeToSleep); end { - break MainLoop + if end := sleep(timeToSleep); end { + break + } } } - - // End Rendering Thread - close(chRender) - wgRender.Wait() - return 0 } // keyboardRoutine does process all keyboard inputs. // // It gets a reference to the render channel to start a redrawn of the user interface. -func keyboardRoutine(chRender chan Command) { +func keyboardRoutine(ctx context.Context, cancelRoutines context.CancelFunc, chRender chan Command) { keysEvents, err := keyboard.GetKeys(3) if err != nil { panic(err) @@ -326,115 +335,126 @@ func keyboardRoutine(chRender chan Command) { _ = keyboard.Close() }() - for event := range keysEvents { - if event.Err != nil { - panic(event.Err) - } - //fmt.Printf("You pressed: rune %q, key %X\r\n", event.Rune, event.Key) - - // Pause - if event.Rune == 'p' || event.Rune == 'P' { - GlobalState.TogglePause() - } - // Quit - if event.Rune == 'q' || event.Rune == 'Q' || event.Key == keyboard.KeyCtrlC { + for { + select { + case <-ctx.Done(): chRender <- Command{Command: "clearConsole"} - os.Exit(0) - } - // Reset - Set Variable to do reset between tests - if event.Rune == 'r' || event.Rune == 'R' { - GlobalState.ResetState = true - } + return + case event := <-keysEvents: + if event.Err != nil { + panic(event.Err) + } + //fmt.Printf("You pressed: rune %q, key %X\r\n", event.Rune, event.Key) - // Arrow Key down -> decrease delay - if event.Key == keyboard.KeyArrowDown { - if WaitTime.Milliseconds() >= int64(110) { - *WaitTime = *WaitTime - (100 * time.Millisecond) + // Pause + if event.Rune == 'p' || event.Rune == 'P' { + globalState.TogglePause() + } + // Quit + if event.Rune == 'q' || event.Rune == 'Q' || event.Key == keyboard.KeyCtrlC { + chRender <- Command{Command: "clearConsole"} + cancelRoutines() + return + } + // Reset - Set Variable to do reset between tests + if event.Rune == 'r' || event.Rune == 'R' { + globalState.ResetState = true } - } - // Arrow Key up -> increase delay - if event.Key == keyboard.KeyArrowUp { - *WaitTime = *WaitTime + (100 * time.Millisecond) - } - // Arrow Key left -> decrease timeout - if event.Key == keyboard.KeyArrowLeft { - if GlobalState.Timeout.Milliseconds() >= int64(110) { - GlobalState.Timeout = GlobalState.Timeout - (100 * time.Millisecond) - GlobalState.UpdateTimeouts() + // Arrow Key down -> decrease delay + if event.Key == keyboard.KeyArrowDown { + if WaitTime.Milliseconds() >= int64(110) { + *WaitTime = *WaitTime - (100 * time.Millisecond) + } + } + // Arrow Key up -> increase delay + if event.Key == keyboard.KeyArrowUp { + *WaitTime = *WaitTime + (100 * time.Millisecond) } - } - // Arrow Key right -> increase timeout - if event.Key == keyboard.KeyArrowRight { - GlobalState.Timeout = GlobalState.Timeout + (100 * time.Millisecond) - GlobalState.UpdateTimeouts() - } - // Re-render Table - chRender <- Command{Command: "renderTable"} - } + // Arrow Key left -> decrease timeout + if event.Key == keyboard.KeyArrowLeft { + if globalState.Timeout.Milliseconds() >= int64(110) { + globalState.Timeout = globalState.Timeout - (100 * time.Millisecond) + globalState.UpdateTimeouts() + } + } + // Arrow Key right -> increase timeout + if event.Key == keyboard.KeyArrowRight { + globalState.Timeout = globalState.Timeout + (100 * time.Millisecond) + globalState.UpdateTimeouts() + } + // Re-render Table + chRender <- Command{Command: "renderTable"} + } + } } -func renderRoutine(wg *sync.WaitGroup, commands <-chan Command) { +func renderRoutine(ctx context.Context, wg *sync.WaitGroup, commands <-chan Command) { defer wg.Done() writer := uilive.New() - for cmd := range commands { - switch cmd.Command { - case "quit": - return - case "clearConsole": - fmt.Print("\033[H\033[2J") - - // Rewrite the whole console output - case "renderTable": - // Rewrite the whole table to allow a down scale of the query history column - table := tablewriter.NewWriter(writer) - table.SetHeader( - []string{"Server", "Success", "Errors", "Error %", "Last", "Average", "Best", "Worst", "Query History"}, - ) - table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) - table.SetCenterSeparator("|") - - for _, resolver := range GlobalState.Server { - - table.Append( - []string{ - resolver.TestPlugin.GetName(), - fmt.Sprintf("%d", resolver.SuccessQueries), - fmt.Sprintf("%d", resolver.ErrorQueries), - strconv.FormatFloat(resolver.GetErrorPercentage(), 'f', 2, 64) + "%", - fmt.Sprintf("%.2f ms", float64(resolver.LastDelay/time.Microsecond)/1000), - fmt.Sprintf("%.2f ms", float64(resolver.AverageDelay/time.Microsecond)/1000), - fmt.Sprintf("%.2f ms", float64(resolver.BestDelay/time.Microsecond)/1000), - fmt.Sprintf("%.2f ms", float64(resolver.WorstDelay/time.Microsecond)/1000), - resolver.GetQueryHistory(), - }, + for { + select { + case <-ctx.Done(): + runtime.Goexit() + case cmd := <-commands: + switch cmd.Command { + case "quit": + return + case "clearConsole": + fmt.Print("\033[H\033[2J") + + // Rewrite the whole console output + case "renderTable": + // Rewrite the whole table to allow a down scale of the query history column + table := tablewriter.NewWriter(writer) + table.SetHeader( + []string{"Server", "Success", "Errors", "Error %", "Last", "Average", "Best", "Worst", "Query History"}, ) - } - - table.Render() - - // Print some additional infos - _, _ = fmt.Fprintf(writer, "\n%s\n", " "+getHistoryColorScale()) - _, _ = fmt.Fprintf(writer, " Query History: %d Requests / ~%s\n", GlobalState.MaximumHistoryLength, - time.Duration( - int(*WaitTime+GlobalState.WorstResponseDelay)*GlobalState.MaximumHistoryLength, - ).Round(time.Second), - ) - _, _ = fmt.Fprintf(writer, " Timeout: %s | Delay: %s", GlobalState.Timeout, *WaitTime) - if GlobalState.Pause { - _, _ = fmt.Fprintf(writer, " | Pause Active\n") - } else { - _, _ = fmt.Fprintf(writer, "\n") - } - _, _ = fmt.Fprintf(writer, " Tests: %s\n", PluginToUse) - - err := writer.Flush() - if err != nil { - fmt.Printf("Error has happened at write to terminal: %v\n", err) - os.Exit(1) + table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) + table.SetCenterSeparator("|") + + for _, resolver := range globalState.Server { + + table.Append( + []string{ + resolver.TestPlugin.GetName(), + fmt.Sprintf("%d", resolver.SuccessQueries), + fmt.Sprintf("%d", resolver.ErrorQueries), + strconv.FormatFloat(resolver.GetErrorPercentage(), 'f', 2, 64) + "%", + fmt.Sprintf("%.2f ms", float64(resolver.LastDelay/time.Microsecond)/1000), + fmt.Sprintf("%.2f ms", float64(resolver.AverageDelay/time.Microsecond)/1000), + fmt.Sprintf("%.2f ms", float64(resolver.BestDelay/time.Microsecond)/1000), + fmt.Sprintf("%.2f ms", float64(resolver.WorstDelay/time.Microsecond)/1000), + resolver.GetQueryHistory(), + }, + ) + } + + table.Render() + + // Print some additional infos + _, _ = fmt.Fprintf(writer, "\n%s\n", " "+getHistoryColorScale()) + _, _ = fmt.Fprintf(writer, " Query History: %d Requests / ~%s\n", globalState.MaximumHistoryLength, + time.Duration( + int(*WaitTime+globalState.WorstResponseDelay)*globalState.MaximumHistoryLength, + ).Round(time.Second), + ) + _, _ = fmt.Fprintf(writer, " Timeout: %s | Delay: %s", globalState.Timeout, *WaitTime) + if globalState.Pause { + _, _ = fmt.Fprintf(writer, " | Pause Active\n") + } else { + _, _ = fmt.Fprintf(writer, "\n") + } + _, _ = fmt.Fprintf(writer, " Tests: %s\n", PluginToUse) + + err := writer.Flush() + if err != nil { + fmt.Printf("Error has happened at write to terminal: %v\n", err) + os.Exit(1) + } } } } diff --git a/server.go b/server.go index 91dc661..e7915d7 100644 --- a/server.go +++ b/server.go @@ -65,7 +65,7 @@ func (s *Server) ExecuteQuery() { s.SetAverageDelay(dataPoint.GetDelay()) // Set overall worst response delay - GlobalState.SetWorstResponseDelay(dataPoint.GetDelay()) + globalState.SetWorstResponseDelay(dataPoint.GetDelay()) } else { s.ErrorQueries++ } @@ -78,7 +78,7 @@ func (s *Server) ExecuteQuery() { func (s *Server) SetBestDelay(d time.Duration) { // Default value for s.BestDelay is 0 - so set it explicit at the first query - if GlobalState.TestCounter == 1 { + if globalState.TestCounter == 1 { s.BestDelay = d } else if s.BestDelay > d { s.BestDelay = d @@ -106,7 +106,7 @@ func (s *Server) AppendAnswer(delay time.Duration, result bool) { // DeleteOldestTest deletes the oldest Server.Answer entry when it // would exceed the query history length to be displayed func (s *Server) DeleteOldestTest() { - toRemove := len(s.Answers) - GlobalState.MaximumHistoryLength + toRemove := len(s.Answers) - globalState.MaximumHistoryLength if toRemove >= 1 { s.Answers = s.Answers[toRemove:] } @@ -161,14 +161,14 @@ func (a *TestResult) GetColoredHistoryEntry() string { // getHistoryDelayRating returns an arbitrary float between 0 and 1 (lower is better) which // indicates how good/bad the response was in comparison to the worst response func getHistoryDelayRating(d time.Duration) float64 { - return float64(d) / float64(GlobalState.WorstResponseDelay) + return float64(d) / float64(globalState.WorstResponseDelay) } // show a scale for the usage of the color in the query history func getHistoryColorScale() string { scale := "Scale: " - worstDelay := GlobalState.WorstResponseDelay + worstDelay := globalState.WorstResponseDelay delays := []float64{ float64(worstDelay) * 0.6, From a7990558465234a9e348df19925555f99ce30289 Mon Sep 17 00:00:00 2001 From: Florian Bauer Date: Tue, 5 Jul 2022 23:25:28 +0200 Subject: [PATCH 5/6] use custom type for command types --- main.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index 52f220e..fa1b3fd 100644 --- a/main.go +++ b/main.go @@ -176,9 +176,18 @@ func (gs *GlobalStateType) UpdateTimeouts() { // Command is used to communicate with some Go Routines for Output and User Handling type Command struct { - Command string + Command CommandType } +type CommandType int + +const ( + _ CommandType = iota + CommandTypeClearConsole + CommandTypeRenderTable + CommandTypeQuit +) + /*************/ /* Functions */ /*************/ @@ -282,7 +291,7 @@ func run() int { go keyboardRoutine(ctx, cancelRoutines, chRender) // Clear Console Screen - chRender <- Command{Command: "clearConsole"} + chRender <- Command{Command: CommandTypeClearConsole} // Start Main Loop which coordinate queries and rendering for { @@ -307,7 +316,7 @@ func run() int { } // render the user interface - chRender <- Command{Command: "renderTable"} + chRender <- Command{Command: CommandTypeRenderTable} elapsedTimeSinceStart := time.Since(startLoop) // calculate how long the tests in the last frame have taken and only wait up to the max wait time @@ -338,7 +347,7 @@ func keyboardRoutine(ctx context.Context, cancelRoutines context.CancelFunc, chR for { select { case <-ctx.Done(): - chRender <- Command{Command: "clearConsole"} + chRender <- Command{Command: CommandTypeClearConsole} return case event := <-keysEvents: if event.Err != nil { @@ -352,7 +361,7 @@ func keyboardRoutine(ctx context.Context, cancelRoutines context.CancelFunc, chR } // Quit if event.Rune == 'q' || event.Rune == 'Q' || event.Key == keyboard.KeyCtrlC { - chRender <- Command{Command: "clearConsole"} + chRender <- Command{Command: CommandTypeClearConsole} cancelRoutines() return } @@ -386,7 +395,7 @@ func keyboardRoutine(ctx context.Context, cancelRoutines context.CancelFunc, chR } // Re-render Table - chRender <- Command{Command: "renderTable"} + chRender <- Command{Command: CommandTypeRenderTable} } } } @@ -401,13 +410,13 @@ func renderRoutine(ctx context.Context, wg *sync.WaitGroup, commands <-chan Comm runtime.Goexit() case cmd := <-commands: switch cmd.Command { - case "quit": + case CommandTypeQuit: return - case "clearConsole": + case CommandTypeClearConsole: fmt.Print("\033[H\033[2J") // Rewrite the whole console output - case "renderTable": + case CommandTypeRenderTable: // Rewrite the whole table to allow a down scale of the query history column table := tablewriter.NewWriter(writer) table.SetHeader( From 6682ef6539f1aea1cfe291f092305b9013e14dfc Mon Sep 17 00:00:00 2001 From: Florian Bauer Date: Wed, 6 Jul 2022 19:25:02 +0200 Subject: [PATCH 6/6] do not clear console at quit --- main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.go b/main.go index fa1b3fd..0fc0e27 100644 --- a/main.go +++ b/main.go @@ -347,7 +347,6 @@ func keyboardRoutine(ctx context.Context, cancelRoutines context.CancelFunc, chR for { select { case <-ctx.Done(): - chRender <- Command{Command: CommandTypeClearConsole} return case event := <-keysEvents: if event.Err != nil { @@ -361,7 +360,6 @@ func keyboardRoutine(ctx context.Context, cancelRoutines context.CancelFunc, chR } // Quit if event.Rune == 'q' || event.Rune == 'Q' || event.Key == keyboard.KeyCtrlC { - chRender <- Command{Command: CommandTypeClearConsole} cancelRoutines() return }