├── README.md ├── go.mod ├── go.sum ├── sloth.go ├── sloth_test.go ├── table └── table.go ├── terminal └── terminal.go └── validator └── validator.go /README.md: -------------------------------------------------------------------------------- 1 | # 🦥 Sloth 2 | 3 | Sloths are adorable but a bit too slow. You don't want your website be like sloth. -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stonedem0/sloth 2 | 3 | require ( 4 | github.com/logrusorgru/aurora v2.0.3+incompatible 5 | github.com/stonedem0/roti v0.0.0-20200423165933-ec05f3ec4c86 6 | github.com/stonedem0/tofu v0.0.0-20200812165329-874d3908be59 7 | ) 8 | 9 | replace github.com/stonedem0/roti => /Users/anastasiia/Github/roti 10 | 11 | replace github.com/stonedem0/tofu => /Users/anastasiia/Github/tofu 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= 2 | github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 3 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 4 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 5 | github.com/stonedem0/roti v0.0.0-20200423165933-ec05f3ec4c86 h1:gPceR2gz5J1j35nY4w+HTGMex05T9tQ/AfYnNOt5DII= 6 | github.com/stonedem0/roti v0.0.0-20200423165933-ec05f3ec4c86/go.mod h1:rOcqRSEUnj6uS5Wv0RB89Aa4ZwH1MyqqQMQYKcy2vNU= 7 | github.com/stonedem0/tofu v0.0.0-20200812165329-874d3908be59 h1:FW250CuFgBaDN+6YQiGrYQHZ+zmYCfrRHPoqSgUm8kI= 8 | github.com/stonedem0/tofu v0.0.0-20200812165329-874d3908be59/go.mod h1:rEnyAZZvUog9mwpoAghbj8s6NapIrTdbe2PhxzKfoh0= 9 | -------------------------------------------------------------------------------- /sloth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | "github.com/stonedem0/sloth/table" 12 | "github.com/stonedem0/sloth/terminal" 13 | "github.com/stonedem0/sloth/validator" 14 | 15 | "github.com/logrusorgru/aurora" 16 | "github.com/stonedem0/tofu" 17 | ) 18 | 19 | const ( 20 | softPink = 213 21 | purple = 57 22 | ) 23 | 24 | func main() { 25 | t := tofu.ProgressBarStr{} 26 | count := flag.Int("count", 10, "number of requests") 27 | flag.Parse() 28 | urls := flag.Args() 29 | argsWithoutProg := os.Args[1:] 30 | 31 | if len(argsWithoutProg) == 0 { 32 | slothMsg := "Hi. It's Sloth. Looks like you're trying to talk to me. Try some of these commands:\n" 33 | commads := " • [urls]: list of URls for testing " 34 | fmt.Printf("%s %s", aurora.Index(57, slothMsg), aurora.Index(201, commads)) 35 | os.Exit(1) 36 | } 37 | 38 | for _, u := range urls { 39 | validator.URLValidator(u) 40 | } 41 | 42 | results := make(chan Result) 43 | total := len(urls) * *count 44 | m := map[string][]time.Duration{} 45 | errors := map[string][]error{} 46 | 47 | // Terminal setup 48 | terminal.CleanTerminalScreen() 49 | terminal.MoveCursorUpperLeft() 50 | terminal.HideCursor() 51 | 52 | go Sloth(urls, *count, results) 53 | for a := 0; a < total; a++ { 54 | r := <-results 55 | m[r.URL] = append(m[r.URL], r.Duration) 56 | 57 | if r.Error == nil { 58 | t.ProgressBar(float32(a)/float32(total), 40, softPink, "▇", "░") 59 | t.PrintProgressBar() 60 | continue 61 | } 62 | errors[r.URL] = append(errors[r.URL], r.Error) 63 | } 64 | close(results) 65 | terminal.EraseProgressBar() 66 | terminal.MoveCursorUpperLeft() 67 | table.PrintTable(m, *count) 68 | for url, e := range errors { 69 | for _, err := range e { 70 | fmt.Printf("%s has an error:\n %s\n", aurora.Index(118, url), aurora.Index(197, err)) 71 | } 72 | } 73 | //Show cursor 74 | terminal.ShowCursor() 75 | } 76 | 77 | // Result ... 78 | type Result struct { 79 | Error error 80 | Index int 81 | Duration time.Duration 82 | URL string 83 | } 84 | 85 | // Sloth ... 86 | func Sloth(urls []string, count int, res chan Result) { 87 | var wg sync.WaitGroup 88 | wg.Add(len(urls) * count) 89 | for _, val := range urls { 90 | for a := 0; a < count; a++ { 91 | go func(val string, index int) { 92 | defer wg.Done() 93 | start := time.Now() 94 | r, err := http.Get(val) 95 | if err != nil { 96 | res <- Result{Index: index, URL: val, Error: err} 97 | return 98 | } 99 | defer r.Body.Close() 100 | elapsed := time.Since(start).Round(time.Millisecond) 101 | res <- Result{Index: index, Duration: elapsed, URL: val} 102 | }(val, a) 103 | } 104 | } 105 | wg.Wait() 106 | } 107 | -------------------------------------------------------------------------------- /sloth_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test(t *testing.T) { 8 | urls := []string{"http://stonedemo.wtf", "https://apex.sh"} 9 | results := make(chan Result) 10 | go Sloth(urls, results) 11 | for a := 0; a < len(urls); a++ { 12 | r := <-results 13 | if r.Error != nil { 14 | t.Fatalf("error: %s", r.Error) 15 | continue 16 | } 17 | } 18 | close(results) 19 | } 20 | -------------------------------------------------------------------------------- /table/table.go: -------------------------------------------------------------------------------- 1 | package table 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/stonedem0/roti" 7 | ) 8 | 9 | const ( 10 | softPink = 213 11 | purple = 57 12 | pad = " " 13 | ) 14 | 15 | func calculateResults(t []time.Duration, c int, url string) []string { 16 | var sum time.Duration 17 | results := []string{} 18 | min := t[0] 19 | max := t[1] 20 | for _, v := range t { 21 | sum = sum + v 22 | if v < min { 23 | min = v 24 | } 25 | if v > max { 26 | max = v 27 | } 28 | } 29 | average := sum / time.Duration(c) 30 | results = append(results, url, average.String(), min.String(), max.String()) 31 | return results 32 | } 33 | 34 | // PrintTable accepts map of results and print a table with it. 35 | func PrintTable(m map[string][]time.Duration, c int) { 36 | t := roti.Table{} 37 | headers := []string{"URL", "average", "min", "max"} 38 | t.AddHeader(headers, softPink, purple, 3) 39 | for k, v := range m { 40 | runes := []rune(k) 41 | prettyURL := string(runes[8:]) 42 | t.AddRow(calculateResults(v, c, prettyURL), purple, softPink, 3) 43 | 44 | } 45 | println(t.String()) 46 | 47 | } 48 | -------------------------------------------------------------------------------- /terminal/terminal.go: -------------------------------------------------------------------------------- 1 | package terminal 2 | 3 | import "fmt" 4 | 5 | //CleanTerminalScreen just cleans current terminal screen before progress bar printing. 6 | func CleanTerminalScreen() { 7 | fmt.Printf("\033[2J") 8 | } 9 | 10 | //MoveCursorUpperLeft moves cursor so t result able will be printed on upper left corner 11 | func MoveCursorUpperLeft() { 12 | fmt.Printf("\033[f") 13 | } 14 | 15 | // HideCursor hides cursor during progress bar and result printing 16 | func HideCursor() { 17 | fmt.Printf("\033[?25l") 18 | } 19 | 20 | //ShowCursor brings cursor back 21 | func ShowCursor() { 22 | fmt.Printf("\033[?25h") 23 | } 24 | 25 | //EraseProgressBar erases printed progressBar 26 | func EraseProgressBar() { 27 | fmt.Printf("\033[1K") 28 | } 29 | -------------------------------------------------------------------------------- /validator/validator.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | 8 | "github.com/logrusorgru/aurora" 9 | ) 10 | 11 | //URLValidator validates ursl from command arguments and print error and exits in case of invalid url 12 | func URLValidator(u string) { 13 | _, err := url.ParseRequestURI(u) 14 | if err != nil { 15 | error := fmt.Errorf("%v", aurora.Index(197, err)) 16 | fmt.Println(error.Error()) 17 | os.Exit(1) 18 | } 19 | } 20 | --------------------------------------------------------------------------------