mirror of
				https://github.com/aykhans/dodo.git
				synced 2025-10-29 03:39:20 +00:00 
			
		
		
		
	Compare commits
	
		
			22 Commits
		
	
	
		
			v0.5.2
			...
			04e5b5f3eb
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 04e5b5f3eb | |||
| 2bebf09adb | |||
| ebacebff16 | |||
| 575ca2913a | |||
| 4686e26ede | |||
| bd33b2c6a2 | |||
| 3068e0acae | |||
| 7a40bbbd3c | |||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 43c0bd2138 | ||
| fddeec1791 | |||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 6f5fa1860c | ||
| 4480aed0f0 | |||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | f5144ac4be | ||
| 4a1051e08c | |||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 2371986909 | ||
| e9af46084d | |||
| cdaa63152f | |||
| 16791421c7 | |||
| c90cfd8090 | |||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 1aaea8e437 | ||
| c44a0c978a | |||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 1ace7be2b4 | 
| @@ -49,7 +49,7 @@ dodo -u https://example.com -m GET -d 10 -r 1000 -t 2000 | ||||
| ``` | ||||
| With Docker: | ||||
| ```sh | ||||
| docker run --rm aykhans/dodo -u https://example.com -m GET -d 10 -r 1000 -t 2000 | ||||
| docker run --rm -i aykhans/dodo -u https://example.com -m GET -d 10 -r 1000 -t 2000 | ||||
| ``` | ||||
|  | ||||
| ### 2. JSON config file | ||||
| @@ -85,7 +85,7 @@ dodo -c /path/config.json | ||||
| ``` | ||||
| With Docker: | ||||
| ```sh | ||||
| docker run --rm -v ./path/config.json:/dodo/config.json -i aykhans/dodo | ||||
| docker run --rm -i -v ./path/config.json:/dodo/config.json aykhans/dodo | ||||
| ``` | ||||
|  | ||||
| ### 3. Both (CLI & JSON) | ||||
| @@ -96,7 +96,7 @@ dodo -c /path/config.json -u https://example.com -m GET -d 10 -r 1000 -t 2000 | ||||
| ``` | ||||
| With Docker: | ||||
| ```sh | ||||
| docker run --rm -v ./path/config.json:/dodo/config.json -i aykhans/dodo -u https://example.com -m GET -d 10 -r 1000 -t 2000 | ||||
| docker run --rm -i -v ./path/config.json:/dodo/config.json aykhans/dodo -u https://example.com -m GET -d 10 -r 1000 -t 2000 | ||||
| ``` | ||||
|  | ||||
| ## CLI and JSON Config Parameters | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|     "method": "GET", | ||||
|     "url": "https://example.com", | ||||
|     "no_proxy_check": true, | ||||
|     "no_proxy_check": false, | ||||
|     "timeout": 10000, | ||||
|     "dodos_count": 50, | ||||
|     "request_count": 1000, | ||||
| @@ -19,4 +19,4 @@ | ||||
|             "url": "http://example.com:8080" | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -6,12 +6,13 @@ import ( | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	. "github.com/aykhans/dodo/types" | ||||
| 	"github.com/aykhans/dodo/utils" | ||||
| 	"github.com/jedib0t/go-pretty/v6/table" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	VERSION                 string = "0.5.2" | ||||
| 	VERSION                 string = "0.5.3" | ||||
| 	DefaultUserAgent        string = "Dodo/" + VERSION | ||||
| 	ProxyCheckURL           string = "https://www.google.com" | ||||
| 	DefaultMethod           string = "GET" | ||||
| @@ -92,12 +93,12 @@ func (config *RequestConfig) GetMaxConns(minConns uint) uint { | ||||
| } | ||||
|  | ||||
| type Config struct { | ||||
| 	Method       string             `json:"method" validate:"http_method"` // custom validations: http_method | ||||
| 	URL          string             `json:"url" validate:"http_url,required"` | ||||
| 	Timeout      uint32             `json:"timeout" validate:"gte=1,lte=100000"` | ||||
| 	DodosCount   uint               `json:"dodos_count" validate:"gte=1"` | ||||
| 	RequestCount uint               `json:"request_count" validation_name:"request-count" validate:"gte=1"` | ||||
| 	NoProxyCheck utils.Option[bool] `json:"no_proxy_check"` | ||||
| 	Method       string       `json:"method" validate:"http_method"` // custom validations: http_method | ||||
| 	URL          string       `json:"url" validate:"http_url,required"` | ||||
| 	Timeout      uint32       `json:"timeout" validate:"gte=1,lte=100000"` | ||||
| 	DodosCount   uint         `json:"dodos_count" validate:"gte=1"` | ||||
| 	RequestCount uint         `json:"request_count" validation_name:"request-count" validate:"gte=1"` | ||||
| 	NoProxyCheck Option[bool] `json:"no_proxy_check"` | ||||
| } | ||||
|  | ||||
| func NewConfig( | ||||
| @@ -105,10 +106,10 @@ func NewConfig( | ||||
| 	timeout uint32, | ||||
| 	dodosCount uint, | ||||
| 	requestCount uint, | ||||
| 	noProxyCheck utils.Option[bool], | ||||
| 	noProxyCheck Option[bool], | ||||
| ) *Config { | ||||
| 	if noProxyCheck == nil { | ||||
| 		noProxyCheck = utils.NewNoneOption[bool]() | ||||
| 		noProxyCheck = NewNoneOption[bool]() | ||||
| 	} | ||||
|  | ||||
| 	return &Config{ | ||||
| @@ -155,7 +156,7 @@ func (config *Config) SetDefaults() { | ||||
| 		config.RequestCount = DefaultRequestCount | ||||
| 	} | ||||
| 	if config.NoProxyCheck.IsNone() { | ||||
| 		config.NoProxyCheck = utils.NewOption(false) | ||||
| 		config.NoProxyCheck = NewOption(false) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										14
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								go.mod
									
									
									
									
									
								
							| @@ -4,11 +4,11 @@ go 1.23.2 | ||||
|  | ||||
| require ( | ||||
| 	github.com/go-playground/validator/v10 v10.23.0 | ||||
| 	github.com/jedib0t/go-pretty/v6 v6.6.2 | ||||
| 	github.com/jedib0t/go-pretty/v6 v6.6.5 | ||||
| 	github.com/spf13/cobra v1.8.1 | ||||
| 	github.com/spf13/pflag v1.0.5 | ||||
| 	github.com/valyala/fasthttp v1.57.0 | ||||
| 	golang.org/x/net v0.31.0 | ||||
| 	github.com/valyala/fasthttp v1.58.0 | ||||
| 	golang.org/x/net v0.32.0 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| @@ -22,8 +22,8 @@ require ( | ||||
| 	github.com/mattn/go-runewidth v0.0.15 // indirect | ||||
| 	github.com/rivo/uniseg v0.4.7 // indirect | ||||
| 	github.com/valyala/bytebufferpool v1.0.0 // indirect | ||||
| 	golang.org/x/crypto v0.29.0 // indirect | ||||
| 	golang.org/x/sys v0.27.0 // indirect | ||||
| 	golang.org/x/term v0.26.0 // indirect | ||||
| 	golang.org/x/text v0.20.0 // indirect | ||||
| 	golang.org/x/crypto v0.31.0 // indirect | ||||
| 	golang.org/x/sys v0.28.0 // indirect | ||||
| 	golang.org/x/term v0.27.0 // indirect | ||||
| 	golang.org/x/text v0.21.0 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										28
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								go.sum
									
									
									
									
									
								
							| @@ -15,8 +15,8 @@ github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL | ||||
| github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= | ||||
| github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||||
| github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||||
| github.com/jedib0t/go-pretty/v6 v6.6.2 h1:27bLj3nRODzaiA7tPIxy9UVWHoPspFfME9XxgwiiNsM= | ||||
| github.com/jedib0t/go-pretty/v6 v6.6.2/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= | ||||
| github.com/jedib0t/go-pretty/v6 v6.6.5 h1:9PgMJOVBedpgYLI56jQRJYqngxYAAzfEUua+3NgSqAo= | ||||
| github.com/jedib0t/go-pretty/v6 v6.6.5/go.mod h1:Uq/HrbhuFty5WSVNfjpQQe47x16RwVGXIveNGEyGtHs= | ||||
| github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= | ||||
| github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= | ||||
| github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= | ||||
| @@ -37,20 +37,20 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU | ||||
| github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||
| github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||
| github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||||
| github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg= | ||||
| github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE= | ||||
| github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= | ||||
| github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= | ||||
| github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= | ||||
| github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= | ||||
| golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= | ||||
| golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= | ||||
| golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= | ||||
| golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= | ||||
| golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= | ||||
| golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= | ||||
| golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= | ||||
| golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= | ||||
| golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= | ||||
| golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= | ||||
| golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= | ||||
| golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= | ||||
| golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= | ||||
| golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= | ||||
| golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= | ||||
| golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= | ||||
| golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= | ||||
| golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
|  | ||||
| 	"github.com/aykhans/dodo/config" | ||||
| 	"github.com/aykhans/dodo/custom_errors" | ||||
| 	. "github.com/aykhans/dodo/types" | ||||
| 	"github.com/aykhans/dodo/utils" | ||||
| 	"github.com/spf13/cobra" | ||||
| 	"github.com/spf13/pflag" | ||||
| @@ -61,7 +62,7 @@ func CLIConfigReader() (*config.CLIConfig, error) { | ||||
| 		case "timeout": | ||||
| 			cliConfig.Timeout = timeout | ||||
| 		case "no-proxy-check": | ||||
| 			cliConfig.NoProxyCheck = utils.NewOption(noProxyCheck) | ||||
| 			cliConfig.NoProxyCheck = NewOption(noProxyCheck) | ||||
| 		} | ||||
| 	}) | ||||
| 	if returnNil { | ||||
|   | ||||
| @@ -1,85 +1,106 @@ | ||||
| package requests | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/aykhans/dodo/utils" | ||||
| 	. "github.com/aykhans/dodo/types" | ||||
| 	"github.com/jedib0t/go-pretty/v6/table" | ||||
| ) | ||||
|  | ||||
| type Response struct { | ||||
| 	StatusCode int | ||||
| 	Error      error | ||||
| 	Time       time.Duration | ||||
| 	Response string | ||||
| 	Time     time.Duration | ||||
| } | ||||
|  | ||||
| type Responses []*Response | ||||
|  | ||||
| // Print prints the responses in a tabular format, including information such as | ||||
| // response count, minimum time, maximum time, and average time. | ||||
| // response count, minimum time, maximum time, average time, and latency percentiles. | ||||
| func (respones Responses) Print() { | ||||
| 	var ( | ||||
| 		totalMinDuration time.Duration = respones[0].Time | ||||
| 		totalMaxDuration time.Duration = respones[0].Time | ||||
| 		totalDuration    time.Duration | ||||
| 		totalCount       int = len(respones) | ||||
| 	) | ||||
| 	mergedResponses := make(map[string][]time.Duration) | ||||
| 	total := struct { | ||||
| 		Count int | ||||
| 		Min   time.Duration | ||||
| 		Max   time.Duration | ||||
| 		Sum   time.Duration | ||||
| 		P90   time.Duration | ||||
| 		P95   time.Duration | ||||
| 		P99   time.Duration | ||||
| 	}{ | ||||
| 		Count: len(respones), | ||||
| 		Min:   respones[0].Time, | ||||
| 		Max:   respones[0].Time, | ||||
| 	} | ||||
| 	mergedResponses := make(map[string]Durations) | ||||
| 	var allDurations Durations | ||||
|  | ||||
| 	for _, response := range respones { | ||||
| 		if response.Time < totalMinDuration { | ||||
| 			totalMinDuration = response.Time | ||||
| 		if response.Time < total.Min { | ||||
| 			total.Min = response.Time | ||||
| 		} | ||||
| 		if response.Time > totalMaxDuration { | ||||
| 			totalMaxDuration = response.Time | ||||
| 		if response.Time > total.Max { | ||||
| 			total.Max = response.Time | ||||
| 		} | ||||
| 		totalDuration += response.Time | ||||
| 		total.Sum += response.Time | ||||
|  | ||||
| 		if response.Error != nil { | ||||
| 			mergedResponses[response.Error.Error()] = append( | ||||
| 				mergedResponses[response.Error.Error()], | ||||
| 				response.Time, | ||||
| 			) | ||||
| 		} else { | ||||
| 			mergedResponses[fmt.Sprintf("%d", response.StatusCode)] = append( | ||||
| 				mergedResponses[fmt.Sprintf("%d", response.StatusCode)], | ||||
| 				response.Time, | ||||
| 			) | ||||
| 		} | ||||
| 		mergedResponses[response.Response] = append( | ||||
| 			mergedResponses[response.Response], | ||||
| 			response.Time, | ||||
| 		) | ||||
| 		allDurations = append(allDurations, response.Time) | ||||
| 	} | ||||
| 	allDurations.Sort() | ||||
| 	allDurationsLenAsFloat := float64(len(allDurations) - 1) | ||||
| 	total.P90 = allDurations[int(0.90*allDurationsLenAsFloat)] | ||||
| 	total.P95 = allDurations[int(0.95*allDurationsLenAsFloat)] | ||||
| 	total.P99 = allDurations[int(0.99*allDurationsLenAsFloat)] | ||||
|  | ||||
| 	t := table.NewWriter() | ||||
| 	t.SetOutputMirror(os.Stdout) | ||||
| 	t.SetStyle(table.StyleLight) | ||||
| 	t.SetColumnConfigs([]table.ColumnConfig{ | ||||
| 		{Number: 1, WidthMax: 80}, | ||||
| 		{Number: 1, WidthMax: 40}, | ||||
| 	}) | ||||
|  | ||||
| 	t.AppendHeader(table.Row{ | ||||
| 		"Response", | ||||
| 		"Count", | ||||
| 		"Min Time", | ||||
| 		"Max Time", | ||||
| 		"Average Time", | ||||
| 		"P90", | ||||
| 		"P95", | ||||
| 		"P99", | ||||
| 	}) | ||||
|  | ||||
| 	for key, durations := range mergedResponses { | ||||
| 		durations.Sort() | ||||
| 		durationsLen := len(durations) | ||||
| 		durationsLenAsFloat := float64(durationsLen - 1) | ||||
|  | ||||
| 		t.AppendRow(table.Row{ | ||||
| 			key, | ||||
| 			len(durations), | ||||
| 			utils.MinDuration(durations...), | ||||
| 			utils.MaxDuration(durations...), | ||||
| 			utils.AvgDuration(durations...), | ||||
| 			durationsLen, | ||||
| 			durations.First(), | ||||
| 			durations.Last(), | ||||
| 			durations.Avg(), | ||||
| 			durations[int(0.90*durationsLenAsFloat)], | ||||
| 			durations[int(0.95*durationsLenAsFloat)], | ||||
| 			durations[int(0.99*durationsLenAsFloat)], | ||||
| 		}) | ||||
| 		t.AppendSeparator() | ||||
| 	} | ||||
| 	t.AppendRow(table.Row{ | ||||
| 		"Total", | ||||
| 		totalCount, | ||||
| 		totalMinDuration, | ||||
| 		totalMaxDuration, | ||||
| 		totalDuration / time.Duration(totalCount), | ||||
| 	}) | ||||
|  | ||||
| 	if len(mergedResponses) > 1 { | ||||
| 		t.AppendRow(table.Row{ | ||||
| 			"Total", | ||||
| 			total.Count, | ||||
| 			total.Min, | ||||
| 			total.Max, | ||||
| 			total.Sum / time.Duration(total.Count), // Average | ||||
| 			total.P90, | ||||
| 			total.P95, | ||||
| 			total.P99, | ||||
| 		}) | ||||
| 	} | ||||
| 	t.Render() | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package requests | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"strconv" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| @@ -142,18 +143,16 @@ func sendRequest( | ||||
| 					return | ||||
| 				} | ||||
| 				*responseData = append(*responseData, &Response{ | ||||
| 					StatusCode: 0, | ||||
| 					Error:      err, | ||||
| 					Time:       completedTime, | ||||
| 					Response: err.Error(), | ||||
| 					Time:     completedTime, | ||||
| 				}) | ||||
| 				increase <- 1 | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			*responseData = append(*responseData, &Response{ | ||||
| 				StatusCode: response.StatusCode(), | ||||
| 				Error:      nil, | ||||
| 				Time:       completedTime, | ||||
| 				Response: strconv.Itoa(response.StatusCode()), | ||||
| 				Time:     completedTime, | ||||
| 			}) | ||||
| 			increase <- 1 | ||||
| 		}() | ||||
|   | ||||
							
								
								
									
										41
									
								
								types/durations.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								types/durations.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| package types | ||||
|  | ||||
| import ( | ||||
| 	"sort" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type Durations []time.Duration | ||||
|  | ||||
| func (d Durations) Sort(ascending ...bool) { | ||||
| 	// If ascending is provided and is false, sort in descending order | ||||
| 	if len(ascending) > 0 && ascending[0] == false { | ||||
| 		sort.Slice(d, func(i, j int) bool { | ||||
| 			return d[i] > d[j] | ||||
| 		}) | ||||
| 	} else { // Otherwise, sort in ascending order | ||||
| 		sort.Slice(d, func(i, j int) bool { | ||||
| 			return d[i] < d[j] | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d Durations) First() *time.Duration { | ||||
| 	return &d[0] | ||||
| } | ||||
|  | ||||
| func (d Durations) Last() *time.Duration { | ||||
| 	return &d[len(d)-1] | ||||
| } | ||||
|  | ||||
| func (d Durations) Sum() time.Duration { | ||||
| 	sum := time.Duration(0) | ||||
| 	for _, duration := range d { | ||||
| 		sum += duration | ||||
| 	} | ||||
| 	return sum | ||||
| } | ||||
|  | ||||
| func (d Durations) Avg() time.Duration { | ||||
| 	return d.Sum() / time.Duration(len(d)) | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package utils | ||||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| @@ -1,31 +0,0 @@ | ||||
| package utils | ||||
|  | ||||
| import "time" | ||||
|  | ||||
| func MinDuration(durations ...time.Duration) time.Duration { | ||||
| 	min := durations[0] | ||||
| 	for _, d := range durations { | ||||
| 		if d < min { | ||||
| 			min = d | ||||
| 		} | ||||
| 	} | ||||
| 	return min | ||||
| } | ||||
|  | ||||
| func MaxDuration(durations ...time.Duration) time.Duration { | ||||
| 	max := durations[0] | ||||
| 	for _, d := range durations { | ||||
| 		if d > max { | ||||
| 			max = d | ||||
| 		} | ||||
| 	} | ||||
| 	return max | ||||
| } | ||||
|  | ||||
| func AvgDuration(durations ...time.Duration) time.Duration { | ||||
| 	total := time.Duration(0) | ||||
| 	for _, d := range durations { | ||||
| 		total += d | ||||
| 	} | ||||
| 	return total / time.Duration(len(durations)) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user