diff --git a/client/client.go b/client/client.go index e7e3774..bd68880 100644 --- a/client/client.go +++ b/client/client.go @@ -32,7 +32,6 @@ import ( "runtime/debug" "slices" "strconv" - "strings" "sync" "sync/atomic" "time" @@ -399,6 +398,13 @@ func RunTest(ctx context.Context, c shared.Config) (err error) { } } + if !c.Micro { + to.TTFBH = to.TTFBH / 1000 + to.TTFBL = to.TTFBL / 1000 + to.RMSH = to.RMSH / 1000 + to.RMSL = to.RMSL / 1000 + } + for i := range responseERR { fmt.Println(responseERR[i]) } @@ -580,96 +586,103 @@ func AnalyzeTest(ctx context.Context, c shared.Config) (err error) { } else { shared.DEBUG(ErrorStyle.Render("Unknown data point encountered: ", string(b))) } + } + if c.HostFilter != "" { + dps = shared.HostFilter(c.HostFilter, dps) } if c.PrintFull { - printDataPointHeaders(dps[0].Type) - for i := range dps { - dp := dps[i] - sp1 := strings.Split(dp.Local, ":") - sp2 := strings.Split(sp1[0], ".") - s1 := lipgloss.NewStyle().Background(lipgloss.Color(getHex(sp2[len(sp2)-1]))) - printTableRow(s1, &dp, dp.Type) - } + printSliceOfDataPoints(dps, c) } if c.PrintErrors { + if len(errors) > 0 { + fmt.Println(" ____ ERRORS ____") + } for i := range errors { PrintTError(errors[i]) } + if len(errors) > 0 { + fmt.Println("") + } } + if len(dps) == 0 { + fmt.Println("No datapoints found") + return + } + + switch dps[0].Type { + case shared.LatencyTest: + analyzeLatencyTest(dps, c) + case shared.BandwidthTest: + fmt.Println("") + fmt.Println("Detailed analysis for bandwidth testing is in development") + } + + return nil +} + +func analyzeLatencyTest(dps []shared.DP, c shared.Config) { + shared.SortDataPoints(dps, c) + dps10 := math.Ceil((float64(len(dps)) / 100) * 10) + dps50 := math.Floor((float64(len(dps)) / 100) * 50) dps90 := math.Floor((float64(len(dps)) / 100) * 90) - - slices.SortFunc(dps, func(a shared.DP, b shared.DP) int { - if a.RMSH < b.RMSH { - return -1 - } else { - return 1 - } - }) + dps99 := math.Floor((float64(len(dps)) / 100) * 99) dps10s := make([]shared.DP, 0) dps50s := make([]shared.DP, 0) dps90s := make([]shared.DP, 0) + dps99s := make([]shared.DP, 0) - // total, sum, low, mean, high + // count, sum, low, avg, high dps10stats := []int64{0, 0, math.MaxInt64, 0, 0} dps50stats := []int64{0, 0, math.MaxInt64, 0, 0} dps90stats := []int64{0, 0, math.MaxInt64, 0, 0} + dps99stats := []int64{0, 0, math.MaxInt64, 0, 0} for i := range dps { - if i <= int(dps10) { + if i >= int(dps10) { dps10s = append(dps10s, dps[i]) - updateBracketStats(dps10stats, dps[i]) - } else if i >= int(dps90) { - dps90s = append(dps90s, dps[i]) - updateBracketStats(dps90stats, dps[i]) - } else { + shared.UpdatePSStats(dps10stats, dps[i], c) + } + if i >= int(dps50) { dps50s = append(dps50s, dps[i]) - updateBracketStats(dps50stats, dps[i]) + shared.UpdatePSStats(dps50stats, dps[i], c) + } + if i >= int(dps90) { + dps90s = append(dps90s, dps[i]) + shared.UpdatePSStats(dps90stats, dps[i], c) + } + if i >= int(dps99) { + dps99s = append(dps99s, dps[i]) + shared.UpdatePSStats(dps99stats, dps[i], c) } } fmt.Println("") + fmt.Println(" _____ P99 data points _____ ") fmt.Println("") - fmt.Println("") + printSliceOfDataPoints(dps99s, c) - fmt.Println(" First 10% of data points") - printBracker(dps10stats, SuccessStyle) fmt.Println("") - fmt.Println(" Between 10% and 90%") - printBracker(dps50stats, WarningStyle) - fmt.Println("") - fmt.Println(" Last 10% of data points") - printBracker(dps90stats, ErrorStyle) - fmt.Println("") - return nil -} - -func printBracker(b []int64, style lipgloss.Style) { - fmt.Println(style.Render( - fmt.Sprintf(" Total %d | Low %d | Avg %d | High %d | Microseconds ", - b[0], - b[2], - b[3], - b[4], - ), - )) -} - -func updateBracketStats(b []int64, dp shared.DP) { - b[0]++ - b[1] += dp.RMSH - if dp.RMSH < b[2] { - b[2] = dp.RMSH + if c.Sort == "" { + fmt.Println(" Sorting:", shared.SortDefault) + } else { + fmt.Println(" Sorting:", c.Sort) } - b[3] = b[1] / b[0] - if dp.RMSH > b[4] { - b[4] = dp.RMSH + if c.Micro { + fmt.Println(" Time: Microseconds") + } else { + fmt.Println(" Time: Milliseconds") } + fmt.Println("") + PrintPercentiles(SuccessStyle, "P10", dps10stats, c) + PrintPercentiles(WarningStyle, "P50", dps50stats, c) + PrintPercentiles(ErrorStyle, "P90", dps90stats, c) + PrintPercentiles(ErrorStyle, "P99", dps99stats, c) } func MakeCSV(ctx context.Context, c shared.Config) (err error) { @@ -732,3 +745,32 @@ func dpToSlice(dp *shared.DP) (data []string) { } return } + +func transformDataPointsToMilliseconds(dps []shared.DP) (clone []shared.DP) { + clone = make([]shared.DP, len(dps)) + copy(clone, dps) + for i := range clone { + clone[i].TTFBH = clone[i].TTFBH / 1000 + clone[i].TTFBL = clone[i].TTFBL / 1000 + clone[i].RMSH = clone[i].RMSH / 1000 + clone[i].RMSL = clone[i].RMSL / 1000 + } + return +} + +func printSliceOfDataPoints(dps []shared.DP, c shared.Config) { + var data []shared.DP + if !c.Micro { + data = transformDataPointsToMilliseconds(dps) + } else { + data = dps + } + + for i := range data { + if i%20 == 0 { + printDataPointHeaders(data[0].Type) + } + dp := data[i] + printTableRow(BaseStyle, &dp, dp.Type) + } +} diff --git a/client/table.go b/client/table.go index 81b2f0b..299a664 100644 --- a/client/table.go +++ b/client/table.go @@ -104,13 +104,11 @@ func GenerateFormatString(columnCount int) (fs string) { var ( ListHeaders = []HeaderField{IntNumber, ID, HumanTime} BandwidthHeaders = []HeaderField{Created, Local, Remote, TX, ErrCount, DroppedPackets, MemoryUsage, CPUUsage} - LatencyHeaders = []HeaderField{Created, Local, Remote, RMSH, RMSL, TXCount, ErrCount, DroppedPackets, MemoryUsage, CPUUsage} - HTTPHeaders = []HeaderField{Created, Local, Remote, RMSH, RMSL, TTFBH, TTFBL, TX, TXCount, ErrCount, DroppedPackets, MemoryUsage, CPUUsage} + LatencyHeaders = []HeaderField{Created, Local, Remote, RMSH, RMSL, TTFBH, TTFBL, TX, TXCount, ErrCount, DroppedPackets, MemoryUsage, CPUUsage} FullDataPointHeaders = []HeaderField{Created, Local, Remote, RMSH, RMSL, TTFBH, TTFBL, TX, TXCount, ErrCount, DroppedPackets, MemoryUsage, CPUUsage} - RealTimeLatencyHeaders = []HeaderField{ErrCount, TXCount, RMSH, RMSL, DroppedPackets, MemoryHigh, MemoryLow, CPUHigh, CPULow} RealTimeBandwidthHeaders = []HeaderField{ErrCount, TXCount, TXH, TXL, TXT, DroppedPackets, MemoryHigh, MemoryLow, CPUHigh, CPULow} - RealTimeHTTPHeaders = []HeaderField{ErrCount, TXCount, TXH, TXL, TXT, RMSH, RMSL, TTFBH, TTFBL, DroppedPackets, MemoryHigh, MemoryLow, CPUHigh, CPULow} + RealTimeLatencyHeaders = []HeaderField{ErrCount, TXCount, TXH, TXL, TXT, RMSH, RMSL, TTFBH, TTFBL, DroppedPackets, MemoryHigh, MemoryLow, CPUHigh, CPULow} ) var ( @@ -135,6 +133,51 @@ func printHeader(fields []HeaderField) { fmt.Println(HeaderStyle.Render(fmt.Sprintf(fs, hs...))) } +func PrintPercentilesHeader(style lipgloss.Style, tag string, dps []int64, c shared.Config) { + fs := GenerateFormatString(6) + hs := []interface{}{ + 4, tag, + 10, "count", + 10, "sum", + 10, "min", + 10, "avg", + 10, "max", + } + fmt.Println(style.Render( + fmt.Sprintf(fs, hs...), + )) +} + +func PrintPercentiles(style lipgloss.Style, tag string, dps []int64, c shared.Config) { + PrintPercentilesHeader(style, tag, dps, c) + fs := GenerateFormatString(6) + hs := make([]interface{}, 12) + hs[0] = 4 + hs[1] = "" + hs[2] = 10 + hs[3] = formatInt(dps[0]) + hs[4] = 10 + hs[6] = 10 + hs[8] = 10 + hs[10] = 10 + + if c.Micro { + hs[5] = formatInt(dps[1]) + hs[7] = formatInt(dps[2]) + hs[9] = formatInt(dps[3]) + hs[11] = formatInt(dps[4]) + } else { + hs[5] = formatInt(dps[1] / 1000) + hs[7] = formatInt(dps[2] / 1000) + hs[9] = formatInt(dps[3] / 1000) + hs[11] = formatInt(dps[4] / 1000) + } + + fmt.Println(BaseStyle.Render( + fmt.Sprintf(fs, hs...), + )) +} + func PrintColumns(style lipgloss.Style, columns ...column) { fs := GenerateFormatString(len(columns)) hs := make([]interface{}, 0) @@ -152,8 +195,6 @@ func printDataPointHeaders(t shared.TestType) { printHeader(BandwidthHeaders) case shared.LatencyTest: printHeader(LatencyHeaders) - case shared.HTTPTest: - printHeader(HTTPHeaders) default: printHeader(FullDataPointHeaders) } @@ -165,36 +206,20 @@ func printRealTimeHeaders(t shared.TestType) { printHeader(RealTimeBandwidthHeaders) case shared.LatencyTest: printHeader(RealTimeLatencyHeaders) - case shared.HTTPTest: - printHeader(RealTimeHTTPHeaders) default: } } func printRealTimeRow(style lipgloss.Style, entry *shared.TestOutput, t shared.TestType) { switch t { - case shared.LatencyTest: - PrintColumns( - style, - column{formatInt(int64(entry.ErrCount)), headerSlice[ErrCount].width}, - column{formatUint(entry.TXC), headerSlice[TXCount].width}, - column{formatInt(entry.RMSH), headerSlice[RMSH].width}, - column{formatInt(entry.RMSL), headerSlice[RMSL].width}, - column{formatInt(int64(entry.DP)), headerSlice[DroppedPackets].width}, - column{formatInt(int64(entry.MH)), headerSlice[MemoryHigh].width}, - column{formatInt(int64(entry.ML)), headerSlice[MemoryLow].width}, - column{formatInt(int64(entry.CH)), headerSlice[CPUHigh].width}, - column{formatInt(int64(entry.CL)), headerSlice[CPULow].width}, - ) - return case shared.BandwidthTest: PrintColumns( style, column{formatInt(int64(entry.ErrCount)), headerSlice[ErrCount].width}, column{formatUint(entry.TXC), headerSlice[TXCount].width}, - column{formatUint(entry.TXH), headerSlice[TXH].width}, - column{formatUint(entry.TXL), headerSlice[TXL].width}, - column{formatUint(entry.TXT), headerSlice[TXT].width}, + column{shared.BWToString(entry.TXH), headerSlice[TXH].width}, + column{shared.BWToString(entry.TXL), headerSlice[TXL].width}, + column{shared.BToString(entry.TXT), headerSlice[TXT].width}, column{formatInt(int64(entry.DP)), headerSlice[DroppedPackets].width}, column{formatInt(int64(entry.MH)), headerSlice[MemoryHigh].width}, column{formatInt(int64(entry.ML)), headerSlice[MemoryLow].width}, @@ -202,14 +227,14 @@ func printRealTimeRow(style lipgloss.Style, entry *shared.TestOutput, t shared.T column{formatInt(int64(entry.CL)), headerSlice[CPULow].width}, ) return - case shared.HTTPTest: + case shared.LatencyTest: PrintColumns( style, column{formatInt(int64(entry.ErrCount)), headerSlice[ErrCount].width}, column{formatUint(entry.TXC), headerSlice[TXCount].width}, - column{formatUint(entry.TXH), headerSlice[TXH].width}, - column{formatUint(entry.TXL), headerSlice[TXL].width}, - column{formatUint(entry.TXT), headerSlice[TXT].width}, + column{shared.BWToString(entry.TXH), headerSlice[TXH].width}, + column{shared.BWToString(entry.TXL), headerSlice[TXL].width}, + column{shared.BToString(entry.TXT), headerSlice[TXT].width}, column{formatInt(entry.RMSH), headerSlice[RMSH].width}, column{formatInt(entry.RMSL), headerSlice[RMSL].width}, column{formatInt(entry.TTFBH), headerSlice[TTFBH].width}, @@ -227,35 +252,20 @@ func printRealTimeRow(style lipgloss.Style, entry *shared.TestOutput, t shared.T func printTableRow(style lipgloss.Style, entry *shared.DP, t shared.TestType) { switch t { - case shared.LatencyTest: - PrintColumns( - style, - column{entry.Created.Format("15:04:05"), headerSlice[Created].width}, - column{strings.Split(entry.Local, ":")[0], headerSlice[Local].width}, - column{strings.Split(entry.Remote, ":")[0], headerSlice[Remote].width}, - column{formatInt(entry.RMSH), headerSlice[RMSH].width}, - column{formatInt(entry.RMSL), headerSlice[RMSL].width}, - column{formatUint(entry.TXCount), headerSlice[TXCount].width}, - column{formatInt(int64(entry.ErrCount)), headerSlice[ErrCount].width}, - column{formatInt(int64(entry.DroppedPackets)), headerSlice[DroppedPackets].width}, - column{formatInt(int64(entry.MemoryUsedPercent)), headerSlice[MemoryUsage].width}, - column{formatInt(int64(entry.CPUUsedPercent)), headerSlice[MemoryUsage].width}, - ) - return case shared.BandwidthTest: PrintColumns( style, column{entry.Created.Format("15:04:05"), headerSlice[Created].width}, column{strings.Split(entry.Local, ":")[0], headerSlice[Local].width}, column{strings.Split(entry.Remote, ":")[0], headerSlice[Remote].width}, - column{shared.BandwidthBytesToString(entry.TX), headerSlice[TX].width}, + column{shared.BWToString(entry.TX), headerSlice[TX].width}, column{formatInt(int64(entry.ErrCount)), headerSlice[ErrCount].width}, column{formatInt(int64(entry.DroppedPackets)), headerSlice[DroppedPackets].width}, column{formatInt(int64(entry.MemoryUsedPercent)), headerSlice[MemoryUsage].width}, column{formatInt(int64(entry.CPUUsedPercent)), headerSlice[CPUUsage].width}, ) return - case shared.HTTPTest: + case shared.LatencyTest: PrintColumns( style, column{entry.Created.Format("15:04:05"), headerSlice[Created].width}, @@ -265,7 +275,7 @@ func printTableRow(style lipgloss.Style, entry *shared.DP, t shared.TestType) { column{formatInt(entry.RMSL), headerSlice[RMSL].width}, column{formatInt(entry.TTFBH), headerSlice[TTFBH].width}, column{formatInt(entry.TTFBL), headerSlice[TTFBH].width}, - column{shared.BandwidthBytesToString(entry.TX), headerSlice[TX].width}, + column{shared.BWToString(entry.TX), headerSlice[TX].width}, column{formatUint(entry.TXCount), headerSlice[TXCount].width}, column{formatInt(int64(entry.ErrCount)), headerSlice[ErrCount].width}, column{formatInt(int64(entry.DroppedPackets)), headerSlice[DroppedPackets].width}, @@ -297,8 +307,6 @@ func praseDataPoint(r *shared.DataReponseToClient, c *shared.Config) { responseLock.Lock() defer responseLock.Unlock() - s1 := lipgloss.NewStyle() - // This guarantees we are always printing the // same header types as the data point types. if len(r.DPS) > 0 { @@ -317,10 +325,7 @@ func praseDataPoint(r *shared.DataReponseToClient, c *shared.Config) { for i := range r.DPS { r.DPS[i].Received = time.Now() entry := r.DPS[i] - sp1 := strings.Split(entry.Local, ":") - sp2 := strings.Split(sp1[0], ".") - s1 = s1.Background(lipgloss.Color(getHex(sp2[len(sp2)-1]))) - printTableRow(s1, &entry, entry.Type) + printTableRow(BaseStyle, &entry, entry.Type) } for i := range r.Errors { @@ -339,35 +344,3 @@ func formatInt(val int64) string { func formatUint(val uint64) string { return strconv.FormatUint(val, 10) } - -var baseStyle = lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) - -func getHex(firstOcted string) (hex string) { - of, err := strconv.ParseFloat(firstOcted, 64) - if err != nil { - return COLORS[0] - } - index := (of / 25) * 10 - if index > 10 { - index = 10 - } else if index < 0 { - index = 0 - } - return COLORS[int(index)] -} - -var COLORS = []string{ - "#00000E", - "#00001E", - "#00002E", - "#00003E", - "#00004E", - "#00005E", - "#00006E", - "#00007E", - "#00008E", - "#00009E", - "#0000AE", -} diff --git a/cmd/hperf/analyze.go b/cmd/hperf/analyze.go index cc23f5e..4e294ec 100644 --- a/cmd/hperf/analyze.go +++ b/cmd/hperf/analyze.go @@ -33,6 +33,9 @@ var analyzeCMD = cli.Command{ fileFlag, printStatsFlag, printErrFlag, + sortFlag, + microSecondsFlag, + hostFilterFlag, }, CustomHelpTemplate: `NAME: {{.HelpName}} - {{.Usage}} @@ -46,8 +49,12 @@ FLAGS: EXAMPLES: 1. Analyze test results in file '/tmp/latency-test-1': {{.Prompt}} {{.HelpName}} --hosts 10.10.10.1 --file latency-test-1 - 1. Analyze test results and print full output: + 2. Analyze test results and print full output: {{.Prompt}} {{.HelpName}} --hosts 10.10.10.1 --file latency-test-1 --print-full + 3. Analyze test results with sorted output: + {{.Prompt}} {{.HelpName}} --hosts 10.10.10.1 --file latency-test-1 --sort RMSH + 4. Analyze test results with sorted output: + {{.Prompt}} {{.HelpName}} --hosts 10.10.10.1 --file latency-test-1 --sort RMSH --host-filter 10.10.10.1 `, } diff --git a/cmd/hperf/latency.go b/cmd/hperf/latency.go index df8e4f0..a1d99d5 100644 --- a/cmd/hperf/latency.go +++ b/cmd/hperf/latency.go @@ -39,6 +39,7 @@ var latencyCMD = cli.Command{ testIDFlag, saveTestFlag, dnsServerFlag, + microSecondsFlag, }, CustomHelpTemplate: `NAME: {{.HelpName}} - {{.Usage}} diff --git a/cmd/hperf/main.go b/cmd/hperf/main.go index 5b5b2a0..8a81273 100644 --- a/cmd/hperf/main.go +++ b/cmd/hperf/main.go @@ -155,10 +155,22 @@ var ( Name: "print-stats", Usage: "Print stat points", } + microSecondsFlag = cli.BoolFlag{ + Name: "micro", + Usage: "Display timers in microseconds instead of milliseconds", + } + sortFlag = cli.StringFlag{ + Name: "sort", + Usage: "Sort datapoints using columns", + } printErrFlag = cli.BoolFlag{ Name: "print-errors", Usage: "Print errors", } + hostFilterFlag = cli.StringFlag{ + Name: "host-filter", + Usage: "Filter analysis datapoints based on host", + } ) var ( @@ -174,7 +186,6 @@ var ( latencyCMD, listenCMD, listTestsCMD, - requestsCMD, serverCMD, statDownloadCMD, stopCMD, @@ -256,6 +267,9 @@ func parseConfig(ctx *cli.Context) (*shared.Config, error) { File: ctx.String(fileFlag.Name), PrintFull: ctx.Bool(printStatsFlag.Name), PrintErrors: ctx.Bool(printErrFlag.Name), + Sort: shared.SortType(ctx.String(sortFlag.Name)), + Micro: ctx.Bool(microSecondsFlag.Name), + HostFilter: ctx.String(hostFilterFlag.Name), } switch ctx.Command.Name { diff --git a/cmd/hperf/requests.go b/cmd/hperf/requests.go deleted file mode 100644 index 8cb2725..0000000 --- a/cmd/hperf/requests.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2015-2024 MinIO, Inc. -// -// This file is part of MinIO Object Storage stack -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package main - -import ( - "github.com/minio/cli" - "github.com/minio/hperf/client" - "github.com/minio/hperf/shared" -) - -var requestsCMD = cli.Command{ - Name: "requests", - Usage: "start a test to measure http(s) throughput at the application level", - Action: runRequests, - Flags: []cli.Flag{ - hostsFlag, - portFlag, - concurrencyFlag, - delayFlag, - durationFlag, - bufferSizeFlag, - payloadSizeFlag, - restartOnErrorFlag, - testIDFlag, - saveTestFlag, - dnsServerFlag, - }, - CustomHelpTemplate: `NAME: - {{.HelpName}} - {{.Usage}} - -USAGE: - {{.HelpName}} [FLAGS] - -FLAGS: - {{range .VisibleFlags}}{{.}} - {{end}} -EXAMPLES: - 1. Run a basic test: - {{.Prompt}} {{.HelpName}} --hosts 10.10.10.1,10.10.10.2 - - 2. Run a test with reduced throughput: - {{.Prompt}} {{.HelpName}} --hosts 10.10.10.1,10.10.10.2 --delay 100 - - 3. Run a test with reduced concurrency and throughput: - {{.Prompt}} {{.HelpName}} --hosts 10.10.10.1,10.10.10.2 --delay 100 --concurrency 1 -`, -} - -func runRequests(ctx *cli.Context) error { - config, err := parseConfig(ctx) - if err != nil { - return err - } - config.TestType = shared.HTTPTest - return client.RunTest(GlobalContext, *config) -} diff --git a/server/server.go b/server/server.go index be7f4c3..5cf1ef2 100644 --- a/server/server.go +++ b/server/server.go @@ -216,7 +216,8 @@ func startAPIandWS(ctx context.Context) (err error) { } })) - httpServer.Get("/latency", func(c *fiber.Ctx) error { + httpServer.Put("/latency", func(c *fiber.Ctx) error { + io.Copy(io.Discard, bytes.NewBuffer(c.Body())) return c.SendStatus(200) }) @@ -225,11 +226,6 @@ func startAPIandWS(ctx context.Context) (err error) { return c.SendStatus(200) }) - httpServer.Put("/http", func(c *fiber.Ctx) error { - io.Copy(io.Discard, bytes.NewBuffer(c.Body())) - return c.SendStatus(200) - }) - go func() { err = httpServer.Listen(bindAddress) if err != nil { @@ -692,6 +688,7 @@ type dialContext func(ctx context.Context, network, address string) (net.Conn, e func newPerformanceReaderForASingleHost(c *shared.Config, host string, port string) (r *netPerfReader) { r = new(netPerfReader) + r.lastDataPointTime = time.Now() r.addr = net.JoinHostPort(host, port) r.ip = host r.buf = make([]byte, c.PayloadSize) @@ -755,16 +752,13 @@ func sendRequestToHost(t *test, r *netPerfReader, cid int) { var body io.Reader method := http.MethodGet switch t.Config.TestType { - case shared.LatencyTest: - route = "/latency" - method = http.MethodGet case shared.BandwidthTest: route = "/bandwidth" body = io.NopCloser(AR) method = http.MethodPut - case shared.HTTPTest: + case shared.LatencyTest: method = http.MethodPut - route = "/http" + route = "/latency" body = AR default: t.AddError(fmt.Errorf("Unknown test type: %d", t.Config.TestType), "unknown-signal") diff --git a/shared/shared.go b/shared/shared.go index 57b29ad..f56bb9f 100644 --- a/shared/shared.go +++ b/shared/shared.go @@ -102,7 +102,7 @@ const ( Unknown TestType = iota LatencyTest BandwidthTest - HTTPTest + // HTTPTest ) const ( @@ -111,27 +111,6 @@ const ( Retry ) -func BandwidthBytesToString(b uint64) string { - if b <= 999 { - intS := strconv.FormatUint(b, 10) - return intS + " B/s" - } else if b <= 999_999 { - intF := float64(b) - return fmt.Sprintf("%.2f KB/s", intF/1000) - } else if b <= 999_999_999 { - intF := float64(b) - return fmt.Sprintf("%.2f MB/s", intF/1_000_000) - } else if b <= 999_999_999_999 { - intF := float64(b) - return fmt.Sprintf("%.2f GB/s", intF/1_000_000_000) - } else if b <= 999_999_999_999_999 { - intF := float64(b) - return fmt.Sprintf("%.2f TB/s", intF/1_000_000_000_000) - } - - return "???" -} - type TError struct { Error string Created time.Time @@ -184,9 +163,12 @@ type Config struct { // AllowLocalInterface bool `json:"AllowLocalInterfaces"` // Client Only - ResolveHosts string `json:"-"` - PrintFull bool `json:"-"` - PrintErrors bool `json:"-"` + ResolveHosts string `json:"-"` + PrintFull bool `json:"-"` + PrintErrors bool `json:"-"` + Sort SortType `json:"-"` + Micro bool `json:"-"` + HostFilter string `json:"-"` } func INFO(items ...any) { @@ -199,6 +181,48 @@ func DEBUG(items ...any) { } } +func BToString(b uint64) string { + if b <= 999 { + intS := strconv.FormatUint(b, 10) + return intS + " B" + } else if b <= 999_999 { + intF := float64(b) + return fmt.Sprintf("%.2f KB", intF/1000) + } else if b <= 999_999_999 { + intF := float64(b) + return fmt.Sprintf("%.2f MB", intF/1_000_000) + } else if b <= 999_999_999_999 { + intF := float64(b) + return fmt.Sprintf("%.2f GB", intF/1_000_000_000) + } else if b <= 999_999_999_999_999 { + intF := float64(b) + return fmt.Sprintf("%.2f TB", intF/1_000_000_000_000) + } + + return "???" +} + +func BWToString(b uint64) string { + if b <= 999 { + intS := strconv.FormatUint(b, 10) + return intS + " B/s" + } else if b <= 999_999 { + intF := float64(b) + return fmt.Sprintf("%.2f KB/s", intF/1000) + } else if b <= 999_999_999 { + intF := float64(b) + return fmt.Sprintf("%.2f MB/s", intF/1_000_000) + } else if b <= 999_999_999_999 { + intF := float64(b) + return fmt.Sprintf("%.2f GB/s", intF/1_000_000_000) + } else if b <= 999_999_999_999_999 { + intF := float64(b) + return fmt.Sprintf("%.2f TB/s", intF/1_000_000_000_000) + } + + return "???" +} + func ParseHosts(hosts string, dnsServer string) (list []string, err error) { list = make([]string, 0) diff --git a/shared/sorting.go b/shared/sorting.go new file mode 100644 index 0000000..df975fa --- /dev/null +++ b/shared/sorting.go @@ -0,0 +1,59 @@ +package shared + +import ( + "slices" + "strings" +) + +type SortType string + +const ( + SortDefault SortType = "RMSH" + SortRMSH SortType = "RMSH" + SortTTFBH SortType = "TTFBH" +) + +func HostFilter(host string, dps []DP) (filtered []DP) { + filtered = make([]DP, 0) + for _, v := range dps { + if strings.Contains(v.Local, host) { + filtered = append(filtered, v) + } else if strings.Contains(v.Remote, host) { + filtered = append(filtered, v) + } + } + + return +} + +func SortDataPoints(dps []DP, c Config) { + switch c.Sort { + case SortRMSH: + SortDataPointRMSH(dps) + case SortTTFBH: + SortDataPointTTFBH(dps) + default: + c.Sort = SortDefault + SortDataPointRMSH(dps) + } +} + +func SortDataPointRMSH(dps []DP) { + slices.SortFunc(dps, func(a DP, b DP) int { + if a.RMSH < b.RMSH { + return -1 + } else { + return 1 + } + }) +} + +func SortDataPointTTFBH(dps []DP) { + slices.SortFunc(dps, func(a DP, b DP) int { + if a.TTFBH < b.TTFBH { + return -1 + } else { + return 1 + } + }) +} diff --git a/shared/stats.go b/shared/stats.go new file mode 100644 index 0000000..4d85cf1 --- /dev/null +++ b/shared/stats.go @@ -0,0 +1,37 @@ +package shared + +func UpdatePSStats(b []int64, dp DP, c Config) { + switch c.Sort { + case SortRMSH: + UpdatePSStatsRMHS(b, dp) + case SortTTFBH: + UpdatePSStatsTTFBH(b, dp) + default: + c.Sort = SortRMSH + UpdatePSStatsRMHS(b, dp) + } +} + +func UpdatePSStatsRMHS(b []int64, dp DP) { + b[0]++ + b[1] += dp.RMSH + if dp.RMSH < b[2] { + b[2] = dp.RMSH + } + b[3] = b[1] / b[0] + if dp.RMSH > b[4] { + b[4] = dp.RMSH + } +} + +func UpdatePSStatsTTFBH(b []int64, dp DP) { + b[0]++ + b[1] += dp.TTFBH + if dp.TTFBH < b[2] { + b[2] = dp.TTFBH + } + b[3] = b[1] / b[0] + if dp.TTFBH > b[4] { + b[4] = dp.TTFBH + } +}