Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fortio for openSUSE:Factory checked in at 2022-03-20 20:55:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fortio (Old) and /work/SRC/openSUSE:Factory/.fortio.new.25692 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fortio" Sun Mar 20 20:55:38 2022 rev:3 rq:963224 version:1.22.0 Changes: -------- --- /work/SRC/openSUSE:Factory/fortio/fortio.changes 2022-03-18 16:42:47.701209621 +0100 +++ /work/SRC/openSUSE:Factory/.fortio.new.25692/fortio.changes 2022-03-20 20:55:54.390557360 +0100 @@ -1,0 +2,8 @@ +Sun Mar 20 10:22:45 UTC 2022 - ka...@b1-systems.de + +- Update to version 1.22.0: + * allow a headers json array in json rest api payload ... (#523) + * Echo prefix after /debug/ (#525) + * `-server-idle-timeout` and close=x as a % instead of just true/false for all echo requests, `-calc-qps` client side (#522) + +------------------------------------------------------------------- Old: ---- fortio-1.21.2.tar.gz New: ---- fortio-1.22.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fortio.spec ++++++ --- /var/tmp/diff_new_pack.pKhkfu/_old 2022-03-20 20:55:55.622559133 +0100 +++ /var/tmp/diff_new_pack.pKhkfu/_new 2022-03-20 20:55:55.626559138 +0100 @@ -19,7 +19,7 @@ %define __arch_install_post export NO_BRP_STRIP_DEBUG=true Name: fortio -Version: 1.21.2 +Version: 1.22.0 Release: 0 Summary: Load testing library, command line tool, advanced echo server and web UI License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.pKhkfu/_old 2022-03-20 20:55:55.666559196 +0100 +++ /var/tmp/diff_new_pack.pKhkfu/_new 2022-03-20 20:55:55.670559202 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/fortio/fortio</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v1.21.2</param> + <param name="revision">v1.22.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> @@ -16,7 +16,7 @@ <param name="compression">gz</param> </service> <service name="go_modules" mode="disabled"> - <param name="archive">fortio-1.21.2.tar.gz</param> + <param name="archive">fortio-1.22.0.tar.gz</param> </service> </services> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.pKhkfu/_old 2022-03-20 20:55:55.690559230 +0100 +++ /var/tmp/diff_new_pack.pKhkfu/_new 2022-03-20 20:55:55.694559236 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/fortio/fortio</param> - <param name="changesrevision">f82e822e2dec925e7a7c85368971560b80bfcdec</param></service></servicedata> + <param name="changesrevision">b0adf910295107f689dcca6afb9fe2b397cbb977</param></service></servicedata> (No newline at EOF) ++++++ fortio-1.21.2.tar.gz -> fortio-1.22.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/README.md new/fortio-1.22.0/README.md --- old/fortio-1.21.2/README.md 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/README.md 2022-03-20 02:19:30.000000000 +0100 @@ -46,13 +46,13 @@ Or download one of the binary distributions, from the [releases](https://github.com/fortio/fortio/releases) assets page or for instance: ```shell -curl -L https://github.com/fortio/fortio/releases/download/v1.21.2/fortio-linux_x64-1.21.2.tgz \ +curl -L https://github.com/fortio/fortio/releases/download/v1.22.0/fortio-linux_x64-1.22.0.tgz \ | sudo tar -C / -xvzpf - # or the debian package -wget https://github.com/fortio/fortio/releases/download/v1.21.2/fortio_1.21.2_amd64.deb -dpkg -i fortio_1.21.2_amd64.deb +wget https://github.com/fortio/fortio/releases/download/v1.22.0/fortio_1.22.0_amd64.deb +dpkg -i fortio_1.22.0_amd64.deb # or the rpm -rpm -i https://github.com/fortio/fortio/releases/download/v1.21.2/fortio-1.21.2-1.x86_64.rpm +rpm -i https://github.com/fortio/fortio/releases/download/v1.22.0/fortio-1.22.0-1.x86_64.rpm ``` On a MacOS you can also install Fortio using [Homebrew](https://brew.sh/): @@ -61,7 +61,7 @@ brew install fortio ``` -On Windows, download https://github.com/fortio/fortio/releases/download/v1.21.2/fortio_win_1.21.2.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt: +On Windows, download https://github.com/fortio/fortio/releases/download/v1.22.0/fortio_win_1.22.0.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt: ``` fortio.exe server ``` @@ -92,6 +92,8 @@ | `-c connections` | Number of parallel simultaneous connections (and matching go routine) | | `-t duration` | How long to run the test (for instance `-t 30m` for 30 minutes) or 0 to run until ^C, example (default 5s) | | `-n numcalls` | Run for exactly this number of calls instead of duration. Default (0) is to use duration (-t). | +| `-payload str` or `-payload-file fname` | Switch to using POST with the given payload (see also `-payload-size` for random payload)| +| `-uniform` | Spread the calls across threads | | `-r resolution` | Resolution of the histogram lowest buckets in seconds (default 0.001 i.e 1ms), use 1/10th of your expected typical latency | | `-H "header: value"` | Can be specified multiple times to add headers (including Host:) | | `-a` | Automatically save JSON result with filename based on labels and timestamp | @@ -104,7 +106,7 @@ <details> <!-- use release/updateFlags.sh to update this section --> <pre> -???????????? 1.21.2 usage: +???????????? 1.22.0 usage: where command is one of: load (load testing), server (starts ui, http-echo, redirect, proxies, tcp-echo and grpc ping servers), tcp-echo (only the tcp-echo server), report (report only UI server), redirect (only the redirect server), @@ -140,6 +142,8 @@ -cacert Path Path to a custom CA certificate file to be used for the TLS client connections, if empty, use https:// prefix for standard internet/system CAs + -calc-qps + Calculate the qps based on number of requests (-n) and duration (-t) -cert Path Path to the certificate file to be used for client or server TLS -compression @@ -152,6 +156,9 @@ from GET to POST. -curl Just fetch the content once + -curl-stdout-headers + Restore pre 1.22 behavior where http headers of the fast client are +output to stdout in curl mode. now stderr by default. -data-dir Directory Directory where JSON results are stored/read (default ".") -echo-debug-path URI @@ -269,6 +276,8 @@ -sequential-warmup http(s) runner warmup done in parallel instead of sequentially. When set, restores pre 1.21 behavior + -server-idle-timeout value + Default IdleTimeout for servers (default 30s) -static-dir path Deprecated/unused path. -stdclient @@ -319,7 +328,7 @@ | delay | duration to delay the response by. Can be a single value or a comma separated list of probabilities, e.g `delay=150us:10,2ms:5,0.5s:1` for 10% of chance of a 150 us delay, 5% of a 2ms delay and 1% of a 1/2 second delay | | status | http status to return instead of 200. Can be a single value or a comma separated list of probabilities, e.g `status=404:10,503:5,429:1` for 10% of chance of a 404 status, 5% of a 503 status and 1% of a 429 status | | size | size of the payload to reply instead of echoing input. Also works as probabilities list. `size=1024:10,512:5` 10% of response will be 1k and 5% will be 512 bytes payload and the rest defaults to echoing back. | -| close | close the socket after answering e.g `close=true` | +| close | close the socket after answering e.g `close=true` to close after all requests or `close=5.3` to close after approximately 5.3% of requests| | header | header(s) to add to the reply e.g. `&header=Foo:Bar&header=X:Y` | You can set a default value for all these by passing `-echo-server-default-params` to the server command line, for instance: @@ -336,7 +345,7 @@ * Download/sync from an Amazon S3 or Google Cloud compatible bucket listings [XML URLs](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html) * API to trigger and cancel runs from the running server (like the form ui but more directly and with `async=on` option) - * `/fortio/rest/run` starts a run; the arguments are either from the command line or from POSTed JSON; `jsonPath` can be provided to look for in a subset of the json object, for instance `jsonPath=metadata` allows to use the flagger webhook meta data for fortio run parameters (see [#493](https://github.com/fortio/fortio/pull/493)). + * `/fortio/rest/run` starts a run; the arguments are either from the command line or from POSTed JSON; `jsonPath` can be provided to look for in a subset of the json object, for instance `jsonPath=metadata` allows to use the flagger webhook meta data for fortio run parameters (see [Remote Triggered load test section below](#remote-triggered-load-test-server-mode-rest-api)). * `/fortio/rest/stop` stops all current run or by run id. The `report` mode is a readonly subset of the above directly on `/`. @@ -596,6 +605,186 @@ All done 40 calls (plus 4 warmup) 60.588 ms avg, 7.9 qps ``` + +### Remote triggered load test (server mode rest API) + +New since 1.18 the server has a `fortio/rest/run` endpoint similar to what the form UI submit in `fortio/` to start a run. + - plus `async` query arg or json value `"on"` will make the run asynchronous (returns just the runid of the run instead of waiting for the result) + - plus read all the run configuration from either query args or jsonPath POSTed info + - compatible with [flagger](https://github.com/fluxcd/flagger) and other webhooks + - New in 1.22: use `headers` json array to send headers (or multiple `&H=` query args) + +Examples: + +```shell +$ curl -v -d '{"metadata": {"url":"localhost:8080", "c":"1", "n":"1", "async":"on", "save":"on"}}' \ + "localhost:8080/fortio/rest/run?jsonPath=.metadata" +{"started": 3} +``` +makes a 1 connection 1 query run for localhost:8080 url asynchronously and saves results + +or minimally: +```shell +curl -s -d '{"url":"localhost:8080"}' "localhost:8080/fortio/rest/run" | jq +``` + +More complete example: + +With sample.json (all values must be strings, even the numbers): +```json +{ + "metadata": { + "url": "localhost:8080", + "payload": "foo", + "qps": "40", + "c": "2", + "t": "0.1s", + "headers": [ + "Foo:Bar", + "X-Blah: Something else" + ], + "save": "on" + } +} +``` +You can run: +```shell +$ fortio curl -stdclient -payload-file sample.json "http://localhost:8080/fortio/rest/run?jsonPath=.metadata" > result.json +``` +which makes requests like this: +``` +POST / HTTP/1.1 +Host: localhost:8080 +Content-Length: 3 +Content-Type: application/octet-stream +Foo: Bar +X-Blah: Something else +X-On-Behalf-Of: [::1]:62629 + +foo +``` + +and you get in result.json +```json +{ + "RunType": "HTTP", + "Labels": "", + "StartTime": "2022-03-19T15:34:23.279389-07:00", + "RequestedQPS": "40", + "RequestedDuration": "100ms", + "ActualQPS": 38.44836361217263, + "ActualDuration": 104035637, + "NumThreads": 2, + "Version": "v1.22.0", + "DurationHistogram": { + "Count": 4, + "Min": 0.00027292, + "Max": 0.000930407, + "Sum": 0.002332047, + "Avg": 0.00058301175, + "StdDev": 0.00028491034912527755, + "Data": [ + { + "Start": 0.00027292, + "End": 0.000930407, + "Percent": 100, + "Count": 4 + } + ], + "Percentiles": [ + { + "Percentile": 50, + "Value": 0.0004920823333333334 + }, + { + "Percentile": 75, + "Value": 0.0007112446666666667 + }, + { + "Percentile": 90, + "Value": 0.0008427420666666666 + }, + { + "Percentile": 99, + "Value": 0.0009216405066666668 + }, + { + "Percentile": 99.9, + "Value": 0.0009295303506666667 + } + ] + }, + "Exactly": 0, + "Jitter": false, + "Uniform": false, + "RunID": 7, + "AccessLoggerInfo": "", + "RetCodes": { + "200": 4 + }, + "URL": "http://localhost:8080", + "NumConnections": 1, + "Compression": false, + "DisableFastClient": false, + "HTTP10": false, + "DisableKeepAlive": false, + "AllowHalfClose": false, + "Insecure": false, + "FollowRedirects": false, + "CACert": "", + "Cert": "", + "Key": "", + "Resolve": "", + "HTTPReqTimeOut": 3000000000, + "UserCredentials": "", + "ContentType": "", + "Payload": "Zm9v", + "UnixDomainSocket": "", + "LogErrors": false, + "ID": 0, + "SequentialWarmup": false, + "Sizes": { + "Count": 4, + "Min": 118, + "Max": 118, + "Sum": 472, + "Avg": 118, + "StdDev": 0, + "Data": [ + { + "Start": 118, + "End": 118, + "Percent": 100, + "Count": 4 + } + ], + "Percentiles": null + }, + "HeaderSizes": { + "Count": 4, + "Min": 115, + "Max": 115, + "Sum": 460, + "Avg": 115, + "StdDev": 0, + "Data": [ + { + "Start": 115, + "End": 115, + "Percent": 100, + "Count": 4 + } + ], + "Percentiles": null + }, + "SocketCount": 2, + "AbortOn": 0 +} +``` + +- There is also the `fortio/rest/stop` endpoint to stop a run by its id or all runs if not specified + + ### GRPC load test Uses `-s` to use multiple (h2/grpc) streams per connection (`-c`), request to hit the fortio ping grpc endpoint with a delay in replies of 0.25s and an extra payload for 10 bytes and auto save the json result: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/Webtest.sh new/fortio-1.22.0/Webtest.sh --- old/fortio-1.21.2/Webtest.sh 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/Webtest.sh 2022-03-20 02:19:30.000000000 +0100 @@ -48,7 +48,7 @@ $CURL https://www.google.com/robots.txt > /dev/null # Check that quiet is quiet. Issue #385. -QUIETCURLTEST="docker exec $DOCKERNAME $FORTIO_BIN_PATH curl -quiet www.google.com" +QUIETCURLTEST="docker exec $DOCKERNAME $FORTIO_BIN_PATH curl -quiet -curl-stdout-headers www.google.com" if [ "$($QUIETCURLTEST 2>&1 > /dev/null | wc -l)" -ne 0 ]; then echo "Error, -quiet still outputs logs" $QUIETCURLTEST > /dev/null @@ -63,7 +63,7 @@ $CURL "${BASE_FORTIO}fetch/localhost:8080$FORTIO_UI_PREFIX?url=localhost:8079&load=Start&qps=-1&json=on&n=100&runner=grpc" | grep '"SERVING": 100' # Check we get the logo (need to remove the CR from raw headers) VERSION=$(docker exec $DOCKERNAME $FORTIO_BIN_PATH version -s) -LOGO_TYPE=$($CURL "${BASE_FORTIO}${VERSION}/static/img/${LOGO}" | grep -i Content-Type: | tr -d '\r'| awk '{print $2}') +LOGO_TYPE=$($CURL "${BASE_FORTIO}${VERSION}/static/img/${LOGO}" 2>&1 >/dev/null | grep -i Content-Type: | tr -d '\r'| awk '{print $2}') if [ "$LOGO_TYPE" != "image/svg+xml" ]; then echo "Unexpected content type for the logo: $LOGO_TYPE" exit 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/bincommon/commonflags.go new/fortio-1.22.0/bincommon/commonflags.go --- old/fortio-1.21.2/bincommon/commonflags.go 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/bincommon/commonflags.go 2022-03-20 02:19:30.000000000 +0100 @@ -104,6 +104,8 @@ HelpFlag = flag.Bool("h", false, "Print usage/help on stdout") warmupFlag = flag.Bool("sequential-warmup", false, "http(s) runner warmup done in parallel instead of sequentially. When set, restores pre 1.21 behavior") + curlHeadersStdout = flag.Bool("curl-stdout-headers", false, + "Restore pre 1.22 behavior where http headers of the fast client are output to stdout in curl mode. now stderr by default.") ) // SharedMain is the common part of main from fortio_main and fcurl. @@ -145,7 +147,12 @@ } code, data, header := client.Fetch() log.LogVf("Fetch result code %d, data len %d, headerlen %d", code, len(data), header) - os.Stdout.Write(data) + if *curlHeadersStdout { + os.Stdout.Write(data) + } else { + os.Stderr.Write(data[:header]) + os.Stdout.Write(data[header:]) + } if code != http.StatusOK { log.Errf("Error status %d : %s", code, fhttp.DebugSummary(data, 512)) os.Exit(1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/fhttp/http_server.go new/fortio-1.22.0/fhttp/http_server.go --- old/fortio-1.21.2/fhttp/http_server.go 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/fhttp/http_server.go 2022-03-20 02:19:30.000000000 +0100 @@ -51,6 +51,7 @@ "Default parameters/querystring to use if there isn't one provided explicitly. E.g \"status=404&delay=3s\"") fetch2CopiesAllHeader = dflag.DynBool(flag.CommandLine, "proxy-all-headers", true, "Determines if only tracing or all headers (and cookies) are copied from request on the fetch2 ui/server endpoint") + serverIdleTimeout = dflag.DynDuration(flag.CommandLine, "server-idle-timeout", 30*time.Second, "Default IdleTimeout for servers") ) // EchoHandler is an http server handler echoing back the input. @@ -97,7 +98,7 @@ rqNum := atomic.AddInt64(&EchoRequests, 1) log.Debugf("Request # %v", rqNum) } - if r.FormValue("close") != "" { + if generateClose(r.FormValue("close")) { log.Debugf("Adding Connection:close / will close socket") w.Header().Set("Connection", "close") } @@ -167,7 +168,8 @@ m := http.NewServeMux() h2s := &http2.Server{} s := &http.Server{ - Handler: h2c.NewHandler(m, h2s), + IdleTimeout: serverIdleTimeout.Get(), + Handler: h2c.NewHandler(m, h2s), } listener, addr := fnet.Listen(name, port) if listener == nil { @@ -349,6 +351,7 @@ } if debugPath != "" { mux.HandleFunc(debugPath, DebugHandler) + mux.HandleFunc(strings.TrimSuffix(debugPath, "/")+"/echo/", EchoHandler) // Fix #524 } mux.HandleFunc("/", EchoHandler) return mux, addr diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/fhttp/http_test.go new/fortio-1.22.0/fhttp/http_test.go --- old/fortio-1.21.2/fhttp/http_test.go 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/fhttp/http_test.go 2022-03-20 02:19:30.000000000 +0100 @@ -497,6 +497,28 @@ } } +func TestGenerateClose(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + // not numbers + {"true", true}, + {"false", false}, + {"x", true}, + // Numbers + {"0", false}, + {"0.0", false}, + {"99.9999999", true}, // well, in theory this should fail once in a blue moon + {"100", true}, + } + for _, tst := range tests { + if actual := generateClose(tst.input); actual != tst.expected { + t.Errorf("Got %v, expected %v for generateClose(%q)", actual, tst.expected, tst.input) + } + } +} + func TestPayloadWithEchoBack(t *testing.T) { tests := []struct { payload []byte diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/fhttp/http_utils.go new/fortio-1.22.0/fhttp/http_utils.go --- old/fortio-1.21.2/fhttp/http_utils.go 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/fhttp/http_utils.go 2022-03-20 02:19:30.000000000 +0100 @@ -398,6 +398,28 @@ return 0 } +// generateClose from string, format: close=true for 100% close +// close=true:10 or close=10 for 10% socket close. +func generateClose(closeStr string) bool { + if closeStr == "" || closeStr == "false" { + return false + } + if closeStr == "true" { // avoid throwing error for pre 1.22 syntax + return true + } + p, err := strconv.ParseFloat(closeStr, 32) + if err != nil { + log.Debugf("error %v parsing close=%q treating as true", err, closeStr) + return true + } + res := 100. * rand.Float32() // nolint: gosec // we want fast not crypto + log.Debugf("close=%f rolled %f", p, res) + if res <= float32(p) { + return true + } + return false +} + // RoundDuration rounds to 10th of second. func RoundDuration(d time.Duration) time.Duration { return d.Round(100 * time.Millisecond) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/fhttp/httprunner.go new/fortio-1.22.0/fhttp/httprunner.go --- old/fortio-1.21.2/fhttp/httprunner.go 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/fhttp/httprunner.go 2022-03-20 02:19:30.000000000 +0100 @@ -77,7 +77,7 @@ } // RunHTTPTest runs an http test and returns the aggregated stats. -// nolint: funlen, gocognit +// nolint: funlen, gocognit, gocyclo func RunHTTPTest(o *HTTPRunnerOptions) (*HTTPRunnerResults, error) { o.RunType = "HTTP" warmupMode := "parallel" @@ -192,9 +192,10 @@ r.Options().ReleaseRunners() sort.Ints(keys) totalCount := float64(total.DurationHistogram.Count) - _, _ = fmt.Fprintf(out, "Sockets used: %d (for perfect keepalive, would be %d)\n", total.SocketCount, r.Options().NumThreads) - _, _ = fmt.Fprintf(out, "Jitter: %t\n", total.Jitter) - _, _ = fmt.Fprintf(out, "Uniform: %t\n", total.Uniform) + if !o.DisableFastClient { + _, _ = fmt.Fprintf(out, "Sockets used: %d (for perfect keepalive, would be %d)\n", total.SocketCount, r.Options().NumThreads) + } + _, _ = fmt.Fprintf(out, "Uniform: %t, Jitter: %t\n", total.Uniform, total.Jitter) for _, k := range keys { _, _ = fmt.Fprintf(out, "Code %3d : %d (%.1f %%)\n", k, total.RetCodes[k], 100.*float64(total.RetCodes[k])/totalCount) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/fhttp/httprunner_test.go new/fortio-1.22.0/fhttp/httprunner_test.go --- old/fortio-1.21.2/fhttp/httprunner_test.go 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/fhttp/httprunner_test.go 2022-03-20 02:19:30.000000000 +0100 @@ -187,7 +187,7 @@ func TestClosingAndSocketCount(t *testing.T) { mux, addr := DynamicHTTPServer(false) mux.HandleFunc("/echo42/", EchoHandler) - URL := fmt.Sprintf("http://localhost:%d/echo42/?close=1", addr.Port) + URL := fmt.Sprintf("http://localhost:%d/echo42/?close=true", addr.Port) opts := HTTPRunnerOptions{} opts.Init(URL) opts.QPS = 10 @@ -234,10 +234,10 @@ _, addr := ServeTCP("0", "/debugx1") port := addr.Port log.Infof("On addr %s found port: %d", addr, port) - url := fmt.Sprintf("http://localhost:%d/debugx1?env=dump", port) if port == 0 { t.Errorf("outport: %d must be different", port) } + url := fmt.Sprintf("http://localhost:%d/debugx1?env=dump", port) time.Sleep(100 * time.Millisecond) o := NewHTTPOptions(url) o.AddAndValidateExtraHeader("X-Header: value1") @@ -253,6 +253,17 @@ if !strings.Contains(string(data), "X-Header: value1,value2") { t.Errorf("Multi header not found in %s", DebugSummary(data, 1024)) } + url2 := fmt.Sprintf("http://localhost:%d/debugx1/echo/foo", port) + o2 := NewHTTPOptions(url2) + o2.Payload = []byte("abcd") + c2, _ := NewClient(o2) + code2, data2, header := c2.Fetch() + if code2 != http.StatusOK { + t.Errorf("Unexpected non 200 ret code for debug url %s : %d", url, code) + } + if string(data2[header:]) != "abcd" { + t.Errorf("Unexpected that %s isn't an echo server, got %q", url2, string(data2)) + } } func TestAbortOn(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/fortio_main.go new/fortio-1.22.0/fortio_main.go --- old/fortio-1.21.2/fortio_main.go 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/fortio_main.go 2022-03-20 02:19:30.000000000 +0100 @@ -180,6 +180,7 @@ "file `path` to log all requests to. Maybe have performance impacts") accessLogFileFormat = flag.String("access-log-format", "json", "`format` for access log. Supported values: [json, influx]") + calcQPS = flag.Bool("calc-qps", false, "Calculate the qps based on number of requests (-n) and duration (-t)") ) // nolint: funlen // well yes it's fairly big and lotsa ifs. @@ -345,7 +346,7 @@ } } -// nolint: funlen // maybe refactor/shorten later. +// nolint: funlen, gocognit // maybe refactor/shorten later. func fortioLoad(justCurl bool, percList []float64) { if len(flag.Args()) != 1 { usageErr("Error: fortio load/curl needs a url or destination") @@ -359,6 +360,13 @@ prevGoMaxProcs := runtime.GOMAXPROCS(*goMaxProcsFlag) out := os.Stderr qps := *qpsFlag // TODO possibly use translated <=0 to "max" from results/options normalization in periodic/ + if *calcQPS { + if *exactlyFlag == 0 || *durationFlag <= 0 { + usageErr("Error: can't use `-calc-qps` without also specifying `-n` and `-t`") + } + qps = float64(*exactlyFlag) / (*durationFlag).Seconds() + log.LogVf("Calculated QPS to do %d request in %v: %f", *exactlyFlag, *durationFlag, qps) + } _, _ = fmt.Fprintf(out, "Fortio %s running at %g queries per second, %d->%d procs", version.Short(), qps, prevGoMaxProcs, runtime.GOMAXPROCS(0)) if *exactlyFlag > 0 { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/ui/restHandler.go new/fortio-1.22.0/ui/restHandler.go --- old/fortio-1.21.2/ui/restHandler.go 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/ui/restHandler.go 2022-03-20 02:19:30.000000000 +0100 @@ -100,6 +100,7 @@ // RESTRunHandler is api version of UI submit handler. func RESTRunHandler(w http.ResponseWriter, r *http.Request) { // nolint: funlen fhttp.LogRequest(r, "REST Run Api call") + w.Header().Set("Content-Type", "application/json") data, err := ioutil.ReadAll(r.Body) // must be done before calling FormValue if err != nil { log.Errf("Error reading %v", err) @@ -136,13 +137,13 @@ jitter := (FormValue(r, jd, "jitter") == "on") uniform := (FormValue(r, jd, "uniform") == "on") stdClient := (FormValue(r, jd, "stdclient") == "on") - sequentialWarmup := (r.FormValue("sequential-warmup") == "on") + sequentialWarmup := (FormValue(r, jd, "sequential-warmup") == "on") httpsInsecure := (FormValue(r, jd, "https-insecure") == "on") resolve := FormValue(r, jd, "resolve") timeoutStr := strings.TrimSpace(FormValue(r, jd, "timeout")) timeout, _ := time.ParseDuration(timeoutStr) // will be 0 if empty, which is handled by runner and opts var dur time.Duration - if durStr == "on" || ((len(r.Form["t"]) > 1) && r.Form["t"][1] == "on") { + if durStr == "on" { dur = -1 } else { var err error @@ -192,7 +193,6 @@ if len(payload) > 0 { httpopts.Payload = []byte(payload) } - // mode == run case: for _, header := range r.Form["H"] { if len(header) == 0 { continue @@ -203,6 +203,26 @@ log.Errf("Error adding custom headers: %v", err) } } + jsonHeaders, found := jd["headers"] + for found { // really an if, but using while to break out without else below + res, ok := jsonHeaders.([]interface{}) + if !ok { + log.Warnf("Json Headers is %T %v / not an array, can't be used", jsonHeaders, jsonHeaders) + break + } + for _, header := range res { + log.LogVf("adding json header %T: %v", header, header) + hStr, ok := header.(string) + if !ok { + log.Errf("Json headers must be an array of strings (got %T: %v)", header, header) + continue + } + if err := httpopts.AddAndValidateExtraHeader(hStr); err != nil { + log.Errf("Error adding custom json headers: %v", err) + } + } + break + } fhttp.OnBehalfOf(httpopts, r) if async { w.Write([]byte(fmt.Sprintf("{\"started\": %d}", runid))) @@ -281,7 +301,6 @@ // async, no result to output return } - w.Header().Set("Content-Type", "application/json") _, err = w.Write(json) if err != nil { log.Errf("Unable to write json output for %v: %v", r.RemoteAddr, err) @@ -291,6 +310,7 @@ // RESTStatusHandler will print the state of the runs. func RESTStatusHandler(w http.ResponseWriter, r *http.Request) { fhttp.LogRequest(r, "REST Status Api call") + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusServiceUnavailable) w.Write([]byte("{\"error\":\"status not yet implemented\"}")) } @@ -298,6 +318,7 @@ // RESTStopHandler is the api to stop a given run by runid or all the runs if unspecified/0. func RESTStopHandler(w http.ResponseWriter, r *http.Request) { fhttp.LogRequest(r, "REST Stop Api call") + w.Header().Set("Content-Type", "application/json") runid, _ := strconv.ParseInt(r.FormValue("runid"), 10, 64) i := StopByRunID(runid) w.Write([]byte(fmt.Sprintf("{\"stopped\": %d}", i))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/ui/templates/browse.html new/fortio-1.22.0/ui/templates/browse.html --- old/fortio-1.21.2/ui/templates/browse.html 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/ui/templates/browse.html 2022-03-20 02:19:30.000000000 +0100 @@ -1,6 +1,6 @@ <!DOCTYPE html><html><head><title>???????????? v{{.Version}}</title> <script src="{{.ChartJSPath}}"></script> -<link rel="icon" href="../favicon.ico" /> +<link rel="icon" href="{{.Version}}/static/img/favicon.ico"/> <style> a:link { text-decoration: none; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/ui/templates/main.html new/fortio-1.22.0/ui/templates/main.html --- old/fortio-1.21.2/ui/templates/main.html 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/ui/templates/main.html 2022-03-20 02:19:30.000000000 +0100 @@ -3,7 +3,7 @@ <head><title>???????????? v{{.Version}}</title> <script src="{{.ChartJSPath}}"></script> <script src="{{.Version}}/static/js/fortio_chart.js"></script> - <link rel="icon" href="../favicon.ico"/> + <link rel="icon" href="{{.Version}}/static/img/favicon.ico"/> <link rel="stylesheet" href="{{.Version}}/static/css/fortio.css"> </head> {{template "header" .}} @@ -94,8 +94,11 @@ </div> </form> <p><i>Or</i></p> -<a href="{{.DebugPath}}">debug</a> and <a href="{{.DebugPath}}?env=dump">debug with env dump</a> and <a href="{{.DebugPath}}/pprof/">Internal PPROF</a> +<a href="{{.DebugPath}}">debug</a> and <a href="{{.DebugPath}}?env=dump">debug with env dump</a> +and <a href="{{.DebugPath}}/pprof/">Internal PPROF</a> and <a href="flags">Command line flags</a> +and <a href="{{.DebugPath}}/echo/">Additional echo handler under {{.DebugPath}}/echo/</a> (supports all the +query arguments mentioned on the <a href="https://github.com/fortio/fortio#server-urls-and-features">Echo url features</a> github doc) <p><i>Or</i></p> <form action="sync"> <div> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.21.2/ui/templates/sync.html new/fortio-1.22.0/ui/templates/sync.html --- old/fortio-1.21.2/ui/templates/sync.html 2022-03-18 02:34:29.000000000 +0100 +++ new/fortio-1.22.0/ui/templates/sync.html 2022-03-20 02:19:30.000000000 +0100 @@ -1,6 +1,6 @@ <!DOCTYPE html><html><head><title>???????????? v{{.Version}}</title> <script src="{{.Version}}/static/js/fortio_chart.js"></script> -<link rel="icon" href="../favicon.ico" /> +<link rel="icon" href="{{.Version}}/static/img/favicon.ico" /> <link rel="stylesheet" href="{{.Version}}/static/css/fortio.css"> <style> .checkmark { ++++++ vendor.tar.gz ++++++