├── Gopkg.lock ├── Gopkg.toml ├── README.md ├── automation └── automation.go ├── benchmark ├── benchmark.go ├── heavywebsites.go ├── http.go ├── https.go ├── stress.go └── websites.go ├── config ├── config.go ├── proxy.go └── reader.go ├── launcher └── launcher.go ├── main.go ├── proxybench.json.sample ├── reporter └── reporter.go ├── result └── result.go └── suite └── suite.go /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/blang/semver" 6 | packages = ["."] 7 | revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f" 8 | version = "v3.5.1" 9 | 10 | [[projects]] 11 | name = "github.com/mattn/go-runewidth" 12 | packages = ["."] 13 | revision = "9e777a8366cce605130a531d2cd6363d07ad7317" 14 | version = "v0.0.2" 15 | 16 | [[projects]] 17 | branch = "master" 18 | name = "github.com/olekukonko/tablewriter" 19 | packages = ["."] 20 | revision = "b8a9be070da40449e501c3c4730a889e42d87a9e" 21 | 22 | [[projects]] 23 | branch = "master" 24 | name = "github.com/tebeka/selenium" 25 | packages = [ 26 | ".", 27 | "chrome", 28 | "firefox", 29 | "internal/zip", 30 | "log" 31 | ] 32 | revision = "a789e65b0e7f126888873e84f528c1c8537dff3e" 33 | 34 | [solve-meta] 35 | analyzer-name = "dep" 36 | analyzer-version = 1 37 | inputs-digest = "75c3e0167edc568a6a76489cde42f6d0add8be4ff6235c18c43d290c985f7a67" 38 | solver-name = "gps-cdcl" 39 | solver-version = 1 40 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "github.com/olekukonko/tablewriter" 31 | 32 | [[constraint]] 33 | branch = "master" 34 | name = "github.com/tebeka/selenium" 35 | 36 | [prune] 37 | go-tests = true 38 | unused-packages = true 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## proxybench 2 | Benchmark different proxies on basis of the following: 3 | 4 | | test | Implemented | Description | 5 | |:------------------------|:-------------------:|:------------| 6 | |HTTP Stress | :white_check_mark: | records the time it takes to finish the test on http://httpvshttps.com | 7 | |HTTPS Stress (HTTP/2) | :white_check_mark: | records the time it takes to finish the test on https://httpvshttps.com | 8 | | Heavy Websites | :white_check_mark: | records the time it takes to open websites like https://techcrunch.com/ | 9 | |HTTPS Stress (HTTP/1.1) | || 10 | |WS Support | || 11 | |WSS Support | || 12 | |\ | || 13 | 14 | ## Installation 15 | * `go get github.com/pulkitsharma07/proxybench` 16 | 17 | ## Running the benchmark 18 | * Make sure you have `chromedriver` present in your PATH. 19 | * Start the desired proxies on different ports. 20 | * Make sure you add and trust the certificates provided by the respective proxies, so that they can be considered as a trusted CA by the browser. 21 | * Refer `proxybench.json.sample` and create `proxybench.json` similarly. 22 | * Launch `proxybench`, it will look for `proxybench.json` in the current directory and run according to that config. 23 | 24 | ## Reporting 25 | Generates the following: 26 |
27 | +--------------------------------+----------------+------------------------+
28 | |          PROXY CONFIG          |   BENCHMARK    | COMPLETED IN (SECONDS) |
29 | +--------------------------------+----------------+------------------------+
30 | | Proxy: [Direct]                | HTTP Stress    | [15.575000]            |
31 | | Proxy: [Direct]                | HTTPS Stress   | [2.757000]             |
32 | | Proxy: [Direct]                | Heavy Websites | [20.703525]            |
33 | | Proxy:                         | HTTP Stress    | [17.920000]            |
34 | | [browsermob(littleproxy)]      |                |                        |
35 | | Proxy:                         | HTTPS Stress   | [17.053000]            |
36 | | [browsermob(littleproxy)]      |                |                        |
37 | | Proxy:                         | Heavy Websites | [40.902084]            |
38 | | [browsermob(littleproxy)]      |                |                        |
39 | | Proxy: [browsermob(legacy)]    | HTTP Stress    | [20.777000]            |
40 | | Proxy: [browsermob(legacy)]    | HTTPS Stress   | [17.399000]            |
41 | | Proxy: [browsermob(legacy)]    | Heavy Websites | [31.580184]            |
42 | | Proxy: [mitmproxy]             | HTTP Stress    | [39.376000]            |
43 | | Proxy: [mitmproxy]             | HTTPS Stress   | [47.631000]            |
44 | | Proxy: [mitmproxy]             | Heavy Websites | [198.824246]           |
45 | | Proxy: [mitmdump]              | HTTP Stress    | [17.960000]            |
46 | | Proxy: [mitmdump]              | HTTPS Stress   | [8.147000]             |
47 | | Proxy: [mitmdump]              | Heavy Websites | [41.605729]            |
48 | | Proxy: [charles]               | HTTP Stress    | [18.535000]            |
49 | | Proxy: [charles]               | HTTPS Stress   | [6.322000]             |
50 | | Proxy: [charles]               | Heavy Websites | [52.927620]            |
51 | +--------------------------------+----------------+------------------------+
52 | 
53 | * Generated using: browsermob v2.1.4, mitmproxy/mitmdump v3.0.3, charles 4.2.1 54 | * `Direct` stands for the baseline performance, i.e. without using any proxy. 55 | 56 | ## Development 57 | * Use `dep` to install dependencies 58 | * Add proxy information in `proxybench.json` 59 | * benchmarks are defined in `benchmarks/` 60 | * You can define new benchmarks similarly, add them to `NewSimpleSuite` to execute. 61 | * `go install` 62 | * `proxybench` to run (Assuming GOBIN is added to your PATH) 63 | 64 | ## TODO 65 | * Unit and integration tests. 66 | * CLI option to skip the baseline (DIRECT) test. 67 | * CLI options to launch tests in sync/async. 68 | * Add wrapper around `Run` to measure the proxy's CPU/Memory usage (Need to take PID from user, or figure out from script?).Eventually render a timeseries graph. 69 | * Randomize test order and most importantly do multiple test runs and report on standard deviation. (Thanks @mhills) 70 | * Test reports, generate test reports confining to some format, each test case will have corresponding timeseries graphs of CPU, memory, disk, number of sockets etc of the proxy. 71 | * Final plan is to give every proxy a score based on its performance in comparison to the baseline for the different benchmarks. 72 | * Launch proxies from this script? 73 | 74 | 75 | *PS: I made this mainly to learn `Go`. I will really appreciate if you can point out things in the code which I should improve upon. For example naming conventions, directory structure, or new benchmarks etc. Feel free to open any issues.* 76 | 77 | **Thank You !** 78 | -------------------------------------------------------------------------------- /automation/automation.go: -------------------------------------------------------------------------------- 1 | package automation 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/pulkitsharma07/proxybench/config" 11 | "github.com/tebeka/selenium" 12 | "github.com/tebeka/selenium/chrome" 13 | ) 14 | 15 | //TODO: Add checks to instance variables of all the structs 16 | 17 | type Driver struct { 18 | pathToExecutable string 19 | Port int 20 | process *selenium.Service 21 | } 22 | 23 | // Starts chromedriver on random port. 24 | func NewChromeDriver() *Driver { 25 | listener, err := net.Listen("tcp", "127.0.0.1:0") 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | port, err := strconv.Atoi(strings.Split(listener.Addr().String(), ":")[1]) 31 | if err != nil { 32 | panic(err) 33 | } 34 | listener.Close() 35 | 36 | return &Driver{ 37 | "chromedriver", 38 | port, 39 | &selenium.Service{}, 40 | } 41 | } 42 | 43 | func (d *Driver) Start() { 44 | service, err := selenium.NewChromeDriverService(d.pathToExecutable, d.Port) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | d.process = service 49 | } 50 | 51 | func (d *Driver) Stop() { 52 | d.process.Stop() 53 | } 54 | 55 | type BrowserAutomator struct { 56 | driver *Driver 57 | Wd selenium.WebDriver 58 | } 59 | 60 | func NewChromeAutomator(proxyConf ...config.Proxy) *BrowserAutomator { 61 | chromedriver := NewChromeDriver() 62 | chromedriver.Start() 63 | 64 | caps := selenium.Capabilities{"browserName": "chrome"} 65 | 66 | if len(proxyConf) > 0 && proxyConf[0].Address != "" { 67 | chromeCaps := chrome.Capabilities{ 68 | Args: []string{"--proxy-server=" + proxyConf[0].Address}, 69 | } 70 | caps.AddChrome(chromeCaps) 71 | } 72 | 73 | wd, err := selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", chromedriver.Port)) 74 | 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | 79 | return &BrowserAutomator{ 80 | chromedriver, 81 | wd, 82 | } 83 | } 84 | 85 | func (b *BrowserAutomator) Stop() { 86 | b.Wd.Quit() 87 | b.driver.Stop() 88 | } 89 | -------------------------------------------------------------------------------- /benchmark/benchmark.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "github.com/pulkitsharma07/proxybench/config" 5 | "github.com/pulkitsharma07/proxybench/result" 6 | ) 7 | 8 | // TestCase generates testResult 9 | type Benchmark interface { 10 | // Gets the Benchmark's Name For Example: "HTTP Stress" 11 | String() string 12 | 13 | // Runs the benchmark using by launching a WebDriver compatible server listening 14 | // on a random port. 15 | Run(config config.Config) 16 | 17 | // Should return the result(s) generated for this benchmark 18 | Results() result.Result 19 | } 20 | -------------------------------------------------------------------------------- /benchmark/heavywebsites.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | func HeavyWebsites() *Websites { 4 | // Only one website for now, as Chromedriver crashes for some reason and return EOF for open url request. 5 | return NewWebsites("Heavy Websites", []string{"https://www.techcrunch.com"}) 6 | } 7 | -------------------------------------------------------------------------------- /benchmark/http.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | func StressHTTP() *Stress { 4 | return NewStress("HTTP Stress", false) 5 | } 6 | -------------------------------------------------------------------------------- /benchmark/https.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | func StressHTTPS() *Stress { 4 | return NewStress("HTTPS Stress", true) 5 | } 6 | -------------------------------------------------------------------------------- /benchmark/stress.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "time" 8 | 9 | "github.com/pulkitsharma07/proxybench/automation" 10 | "github.com/pulkitsharma07/proxybench/config" 11 | "github.com/pulkitsharma07/proxybench/result" 12 | "github.com/tebeka/selenium" 13 | ) 14 | 15 | // Stress implements Benchmark 16 | type Stress struct { 17 | name string 18 | url string 19 | result result.LoadTimeResult 20 | } 21 | 22 | func NewStress(name string, https bool) *Stress { 23 | if https { 24 | return &Stress{ 25 | name, 26 | "https://www.httpvshttps.com", 27 | result.LoadTimeResult{}, 28 | } 29 | } else { 30 | return &Stress{ 31 | name, 32 | "http://www.httpvshttps.com", 33 | result.LoadTimeResult{}, 34 | } 35 | } 36 | } 37 | 38 | func (t *Stress) String() string { 39 | return t.name 40 | } 41 | 42 | func (t *Stress) Results() result.Result { 43 | return t.result 44 | } 45 | 46 | func (t *Stress) Run(config config.Config) { 47 | chrome := automation.NewChromeAutomator(config.ProxyToUse) 48 | defer chrome.Stop() 49 | wd := chrome.Wd 50 | 51 | if err := wd.Get(t.url); err != nil { 52 | panic(err) 53 | } 54 | 55 | // Fetch the elapsed time from the DOM. 56 | // Wait just to ensure that page has completely loaded and timer has stopped. 57 | time.Sleep(2 * time.Second) 58 | 59 | // Get the timer DOM element 60 | timeToLoad, err := wd.FindElement(selenium.ByCSSSelector, "#time") 61 | if err != nil { 62 | fmt.Printf("Unable to find score..\n") 63 | t.result = result.NewLoadTimeResult(config, t.name+" (FAILED)", "PageLoadTime(sec)", -1.0) 64 | return 65 | } 66 | 67 | // Parse score to float (Will help in computing other things in future) 68 | timeToLoadStr, err := timeToLoad.Text() 69 | duration, err := strconv.ParseFloat(strings.Replace(timeToLoadStr, " s", "", -1), 64) 70 | 71 | t.result = result.NewLoadTimeResult(config, t.name, "PageLoadTime(sec)", duration) 72 | } 73 | -------------------------------------------------------------------------------- /benchmark/websites.go: -------------------------------------------------------------------------------- 1 | package benchmark 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/pulkitsharma07/proxybench/automation" 8 | "github.com/pulkitsharma07/proxybench/config" 9 | "github.com/pulkitsharma07/proxybench/result" 10 | ) 11 | 12 | // Websites implements Benchmark 13 | type Websites struct { 14 | name string 15 | urls []string 16 | result result.LoadTimeResult 17 | } 18 | 19 | func NewWebsites(name string, urls []string) *Websites { 20 | return &Websites{ 21 | name, 22 | urls, 23 | result.LoadTimeResult{}, 24 | } 25 | } 26 | 27 | func (t *Websites) String() string { 28 | return t.name 29 | } 30 | 31 | func (t *Websites) Results() result.Result { 32 | return t.result 33 | } 34 | 35 | func (t *Websites) Run(config config.Config) { 36 | chrome := automation.NewChromeAutomator(config.ProxyToUse) 37 | defer chrome.Stop() 38 | wd := chrome.Wd 39 | 40 | start := time.Now() 41 | 42 | for _, url := range t.urls { 43 | if err := wd.Get(url); err != nil { 44 | fmt.Printf("Failed because of: %+v\n", err) 45 | elapsed := time.Now().Sub(start) 46 | t.result = result.NewLoadTimeResult(config, t.name, "PageLoadTime(sec)", elapsed.Seconds()) 47 | return 48 | } 49 | } 50 | elapsed := time.Now().Sub(start) 51 | t.result = result.NewLoadTimeResult(config, t.name, "PageLoadTime(sec)", elapsed.Seconds()) 52 | } 53 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "fmt" 4 | 5 | type Config struct { 6 | ProxyToUse Proxy 7 | } 8 | 9 | func (c Config) String() string { 10 | return fmt.Sprintf("Proxy: [%s]", c.ProxyToUse) 11 | } 12 | -------------------------------------------------------------------------------- /config/proxy.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "fmt" 4 | 5 | type Proxy struct { 6 | Name string `json:"name"` 7 | Address string `json:"address"` 8 | } 9 | 10 | func (p Proxy) String() string { 11 | return fmt.Sprintf("%s", p.Name) 12 | } 13 | -------------------------------------------------------------------------------- /config/reader.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | type ConfigJson struct { 9 | Proxies []Proxy `json:"proxies"` 10 | } 11 | 12 | func ReadConfig() []Config { 13 | fileReader, err := os.Open("proxybench.json") 14 | if err != nil { 15 | panic("Cannot find proxybench.json") 16 | } 17 | 18 | var configJson ConfigJson 19 | json.NewDecoder(fileReader).Decode(&configJson) 20 | 21 | suiteConfig := []Config{{Proxy{"Direct", ""}}} 22 | 23 | for _, proxy := range configJson.Proxies { 24 | suiteConfig = append(suiteConfig, Config{proxy}) 25 | } 26 | 27 | return suiteConfig 28 | } 29 | -------------------------------------------------------------------------------- /launcher/launcher.go: -------------------------------------------------------------------------------- 1 | package launcher 2 | 3 | import ( 4 | "github.com/pulkitsharma07/proxybench/config" 5 | "github.com/pulkitsharma07/proxybench/suite" 6 | ) 7 | 8 | func Launch(suiteConfig []config.Config) *suite.Suite { 9 | Suite := suite.NewSimpleSuite(suiteConfig) 10 | Suite.Run() 11 | 12 | return Suite 13 | } 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/pulkitsharma07/proxybench/config" 5 | "github.com/pulkitsharma07/proxybench/launcher" 6 | "github.com/pulkitsharma07/proxybench/reporter" 7 | ) 8 | 9 | func main() { 10 | suite := launcher.Launch(config.ReadConfig()) 11 | reporter.ShowReport(suite.Results()) 12 | } 13 | -------------------------------------------------------------------------------- /proxybench.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "proxies" : [ 3 | { 4 | "name": "browsermob(littleproxy)", 5 | "address": "localhost:8081" 6 | }, 7 | { 8 | "name": "browsermob(legacy)", 9 | "address": "localhost:8084" 10 | }, 11 | { 12 | "name": "mitmproxy", 13 | "address": "localhost:8085" 14 | }, 15 | { 16 | "name": "mitmdump", 17 | "address": "localhost:8086" 18 | }, 19 | { 20 | "name": "charles", 21 | "address": "localhost:8888" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /reporter/reporter.go: -------------------------------------------------------------------------------- 1 | package reporter 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/olekukonko/tablewriter" 8 | "github.com/pulkitsharma07/proxybench/result" 9 | ) 10 | 11 | func ShowReport(results []result.Result) { 12 | data := [][]string{} 13 | 14 | for _, result := range results { 15 | data = append(data, []string{result.Config().String(), result.Benchmark(), fmt.Sprintf("%+v", result.Results())}) 16 | } 17 | 18 | table := tablewriter.NewWriter(os.Stdout) 19 | table.SetHeader([]string{"Proxy Config", "Benchmark", "Completed in (seconds)"}) 20 | 21 | table.SetHeaderColor( 22 | tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor}, 23 | tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor}, 24 | tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor}, 25 | ) 26 | 27 | table.SetColumnColor( 28 | tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiGreenColor}, 29 | tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor}, 30 | tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor}, 31 | ) 32 | 33 | table.AppendBulk(data) 34 | fmt.Print("\nREPORT\n") 35 | table.Render() 36 | } 37 | -------------------------------------------------------------------------------- /result/result.go: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pulkitsharma07/proxybench/config" 7 | ) 8 | 9 | // testResultField has the actual result for example: 10 | // "duration": 2.33 11 | type Field struct { 12 | Name string 13 | Value float64 // TODO: make this generic 14 | } 15 | 16 | func (f Field) String() string { 17 | return fmt.Sprintf("%f", f.Value) 18 | } 19 | 20 | // Result has many Fiedls 21 | type Result interface { 22 | Results() []Field 23 | 24 | // Should pretty print the results 25 | String() string 26 | 27 | // Should provide the name of the benchmark it is the result of. 28 | Benchmark() string 29 | 30 | // Should provide the Config with which this benchmark was run 31 | Config() config.Config 32 | } 33 | 34 | //LoadTimeResult is a Result 35 | type LoadTimeResult struct { 36 | config config.Config 37 | benchmark string 38 | results []Field 39 | } 40 | 41 | func NewLoadTimeResult(config config.Config, benchmark string, name string, duration float64) LoadTimeResult { 42 | return LoadTimeResult{ 43 | config, 44 | benchmark, 45 | []Field{{ 46 | name, 47 | duration, 48 | }}, 49 | } 50 | } 51 | 52 | func (l LoadTimeResult) Results() []Field { 53 | return l.results 54 | } 55 | 56 | func (l LoadTimeResult) Benchmark() string { 57 | return l.benchmark 58 | } 59 | 60 | func (l LoadTimeResult) Config() config.Config { 61 | return l.config 62 | } 63 | 64 | func (l LoadTimeResult) String() string { 65 | return fmt.Sprintf("Config: %v, Benchmark: %v, Result: %v", l.config, l.benchmark, l.results) 66 | } 67 | -------------------------------------------------------------------------------- /suite/suite.go: -------------------------------------------------------------------------------- 1 | package suite 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pulkitsharma07/proxybench/benchmark" 7 | "github.com/pulkitsharma07/proxybench/config" 8 | "github.com/pulkitsharma07/proxybench/result" 9 | ) 10 | 11 | type Suite struct { 12 | config []config.Config 13 | tests []benchmark.Benchmark 14 | results []result.Result 15 | } 16 | 17 | // Generates a new Suite, Generates a predefined list of benchmarks to execute 18 | // for each proxy. 19 | func NewSimpleSuite(config []config.Config) *Suite { 20 | return &Suite{ 21 | config, 22 | []benchmark.Benchmark{ 23 | benchmark.StressHTTP(), 24 | benchmark.StressHTTPS(), 25 | benchmark.HeavyWebsites(), 26 | }, 27 | []result.Result{}, 28 | } 29 | } 30 | 31 | func (t *Suite) Run() { 32 | fmt.Printf("Starting with Config: %v\n", t.config) 33 | 34 | for _, benchConfig := range t.config { 35 | t.executeSync(benchConfig) 36 | } 37 | } 38 | 39 | func (t *Suite) String() string { 40 | return fmt.Sprintf("ProxyConfig: %v, tests: %v \n", t.config, t.tests) 41 | } 42 | 43 | func (t *Suite) pushResults(res result.Result) { 44 | t.results = append(t.results, res) 45 | } 46 | 47 | func (t *Suite) Results() []result.Result { 48 | return t.results 49 | } 50 | 51 | func (t *Suite) executeSync(benchConfig config.Config) { 52 | // Launch tests in Sync 53 | for _, test := range t.tests { 54 | fmt.Printf("\t\tStarting %+v\n", test) 55 | test.Run(benchConfig) 56 | t.pushResults(test.Results()) 57 | } 58 | } 59 | --------------------------------------------------------------------------------