Showing 25 of 92 files from the diff.
Other files ignored by Codecov
LICENSE has changed.
.gitignore has changed.
Makefile has changed.
go.sum has changed.
Dockerfile has changed.
Dockerfile.fcurl has changed.
Dockerfile.build has changed.
Webtest.sh has changed.
Dockerfile.test has changed.
go.mod has changed.
README.md has changed.
fortio_main.go has changed.
fcurl/fcurl.go has changed.
fgrpc/ping.pb.go has changed.
ui/uihandler.go has changed.

@@ -14,12 +14,16 @@
Loading
14 14
15 15
package fhttp // import "fortio.org/fortio/fhttp"
16 16
17 +
// pprof import to get /debug/pprof endpoints on a mux through SetupPPROF.
17 18
import (
18 19
	"bytes"
20 +
	"flag"
19 21
	"fmt"
20 22
	"io/ioutil"
21 23
	"net"
22 24
	"net/http"
25 +
	"net/http/pprof"
26 +
	"net/url"
23 27
	"os"
24 28
	"sort"
25 29
	"strconv"
@@ -27,9 +31,7 @@
Loading
27 31
	"sync/atomic"
28 32
	"time"
29 33
30 -
	// get /debug/pprof endpoints on a mux through SetupPPROF
31 -
	"net/http/pprof"
32 -
34 +
	"fortio.org/fortio/dflag"
33 35
	"fortio.org/fortio/fnet"
34 36
	"fortio.org/fortio/log"
35 37
	"fortio.org/fortio/version"
@@ -41,7 +43,9 @@
Loading
41 43
	// Start time of the server (used in debug handler for uptime).
42 44
	startTime time.Time
43 45
	// EchoRequests is the number of request received. Only updated in Debug mode.
44 -
	EchoRequests int64
46 +
	EchoRequests            int64
47 +
	defaultEchoServerParams = dflag.DynString(flag.CommandLine, "echo-server-default-params", "",
48 +
		"Default parameters/querystring to use if there isn't one provided explicitly. E.g \"status=404&delay=3s\"")
45 49
)
46 50
47 51
// EchoHandler is an http server handler echoing back the input.
@@ -49,6 +53,22 @@
Loading
49 53
	if log.LogVerbose() {
50 54
		LogRequest(r, "Echo") // will also print headers
51 55
	}
56 +
	defaultParams := defaultEchoServerParams.Get()
57 +
	hasQuestionMark := strings.Contains(r.RequestURI, "?")
58 +
	if !hasQuestionMark && len(defaultParams) > 0 {
59 +
		newQS := r.RequestURI + "?" + defaultParams
60 +
		log.LogVf("Adding default base query string %q to %v trying %q", defaultParams, r.URL, newQS)
61 +
		nr := http.Request{}
62 +
		nr = *r
63 +
		var err error
64 +
		nr.URL, err = url.ParseRequestURI(newQS)
65 +
		if err != nil {
66 +
			log.Errf("Unexpected error parsing echo-server-default-params: %v", err)
67 +
		} else {
68 +
			nr.Form = nil
69 +
			r = &nr
70 +
		}
71 +
	}
52 72
	data, err := ioutil.ReadAll(r.Body) // must be done before calling FormValue
53 73
	if err != nil {
54 74
		log.Errf("Error reading %v", err)
@@ -171,7 +191,7 @@
Loading
171 191
	// Note: we actually use the fact it's not supported as an error server for tests - need to change that
172 192
	log.Errf("Secure setup not yet supported. Will just close incoming connections for now")
173 193
	listener, addr := fnet.Listen("closing server", "0")
174 -
	//err = http.ServeTLS(listener, nil, "", "") // go 1.9
194 +
	// err = http.ServeTLS(listener, nil, "", "") // go 1.9
175 195
	go func() {
176 196
		err := closingServer(listener)
177 197
		if err != nil {
@@ -228,16 +248,14 @@
Loading
228 248
229 249
// DebugHandler returns debug/useful info to http client.
230 250
func DebugHandler(w http.ResponseWriter, r *http.Request) {
231 -
	if log.LogVerbose() {
232 -
		LogRequest(r, "Debug")
233 -
	}
251 +
	LogRequest(r, "Debug")
234 252
	var buf bytes.Buffer
235 253
	buf.WriteString("Φορτίο version ")
236 254
	buf.WriteString(version.Long())
237 255
	buf.WriteString(" echo debug server up for ")
238 256
	buf.WriteString(fmt.Sprint(RoundDuration(time.Since(startTime))))
239 257
	buf.WriteString(" on ")
240 -
	hostname, _ := os.Hostname() // nolint: gas
258 +
	hostname, _ := os.Hostname()
241 259
	buf.WriteString(hostname)
242 260
	buf.WriteString(" - request from ")
243 261
	buf.WriteString(r.RemoteAddr)
@@ -252,7 +270,7 @@
Loading
252 270
	buf.WriteString("Host: ")
253 271
	buf.WriteString(r.Host)
254 272
255 -
	var keys []string
273 +
	var keys []string // nolint: prealloc // header is multi valued map,...
256 274
	for k := range r.Header {
257 275
		keys = append(keys, k)
258 276
	}
@@ -316,7 +334,7 @@
Loading
316 334
}
317 335
318 336
// ServeTCP is Serve() but restricted to TCP (return address is assumed
319 -
// to be TCP - will panic for unix domain)
337 +
// to be TCP - will panic for unix domain).
320 338
func ServeTCP(port, debugPath string) (*http.ServeMux, *net.TCPAddr) {
321 339
	mux, addr := Serve(port, debugPath)
322 340
	if addr == nil {
@@ -352,13 +370,13 @@
Loading
352 370
		return
353 371
	}
354 372
	// Don't forget to close the connection:
355 -
	defer conn.Close() // nolint: errcheck
373 +
	defer conn.Close()
356 374
	// Stripped prefix gets replaced by ./ - sometimes...
357 375
	url := strings.TrimPrefix(r.URL.String(), "./")
358 376
	opts := NewHTTPOptions("http://" + url)
359 377
	opts.HTTPReqTimeOut = 5 * time.Minute
360 378
	OnBehalfOf(opts, r)
361 -
	client := NewClient(opts)
379 +
	client, _ := NewClient(opts)
362 380
	if client == nil {
363 381
		return // error logged already
364 382
	}
@@ -380,7 +398,7 @@
Loading
380 398
}
381 399
382 400
// RedirectToHTTPS Sets up a redirector to https on the given port.
383 -
// (Do not create a loop, make sure this is addressed from an ingress)
401 +
// (Do not create a loop, make sure this is addressed from an ingress).
384 402
func RedirectToHTTPS(port string) net.Addr {
385 403
	m, a := HTTPServer("https redirector", port)
386 404
	if m == nil {
@@ -390,11 +408,13 @@
Loading
390 408
	return a
391 409
}
392 410
393 -
// LogRequest logs the incoming request, including headers when loglevel is verbose
411 +
// LogRequest logs the incoming request, including headers when loglevel is verbose.
394 412
func LogRequest(r *http.Request, msg string) {
395 413
	log.Infof("%s: %v %v %v %v (%s)", msg, r.Method, r.URL, r.Proto, r.RemoteAddr,
396 414
		r.Header.Get("X-Forwarded-Proto"))
397 415
	if log.LogVerbose() {
416 +
		// Host is removed from headers map and put separately
417 +
		log.LogVf("Header Host: %v", r.Host)
398 418
		for name, headers := range r.Header {
399 419
			for _, h := range headers {
400 420
				log.LogVf("Header %v: %v\n", name, h)
@@ -411,7 +431,7 @@
Loading
411 431
	})
412 432
}
413 433
414 -
// LogAndCallNoArg is LogAndCall for functions not needing the response/request args
434 +
// LogAndCallNoArg is LogAndCall for functions not needing the response/request args.
415 435
func LogAndCallNoArg(msg string, f func()) http.HandlerFunc {
416 436
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
417 437
		LogRequest(r, msg)

@@ -16,7 +16,7 @@
Loading
16 16
17 17
import (
18 18
	"encoding/base64"
19 -
	"fmt"
19 +
	"flag"
20 20
	"html/template"
21 21
	"io"
22 22
	"math/rand"
@@ -26,15 +26,16 @@
Loading
26 26
	"time"
27 27
	"unicode/utf8"
28 28
29 +
	"fortio.org/fortio/dflag"
29 30
	"fortio.org/fortio/fnet"
30 31
	"fortio.org/fortio/log"
31 32
	"fortio.org/fortio/stats"
32 33
)
33 34
34 -
// Used for the fast case insensitive search
35 +
// Used for the fast case insensitive search.
35 36
const toUpperMask = ^byte('a' - 'A')
36 37
37 -
// Slow but correct version
38 +
// Slow but correct version.
38 39
func toUpper(b byte) byte {
39 40
	if b >= 'a' && b <= 'z' {
40 41
		b -= ('a' - 'A')
@@ -108,7 +109,7 @@
Loading
108 109
109 110
// ParseDecimal extracts the first positive integer number from the input.
110 111
// spaces are ignored.
111 -
// any character that isn't a digit cause the parsing to stop
112 +
// any character that isn't a digit cause the parsing to stop.
112 113
func ParseDecimal(inp []byte) int {
113 114
	res := -1
114 115
	for _, b := range inp {
@@ -143,7 +144,7 @@
Loading
143 144
		if off >= end {
144 145
			return off, -1
145 146
		}
146 -
		if inDigits {
147 +
		if inDigits { // nolint: nestif
147 148
			b := toUpper(inp[off])
148 149
			var digit int
149 150
			if b >= 'A' && b <= 'F' {
@@ -180,22 +181,11 @@
Loading
180 181
	}
181 182
}
182 183
183 -
// EscapeBytes returns printable string. Same as %q format without the
184 -
// surrounding/extra "".
185 -
func EscapeBytes(buf []byte) string {
186 -
	e := fmt.Sprintf("%q", buf)
187 -
	return e[1 : len(e)-1]
188 -
}
189 -
190 184
// DebugSummary returns a string with the size and escaped first max/2 and
191 185
// last max/2 bytes of a buffer (or the whole escaped buffer if small enough).
192 186
func DebugSummary(buf []byte, max int) string {
193 -
	l := len(buf)
194 -
	if l <= max+3 { //no point in shortening to add ... if we could return those 3
195 -
		return EscapeBytes(buf)
196 -
	}
197 -
	max /= 2
198 -
	return fmt.Sprintf("%d: %s...%s", l, EscapeBytes(buf[:max]), EscapeBytes(buf[l-max:]))
187 +
	// moved to fnet package
188 +
	return fnet.DebugSummary(buf, max)
199 189
}
200 190
201 191
// -- server utils
@@ -208,7 +198,7 @@
Loading
208 198
}
209 199
210 200
// generateStatus from string, format: status="503" for 100% 503s
211 -
// status="503:20,404:10,403:0.5" for 20% 503s, 10% 404s, 0.5% 403s 69.5% 200s
201 +
// status="503:20,404:10,403:0.5" for 20% 503s, 10% 404s, 0.5% 403s 69.5% 200s.
212 202
func generateStatus(status string) int {
213 203
	lst := strings.Split(status, ",")
214 204
	log.Debugf("Parsing status %s -> %v", status, lst)
@@ -254,7 +244,7 @@
Loading
254 244
		codes[i] = s
255 245
		i++
256 246
	}
257 -
	res := 100. * rand.Float32()
247 +
	res := 100. * rand.Float32() // nolint: gosec // we want fast not crypto
258 248
	for i, v := range weights {
259 249
		if res <= v {
260 250
			log.Debugf("[0.-100.[ for %s roll %f got #%d -> %d", status, res, i, codes[i])
@@ -270,7 +260,7 @@
Loading
270 260
// returns -1 for the default case, so one can specify 0 and force no payload
271 261
// even if it's a post request with a payload (to test asymmetric large inbound
272 262
// small outbound).
273 -
// TODO: refactor similarities with status and delay
263 +
// TODO: refactor similarities with status and delay.
274 264
func generateSize(sizeInput string) (size int) {
275 265
	size = -1 // default value/behavior
276 266
	if len(sizeInput) == 0 {
@@ -323,7 +313,7 @@
Loading
323 313
		sizes[i] = s
324 314
		i++
325 315
	}
326 -
	res := 100. * rand.Float32()
316 +
	res := 100. * rand.Float32() // nolint: gosec // we want fast not crypto
327 317
	for i, v := range weights {
328 318
		if res <= v {
329 319
			log.Debugf("[0.-100.[ for %s roll %f got #%d -> %d", sizeInput, res, i, sizes[i])
@@ -335,8 +325,9 @@
Loading
335 325
}
336 326
337 327
// MaxDelay is the maximum delay allowed for the echoserver responses.
338 -
// 1.5s so we can test the default 1s timeout in envoy.
339 -
const MaxDelay = 1500 * time.Millisecond
328 +
// It is a dynamic flag with default value of 1.5s so we can test the default 1s timeout in envoy.
329 +
var MaxDelay = dflag.DynDuration(flag.CommandLine, "max-echo-delay", 1500*time.Millisecond,
330 +
	"Maximum sleep time for delay= echo server parameter. dynamic flag.")
340 331
341 332
// generateDelay from string, format: delay="100ms" for 100% 100ms delay
342 333
// delay="10ms:20,20ms:10,1s:0.5" for 20% 10ms, 10% 20ms, 0.5% 1s and 69.5% 0
@@ -355,8 +346,8 @@
Loading
355 346
			return -1
356 347
		}
357 348
		log.Debugf("Parsed delay %s -> %d", delay, d)
358 -
		if d > MaxDelay {
359 -
			d = MaxDelay
349 +
		if d > MaxDelay.Get() {
350 +
			d = MaxDelay.Get()
360 351
		}
361 352
		return d
362 353
	}
@@ -375,8 +366,8 @@
Loading
375 366
			log.Warnf("Bad input delay %v -> %v, not a number before colon", delay, l2[0])
376 367
			return -1
377 368
		}
378 -
		if d > MaxDelay {
379 -
			d = MaxDelay
369 +
		if d > MaxDelay.Get() {
370 +
			d = MaxDelay.Get()
380 371
		}
381 372
		percStr := removeTrailingPercent(l2[1])
382 373
		p, err := strconv.ParseFloat(percStr, 32)
@@ -395,7 +386,7 @@
Loading
395 386
		delays[i] = d
396 387
		i++
397 388
	}
398 -
	res := 100. * rand.Float32()
389 +
	res := 100. * rand.Float32() // nolint: gosec // we want fast not crypto
399 390
	for i, v := range weights {
400 391
		if res <= v {
401 392
			log.Debugf("[0.-100.[ for %s roll %f got #%d -> %d", delay, res, i, delays[i])
@@ -406,12 +397,9 @@
Loading
406 397
	return 0
407 398
}
408 399
409 -
// RoundDuration rounds to 10th of second. Only for positive durations.
410 -
// TODO: switch to Duration.Round once switched to go 1.9
400 +
// RoundDuration rounds to 10th of second.
411 401
func RoundDuration(d time.Duration) time.Duration {
412 -
	tenthSec := int64(100 * time.Millisecond)
413 -
	r := int64(d+50*time.Millisecond) / tenthSec
414 -
	return time.Duration(tenthSec * r)
402 +
	return d.Round(100 * time.Millisecond)
415 403
}
416 404
417 405
// -- formerly in uihandler:

@@ -0,0 +1,37 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"flag"
8 +
)
9 +
10 +
// DynamicFlagValue interface is a tag to know if a type is dynamic or not.
11 +
type DynamicFlagValue interface {
12 +
	IsDynamicFlag() bool
13 +
}
14 +
15 +
// DynamicJSONFlagValue is a tag interface for JSON dynamic flags.
16 +
type DynamicJSONFlagValue interface {
17 +
	IsJSON() bool
18 +
}
19 +
20 +
// DynamicFlagValueTag is a struct all dynamic flag inherit for marking they are dynamic.
21 +
type DynamicFlagValueTag struct{}
22 +
23 +
// IsDynamicFlag always returns true.
24 +
func (*DynamicFlagValueTag) IsDynamicFlag() bool {
25 +
	return true
26 +
}
27 +
28 +
// A flag is dynamic if it implements DynamicFlagValue (which all the dyn* do)
29 +
30 +
// IsFlagDynamic returns whether the given Flag has been created in a Dynamic mode.
31 +
func IsFlagDynamic(f *flag.Flag) bool {
32 +
	df, ok := f.Value.(DynamicFlagValue)
33 +
	if !ok {
34 +
		return false
35 +
	}
36 +
	return df.IsDynamicFlag() // will clearly return true if it exists
37 +
}

@@ -15,6 +15,7 @@
Loading
15 15
package fnet // import "fortio.org/fortio/fnet"
16 16
17 17
import (
18 +
	"errors"
18 19
	"fmt"
19 20
	"io"
20 21
	"io/ioutil"
@@ -36,32 +37,35 @@
Loading
36 37
	StandardHTTPPort = "80"
37 38
	// StandardHTTPSPort is the Standard https port number.
38 39
	StandardHTTPSPort = "443"
39 -
	// PrefixHTTP is a constant value for representing http protocol that can be added prefix of url
40 +
	// PrefixHTTP is a constant value for representing http protocol that can be added prefix of url.
40 41
	PrefixHTTP = "http://"
41 -
	// PrefixHTTPS is a constant value for representing secure http protocol that can be added prefix of url
42 +
	// PrefixHTTPS is a constant value for representing secure http protocol that can be added prefix of url.
42 43
	PrefixHTTPS = "https://"
43 44
44 -
	// POST is a constant value that indicates http method as post
45 +
	// POST is a constant value that indicates http method as post.
45 46
	POST = "POST"
46 -
	// GET is a constant value that indicates http method as get
47 +
	// GET is a constant value that indicates http method as get.
47 48
	GET = "GET"
48 49
	// UnixDomainSocket type for network addresses.
49 50
	UnixDomainSocket = "unix"
50 51
)
51 52
52 53
var (
54 +
	// KILOBYTE is a constant for kilobyte (ie 1024).
55 +
	KILOBYTE = 1024
53 56
	// MaxPayloadSize is the maximum size of payload to be generated by the
54 57
	// EchoHandler size= argument. In bytes.
55 -
	MaxPayloadSize = 256 * 1024
56 -
	// Payload that is returned during echo call
58 +
	MaxPayloadSize = 256 * KILOBYTE
59 +
	// Payload that is returned during echo call.
57 60
	Payload []byte
58 61
)
59 62
63 +
// nolint: gochecknoinits // needed here (unit change)
60 64
func init() {
61 65
	ChangeMaxPayloadSize(MaxPayloadSize)
62 66
}
63 67
64 -
// ChangeMaxPayloadSize is used to change max payload size and fill it with pseudorandom content
68 +
// ChangeMaxPayloadSize is used to change max payload size and fill it with pseudorandom content.
65 69
func ChangeMaxPayloadSize(newMaxPayloadSize int) {
66 70
	if newMaxPayloadSize >= 0 {
67 71
		MaxPayloadSize = newMaxPayloadSize
@@ -70,9 +74,8 @@
Loading
70 74
	}
71 75
	Payload = make([]byte, MaxPayloadSize)
72 76
	// One shared and 'constant' (over time) but pseudo random content for payload
73 -
	// (to defeat compression). We don't need crypto strength here, just low cpu
74 -
	// and speed:
75 -
	_, err := rand.Read(Payload)
77 +
	// (to defeat compression).
78 +
	_, err := rand.Read(Payload) // nolint: gosec // We don't need crypto strength here, just low cpu and speed
76 79
	if err != nil {
77 80
		log.Errf("Error changing payload size, read for %d random payload failed: %v", newMaxPayloadSize, err)
78 81
	}
@@ -114,6 +117,35 @@
Loading
114 117
	return listener, lAddr
115 118
}
116 119
120 +
func handleTCPEchoRequest(name string, conn net.Conn) {
121 +
	SetSocketBuffers(conn, 32*KILOBYTE, 32*KILOBYTE)
122 +
	wb, err := Copy(conn, conn) // io.Copy(conn, conn)
123 +
	log.LogVf("TCP echo server (%v) echoed %d bytes from %v to itself (err=%v)", name, wb, conn.RemoteAddr(), err)
124 +
	_ = conn.Close()
125 +
}
126 +
127 +
// TCPEchoServer starts a TCP Echo Server on given port, name is for logging.
128 +
func TCPEchoServer(name string, port string) net.Addr {
129 +
	listener, addr := Listen(name, port)
130 +
	if listener == nil {
131 +
		return nil // error already logged
132 +
	}
133 +
	go func() {
134 +
		for {
135 +
			// TODO limit number of go request, maximum duration/bytes sent, etc...
136 +
			conn, err := listener.Accept()
137 +
			if err != nil {
138 +
				log.Critf("TCP echo server (%v) error accepting: %v", name, err) // will this loop with error?
139 +
			} else {
140 +
				log.LogVf("TCP echo server (%v) accepted connection from %v -> %v",
141 +
					name, conn.RemoteAddr(), conn.LocalAddr())
142 +
				go handleTCPEchoRequest(name, conn)
143 +
			}
144 +
		}
145 +
	}()
146 +
	return addr
147 +
}
148 +
117 149
// GetPort extracts the port for TCP sockets and the path for unix domain sockets.
118 150
func GetPort(lAddr net.Addr) string {
119 151
	var lPort string
@@ -128,11 +160,11 @@
Loading
128 160
129 161
// ResolveDestination returns the TCP address of the "host:port" suitable for net.Dial.
130 162
// nil in case of errors.
131 -
func ResolveDestination(dest string) net.Addr {
163 +
func ResolveDestination(dest string) (*net.TCPAddr, error) {
132 164
	i := strings.LastIndex(dest, ":") // important so [::1]:port works
133 165
	if i < 0 {
134 166
		log.Errf("Destination '%s' is not host:port format", dest)
135 -
		return nil
167 +
		return nil, fmt.Errorf("destination '%s' is not host:port format", dest)
136 168
	}
137 169
	host := dest[0:i]
138 170
	port := dest[i+1:]
@@ -141,7 +173,7 @@
Loading
141 173
142 174
// Resolve returns the TCP address of the host,port suitable for net.Dial.
143 175
// nil in case of errors.
144 -
func Resolve(host string, port string) net.Addr {
176 +
func Resolve(host string, port string) (*net.TCPAddr, error) {
145 177
	log.Debugf("Resolve() called with host=%s port=%s", host, port)
146 178
	dest := &net.TCPAddr{}
147 179
	if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
@@ -158,7 +190,7 @@
Loading
158 190
		addrs, err = net.LookupIP(host)
159 191
		if err != nil {
160 192
			log.Errf("Unable to lookup '%s' : %v", host, err)
161 -
			return nil
193 +
			return nil, err
162 194
		}
163 195
		if len(addrs) > 1 && log.LogDebug() {
164 196
			log.Debugf("Using only the first of the addresses for %s : %v", host, addrs)
@@ -169,9 +201,61 @@
Loading
169 201
	dest.Port, err = net.LookupPort("tcp", port)
170 202
	if err != nil {
171 203
		log.Errf("Unable to resolve port '%s' : %v", port, err)
172 -
		return nil
204 +
		return nil, err
205 +
	}
206 +
	return dest, nil
207 +
}
208 +
209 +
// Copy is a debug version of io.Copy without the zero Copy optimizations.
210 +
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
211 +
	buf := make([]byte, 32*KILOBYTE)
212 +
	for {
213 +
		nr, er := src.Read(buf)
214 +
		log.Debugf("read %d from %+v: %v", nr, src, er)
215 +
		if nr > 0 {
216 +
			nw, ew := dst.Write(buf[0:nr])
217 +
			log.Debugf("wrote %d (expected %d) to %+v: %v", nw, nr, dst, ew)
218 +
			if nw > 0 {
219 +
				written += int64(nw)
220 +
			}
221 +
			if ew != nil {
222 +
				log.Errf("copy: %+v -> %+v write error: %v", src, dst, ew)
223 +
				err = ew
224 +
				break
225 +
			}
226 +
			if nr != nw {
227 +
				err = io.ErrShortWrite
228 +
				break
229 +
			}
230 +
		}
231 +
		if er != nil {
232 +
			if !errors.Is(er, io.EOF) {
233 +
				err = er
234 +
				log.Errf("copy: %+v -> %+v read error: %v", src, dst, er)
235 +
			}
236 +
			break
237 +
		}
238 +
	}
239 +
	return written, err
240 +
}
241 +
242 +
// SetSocketBuffers sets the read and write buffer size of the socket. Also sets tcp SetNoDelay().
243 +
func SetSocketBuffers(socket net.Conn, readBufferSize, writeBufferSize int) {
244 +
	tcpSock, ok := socket.(*net.TCPConn)
245 +
	if !ok {
246 +
		log.LogVf("Not setting socket options on non tcp socket %v", socket.RemoteAddr())
247 +
		return
248 +
	}
249 +
	// For now those errors are not critical/breaking
250 +
	if err := tcpSock.SetNoDelay(true); err != nil {
251 +
		log.Warnf("Unable to connect to set tcp no delay %+v: %v", socket, err)
252 +
	}
253 +
	if err := tcpSock.SetWriteBuffer(writeBufferSize); err != nil {
254 +
		log.Warnf("Unable to connect to set write buffer %d %+v: %v", writeBufferSize, socket, err)
255 +
	}
256 +
	if err := tcpSock.SetReadBuffer(readBufferSize); err != nil {
257 +
		log.Warnf("Unable to connect to read buffer %d %+v: %v", readBufferSize, socket, err)
173 258
	}
174 -
	return dest
175 259
}
176 260
177 261
func transfer(wg *sync.WaitGroup, dst net.Conn, src net.Conn) {
@@ -194,8 +278,11 @@
Loading
194 278
	wg.Done()
195 279
}
196 280
281 +
// ErrNilDestination returned when trying to proxy to a nil address.
282 +
var ErrNilDestination = fmt.Errorf("nil destination")
283 +
197 284
func handleProxyRequest(conn net.Conn, dest net.Addr) {
198 -
	err := fmt.Errorf("nil destination")
285 +
	err := ErrNilDestination
199 286
	var d net.Conn
200 287
	if dest != nil {
201 288
		d, err = net.Dial(dest.Network(), dest.String())
@@ -206,7 +293,7 @@
Loading
206 293
		return
207 294
	}
208 295
	var wg sync.WaitGroup
209 -
	wg.Add(2)
296 +
	wg.Add(2) // 2 threads to wait for...
210 297
	go transfer(&wg, d, conn)
211 298
	transfer(&wg, conn, d)
212 299
	wg.Wait()
@@ -239,13 +326,14 @@
Loading
239 326
}
240 327
241 328
// ProxyToDestination opens a proxy from the listenPort (or addr:port or unix domain socket path) and forwards
242 -
// all traffic to destination (host:port)
329 +
// all traffic to destination (host:port).
243 330
func ProxyToDestination(listenPort string, destination string) net.Addr {
244 -
	return Proxy(listenPort, ResolveDestination(destination))
331 +
	addr, _ := ResolveDestination(destination)
332 +
	return Proxy(listenPort, addr)
245 333
}
246 334
247 335
// NormalizeHostPort generates host:port string for the address or uses localhost instead of [::]
248 -
// when the original port binding input didn't specify an address
336 +
// when the original port binding input didn't specify an address.
249 337
func NormalizeHostPort(inputPort string, addr net.Addr) string {
250 338
	urlHostPort := addr.String()
251 339
	if addr.Network() == UnixDomainSocket {
@@ -259,7 +347,7 @@
Loading
259 347
}
260 348
261 349
// ValidatePayloadSize compares input size with MaxPayLoadSize. If size exceeds the MaxPayloadSize
262 -
// size will set to MaxPayLoadSize
350 +
// size will set to MaxPayLoadSize.
263 351
func ValidatePayloadSize(size *int) {
264 352
	if *size > MaxPayloadSize && *size > 0 {
265 353
		log.Warnf("Requested size %d greater than max size %d, using max instead (change max using -maxpayloadsizekb)",
@@ -271,13 +359,13 @@
Loading
271 359
	}
272 360
}
273 361
274 -
// GenerateRandomPayload generates a random payload with given input size
362 +
// GenerateRandomPayload generates a random payload with given input size.
275 363
func GenerateRandomPayload(payloadSize int) []byte {
276 364
	ValidatePayloadSize(&payloadSize)
277 365
	return Payload[:payloadSize]
278 366
}
279 367
280 -
// ReadFileForPayload reads the file from given input path
368 +
// ReadFileForPayload reads the file from given input path.
281 369
func ReadFileForPayload(payloadFilePath string) ([]byte, error) {
282 370
	data, err := ioutil.ReadFile(payloadFilePath)
283 371
	if err != nil {
@@ -287,7 +375,7 @@
Loading
287 375
}
288 376
289 377
// GeneratePayload generates a payload with given inputs.
290 -
// First tries filePath, then random payload, at last payload
378 +
// First tries filePath, then random payload, at last payload.
291 379
func GeneratePayload(payloadFilePath string, payloadSize int, payload string) []byte {
292 380
	if len(payloadFilePath) > 0 {
293 381
		p, err := ReadFileForPayload(payloadFilePath)
@@ -319,3 +407,86 @@
Loading
319 407
	_ = os.Remove(fname)
320 408
	return fname
321 409
}
410 +
411 +
// SmallReadUntil will read one byte at a time until stopByte is found and up to max bytes total.
412 +
// Returns what was read (without the stop byte when found), whether the stop byte was found, whether an error occurred (eof...).
413 +
// Because we read one by one directly (no buffer) this should only be used for short variable length preamble type read.
414 +
func SmallReadUntil(r io.Reader, stopByte byte, max int) ([]byte, bool, error) {
415 +
	buf := make([]byte, max)
416 +
	i := 0
417 +
	for i < max {
418 +
		n, err := r.Read(buf[i : i+1])
419 +
		if err != nil {
420 +
			return buf[0:i], false, err
421 +
		}
422 +
		if n != 1 {
423 +
			log.Critf("Bug/unexpected case, read %d instead of 1 byte yet no error", n)
424 +
		}
425 +
		if buf[i] == stopByte {
426 +
			return buf[0:i], true, nil
427 +
		}
428 +
		i += n
429 +
	}
430 +
	return buf[0:i], false, nil
431 +
}
432 +
433 +
// NetCat connects to the destination and reads from in, sends to the socket, and write what it reads from the socket to out.
434 +
func NetCat(dest string, in io.Reader, out io.Writer, stopOnEOF bool) error {
435 +
	log.Infof("NetCat to %s, stop on eof %v", dest, stopOnEOF)
436 +
	a, err := ResolveDestination(dest)
437 +
	if a == nil {
438 +
		return err // already logged
439 +
	}
440 +
	d, err := net.DialTCP("tcp", nil, a)
441 +
	if err != nil {
442 +
		log.Errf("Connection error to %q: %v", dest, err)
443 +
		return err
444 +
	}
445 +
	var wg sync.WaitGroup
446 +
	wg.Add(1)
447 +
	var wb int64
448 +
	var we error
449 +
	go func(w *sync.WaitGroup, src io.Reader, dst *net.TCPConn) {
450 +
		wb, we = Copy(dst, src)
451 +
		_ = dst.CloseWrite()
452 +
		w.Done()
453 +
	}(&wg, in, d)
454 +
	rb, re := Copy(out, d)
455 +
	log.Infof("Read %d from %s (err=%v)", rb, dest, re)
456 +
	if !stopOnEOF {
457 +
		wg.Wait()
458 +
	}
459 +
	log.Infof("Wrote %d to %s (err=%v)", wb, dest, we)
460 +
	_ = d.Close()
461 +
	if c, ok := in.(io.Closer); ok {
462 +
		_ = c.Close()
463 +
	}
464 +
	if c, ok := out.(io.Closer); ok {
465 +
		_ = c.Close()
466 +
	}
467 +
	if re != nil {
468 +
		return re
469 +
	}
470 +
	if we != nil {
471 +
		return we
472 +
	}
473 +
	return nil
474 +
}
475 +
476 +
// EscapeBytes returns printable string. Same as %q format without the
477 +
// surrounding/extra "".
478 +
func EscapeBytes(buf []byte) string {
479 +
	e := fmt.Sprintf("%q", buf)
480 +
	return e[1 : len(e)-1]
481 +
}
482 +
483 +
// DebugSummary returns a string with the size and escaped first max/2 and
484 +
// last max/2 bytes of a buffer (or the whole escaped buffer if small enough).
485 +
func DebugSummary(buf []byte, max int) string {
486 +
	l := len(buf)
487 +
	if l <= max+3 { // no point in shortening to add ... if we could return those 3
488 +
		return EscapeBytes(buf)
489 +
	}
490 +
	max /= 2
491 +
	return fmt.Sprintf("%d: %s...%s", l, EscapeBytes(buf[:max]), EscapeBytes(buf[l-max:]))
492 +
}

@@ -21,10 +21,13 @@
Loading
21 21
	"log"
22 22
	"runtime"
23 23
	"strings"
24 +
	"sync/atomic"
25 +
26 +
	"fortio.org/fortio/dflag"
24 27
)
25 28
26 29
// Level is the level of logging (0 Debug -> 6 Fatal).
27 -
type Level int
30 +
type Level int32
28 31
29 32
// Log levels. Go can't have variable and function of the same name so we keep
30 33
// medium length (Dbg,Info,Warn,Err,Crit,Fatal) names for the functions.
@@ -39,16 +42,30 @@
Loading
39 42
)
40 43
41 44
var (
42 -
	level       = Info // default is Info and up
43 45
	levelToStrA []string
44 46
	levelToStrM map[string]Level
45 47
	// LogPrefix is a prefix to include in each log line.
46 48
	LogPrefix = flag.String("logprefix", "> ", "Prefix to log lines before logged messages")
47 49
	// LogFileAndLine determines if the log lines will contain caller file name and line number.
48 50
	LogFileAndLine = flag.Bool("logcaller", true, "Logs filename and line number of callers to log")
51 +
	levelInternal  int32
49 52
)
50 53
54 +
// SetFlagDefaultsForClientTools changes the default value of -logprefix and -logcaller
55 +
// to make output without caller and prefix, a default more suitable for command line tools (like dnsping).
56 +
// Needs to be called before flag.Parse().
57 +
func SetFlagDefaultsForClientTools() {
58 +
	lcf := flag.Lookup("logcaller")
59 +
	lcf.DefValue = "false"
60 +
	_ = lcf.Value.Set("false")
61 +
	lpf := flag.Lookup("logprefix")
62 +
	lpf.DefValue = ""
63 +
	_ = lpf.Value.Set("")
64 +
}
65 +
66 +
// nolint: gochecknoinits // needed
51 67
func init() {
68 +
	setLevel(Info) // starting value
52 69
	levelToStrA = []string{
53 70
		"Debug",
54 71
		"Verbose",
@@ -64,32 +81,45 @@
Loading
64 81
		levelToStrM[name] = Level(l)
65 82
		levelToStrM[strings.ToLower(name)] = Level(l)
66 83
	}
67 -
	flag.Var(&level, "loglevel", fmt.Sprintf("loglevel, one of %v", levelToStrA))
84 +
	// virtual dynLevel flag that maps back to actual level
85 +
	_ = dflag.DynString(flag.CommandLine, "loglevel", GetLogLevel().String(),
86 +
		fmt.Sprintf("loglevel, one of %v", levelToStrA)).WithValidator(func(new string) error {
87 +
		_, err := ValidateLevel(new)
88 +
		return err
89 +
	}).WithSyncNotifier(func(old, new string) {
90 +
		_ = setLogLevelStr(new) // will succeed as we just validated it first
91 +
	})
68 92
	log.SetFlags(log.Ltime)
69 93
}
70 94
71 -
// String returns the string representation of the level.
72 -
// Needed for flag Var interface.
73 -
func (l *Level) String() string {
74 -
	return (*l).ToString()
95 +
func setLevel(lvl Level) {
96 +
	atomic.StoreInt32(&levelInternal, int32(lvl))
75 97
}
76 98
77 -
// ToString returns the string representation of the level.
78 -
// (this can't be the same name as the pointer receiver version)
79 -
func (l Level) ToString() string {
99 +
// String returns the string representation of the level.
100 +
func (l Level) String() string {
80 101
	return levelToStrA[l]
81 102
}
82 103
83 -
// Set is called by the flags.
84 -
func (l *Level) Set(str string) error {
104 +
// ValidateLevel returns error if the level string is not valid.
105 +
func ValidateLevel(str string) (Level, error) {
85 106
	var lvl Level
86 107
	var ok bool
87 -
	if lvl, ok = levelToStrM[str]; !ok {
88 -
		// flag processing already logs the value
89 -
		return fmt.Errorf("should be one of %v", levelToStrA)
108 +
	if lvl, ok = levelToStrM[strings.TrimSpace(str)]; !ok {
109 +
		return -1, fmt.Errorf("should be one of %v", levelToStrA)
110 +
	}
111 +
	return lvl, nil
112 +
}
113 +
114 +
// Sets from string.
115 +
func setLogLevelStr(str string) error {
116 +
	var lvl Level
117 +
	var err error
118 +
	if lvl, err = ValidateLevel(str); err != nil {
119 +
		return err
90 120
	}
91 121
	SetLogLevel(lvl)
92 -
	return nil
122 +
	return err // nil
93 123
}
94 124
95 125
// SetLogLevel sets the log level and returns the previous one.
@@ -106,7 +136,7 @@
Loading
106 136
// setLogLevel sets the log level and returns the previous one.
107 137
// if logChange is true the level change is logged.
108 138
func setLogLevel(lvl Level, logChange bool) Level {
109 -
	prev := level
139 +
	prev := GetLogLevel()
110 140
	if lvl < Debug {
111 141
		log.Printf("SetLogLevel called with level %d lower than Debug!", lvl)
112 142
		return -1
@@ -117,21 +147,21 @@
Loading
117 147
	}
118 148
	if lvl != prev {
119 149
		if logChange {
120 -
			logPrintf(Info, "Log level is now %d %s (was %d %s)\n", lvl, lvl.ToString(), prev, prev.ToString())
150 +
			logPrintf(Info, "Log level is now %d %s (was %d %s)\n", lvl, lvl.String(), prev, prev.String())
121 151
		}
122 -
		level = lvl
152 +
		setLevel(lvl)
123 153
	}
124 154
	return prev
125 155
}
126 156
127 157
// GetLogLevel returns the currently configured LogLevel.
128 158
func GetLogLevel() Level {
129 -
	return level
159 +
	return Level(atomic.LoadInt32(&levelInternal))
130 160
}
131 161
132 162
// Log returns true if a given level is currently logged.
133 163
func Log(lvl Level) bool {
134 -
	return lvl >= level
164 +
	return int32(lvl) >= atomic.LoadInt32(&levelInternal)
135 165
}
136 166
137 167
// LevelByName returns the LogLevel by its name.
@@ -140,7 +170,7 @@
Loading
140 170
}
141 171
142 172
// Logf logs with format at the given level.
143 -
// 2 level of calls so it's always same depth for extracting caller file/line
173 +
// 2 level of calls so it's always same depth for extracting caller file/line.
144 174
func Logf(lvl Level, format string, rest ...interface{}) {
145 175
	logPrintf(lvl, format, rest...)
146 176
}
@@ -208,12 +238,30 @@
Loading
208 238
	logPrintf(Fatal, format, rest...)
209 239
}
210 240
211 -
// LogDebug shortcut for fortio.Log(fortio.Debug)
241 +
// LogDebug shortcut for fortio.Log(fortio.Debug).
212 242
func LogDebug() bool { //nolint: golint
213 243
	return Log(Debug)
214 244
}
215 245
216 -
// LogVerbose shortcut for fortio.Log(fortio.Verbose)
246 +
// LogVerbose shortcut for fortio.Log(fortio.Verbose).
217 247
func LogVerbose() bool { //nolint: golint
218 248
	return Log(Verbose)
219 249
}
250 +
251 +
// LoggerI defines a log.Logger like interface for simple logging.
252 +
type LoggerI interface {
253 +
	Printf(format string, rest ...interface{})
254 +
}
255 +
256 +
type loggerShm struct {
257 +
}
258 +
259 +
func (l *loggerShm) Printf(format string, rest ...interface{}) {
260 +
	logPrintf(Info, format, rest...)
261 +
}
262 +
263 +
// Logger returns a LoggerI (standard logger compatible) that can be used for simple logging.
264 +
func Logger() LoggerI {
265 +
	logger := loggerShm{}
266 +
	return &logger
267 +
}

@@ -0,0 +1,31 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"flag"
8 +
	"hash/fnv"
9 +
	"sync"
10 +
)
11 +
12 +
// @todo Temporary fix for race condition happening in the test:
13 +
// spf13/pflag.FlagSet.VisitAll seems to be prone to a race condition
14 +
// This fixes that but I'm not sure how much slower does it make the codebase.
15 +
var visitAllMutex = &sync.Mutex{}
16 +
17 +
// ChecksumFlagSet will generate a FNV of the *set* values in a FlagSet.
18 +
func ChecksumFlagSet(flagSet *flag.FlagSet, flagFilter func(flag *flag.Flag) bool) []byte {
19 +
	h := fnv.New32a()
20 +
21 +
	visitAllMutex.Lock()
22 +
	defer visitAllMutex.Unlock()
23 +
	flagSet.VisitAll(func(flag *flag.Flag) {
24 +
		if flagFilter != nil && !flagFilter(flag) {
25 +
			return
26 +
		}
27 +
		_, _ = h.Write([]byte(flag.Name))
28 +
		_, _ = h.Write([]byte(flag.Value.String()))
29 +
	})
30 +
	return h.Sum(nil)
31 +
}

@@ -16,7 +16,6 @@
Loading
16 16
17 17
import (
18 18
	"fmt"
19 -
	"net/http"
20 19
	"os"
21 20
	"runtime"
22 21
	"runtime/pprof"
@@ -77,6 +76,7 @@
Loading
77 76
}
78 77
79 78
// RunHTTPTest runs an http test and returns the aggregated stats.
79 +
// nolint: funlen
80 80
func RunHTTPTest(o *HTTPRunnerOptions) (*HTTPRunnerResults, error) {
81 81
	o.RunType = "HTTP"
82 82
	log.Infof("Starting http test for %s with %d threads at %.1f qps", o.URL, o.NumThreads, o.QPS)
@@ -97,13 +97,14 @@
Loading
97 97
	for i := 0; i < numThreads; i++ {
98 98
		r.Options().Runners[i] = &httpstate[i]
99 99
		// Create a client (and transport) and connect once for each 'thread'
100 -
		httpstate[i].client = NewClient(&o.HTTPOptions)
100 +
		var err error
101 +
		httpstate[i].client, err = NewClient(&o.HTTPOptions)
101 102
		if httpstate[i].client == nil {
102 -
			return nil, fmt.Errorf("unable to create client %d for %s", i, o.URL)
103 +
			return nil, err
103 104
		}
104 105
		if o.Exactly <= 0 {
105 106
			code, data, headerSize := httpstate[i].client.Fetch()
106 -
			if !o.AllowInitialErrors && code != http.StatusOK {
107 +
			if !o.AllowInitialErrors && !codeIsOK(code) {
107 108
				return nil, fmt.Errorf("error %d for %s: %q", code, o.URL, string(data))
108 109
			}
109 110
			if i == 0 && log.LogVerbose() {
@@ -117,14 +118,16 @@
Loading
117 118
		httpstate[i].AbortOn = total.AbortOn
118 119
		httpstate[i].aborter = total.aborter
119 120
	}
120 -
121 +
	// TODO avoid copy pasta with grpcrunner
121 122
	if o.Profiler != "" {
122 123
		fc, err := os.Create(o.Profiler + ".cpu")
123 124
		if err != nil {
124 125
			log.Critf("Unable to create .cpu profile: %v", err)
125 126
			return nil, err
126 127
		}
127 -
		pprof.StartCPUProfile(fc) //nolint: gas,errcheck
128 +
		if err = pprof.StartCPUProfile(fc); err != nil {
129 +
			log.Critf("Unable to start cpu profile: %v", err)
130 +
		}
128 131
	}
129 132
	total.RunnerResults = r.Run()
130 133
	if o.Profiler != "" {
@@ -134,9 +137,11 @@
Loading
134 137
			log.Critf("Unable to create .mem profile: %v", err)
135 138
			return nil, err
136 139
		}
137 -
		runtime.GC()               // get up-to-date statistics
138 -
		pprof.WriteHeapProfile(fm) // nolint:gas,errcheck
139 -
		fm.Close()                 // nolint:gas,errcheck
140 +
		runtime.GC() // get up-to-date statistics
141 +
		if err = pprof.WriteHeapProfile(fm); err != nil {
142 +
			log.Critf("Unable to write heap profile: %v", err)
143 +
		}
144 +
		fm.Close()
140 145
		_, _ = fmt.Fprintf(out, "Wrote profile data to %s.{cpu|mem}\n", o.Profiler)
141 146
	}
142 147
	// Numthreads may have reduced but it should be ok to accumulate 0s from

@@ -0,0 +1,93 @@
Loading
1 +
// Copyright (c) Improbable Worlds Ltd, All Rights Reserved
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"flag"
8 +
	"fmt"
9 +
	"strconv"
10 +
	"sync/atomic"
11 +
)
12 +
13 +
// DynBool creates a `Flag` that represents `bool` which is safe to change dynamically at runtime.
14 +
func DynBool(flagSet *flag.FlagSet, name string, value bool, usage string) *DynBoolValue {
15 +
	v := boolToInt(value)
16 +
	dynValue := &DynBoolValue{ptr: &v}
17 +
	flagSet.Var(dynValue, name, usage)
18 +
	flagSet.Lookup(name).DefValue = strconv.FormatBool(value)
19 +
	return dynValue
20 +
}
21 +
22 +
// DynBoolValue is a flag-related `int64` value wrapper.
23 +
type DynBoolValue struct {
24 +
	DynamicFlagValueTag
25 +
	ptr       *uint32
26 +
	validator func(bool) error
27 +
	notifier  func(oldValue bool, newValue bool)
28 +
}
29 +
30 +
// IsBoolFlag lets the flag parsing know that -flagname is enough to turn to true.
31 +
func (d *DynBoolValue) IsBoolFlag() bool {
32 +
	return true
33 +
}
34 +
35 +
// Get retrieves the value in a thread-safe manner.
36 +
func (d *DynBoolValue) Get() bool {
37 +
	if d.ptr == nil {
38 +
		return false
39 +
	}
40 +
	return atomic.LoadUint32(d.ptr) == 1
41 +
}
42 +
43 +
// Set updates the value from a string representation in a thread-safe manner.
44 +
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
45 +
// optional validator.
46 +
// If a notifier is set on the value, it will be invoked in a separate go-routine.
47 +
func (d *DynBoolValue) Set(input string) error {
48 +
	val, err := strconv.ParseBool(input)
49 +
	if err != nil {
50 +
		return err
51 +
	}
52 +
	if d.validator != nil {
53 +
		if err := d.validator(val); err != nil {
54 +
			return err
55 +
		}
56 +
	}
57 +
58 +
	oldVal := atomic.SwapUint32(d.ptr, boolToInt(val))
59 +
	if d.notifier != nil {
60 +
		go d.notifier(oldVal == 1, val)
61 +
	}
62 +
	return nil
63 +
}
64 +
65 +
// WithValidator adds a function that checks values before they're set.
66 +
// Any error returned by the validator will lead to the value being rejected.
67 +
// Validators are executed on the same go-routine as the call to `Set`.
68 +
func (d *DynBoolValue) WithValidator(validator func(bool) error) {
69 +
	d.validator = validator
70 +
}
71 +
72 +
// WithNotifier adds a function is called every time a new value is successfully set.
73 +
// Each notifier is executed in a new go-routine.
74 +
func (d *DynBoolValue) WithNotifier(notifier func(oldValue bool, newValue bool)) {
75 +
	d.notifier = notifier
76 +
}
77 +
78 +
// Type is an indicator of what this flag represents.
79 +
func (d *DynBoolValue) Type() string {
80 +
	return "dyn_bool"
81 +
}
82 +
83 +
// String returns the canonical string representation of the type.
84 +
func (d *DynBoolValue) String() string {
85 +
	return fmt.Sprintf("%v", d.Get())
86 +
}
87 +
88 +
func boolToInt(b bool) uint32 {
89 +
	if b {
90 +
		return 1
91 +
	}
92 +
	return 0
93 +
}

@@ -21,22 +21,21 @@
Loading
21 21
	"os"
22 22
	"time"
23 23
24 +
	"fortio.org/fortio/fnet"
25 +
	"fortio.org/fortio/log"
26 +
	"fortio.org/fortio/stats"
24 27
	"golang.org/x/net/context"
25 28
	"google.golang.org/grpc"
26 29
	"google.golang.org/grpc/credentials"
27 30
	"google.golang.org/grpc/health"
28 31
	"google.golang.org/grpc/health/grpc_health_v1"
29 32
	"google.golang.org/grpc/reflection"
30 -
31 -
	"fortio.org/fortio/fnet"
32 -
	"fortio.org/fortio/log"
33 -
	"fortio.org/fortio/stats"
34 33
)
35 34
36 35
const (
37 36
	// DefaultHealthServiceName is the default health service name used by fortio.
38 37
	DefaultHealthServiceName = "ping"
39 -
	//Error indicates that something went wrong with healthcheck in grpc
38 +
	// Error indicates that something went wrong with healthcheck in grpc.
40 39
	Error = "ERROR"
41 40
)
42 41

@@ -0,0 +1,65 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"flag"
8 +
	"fmt"
9 +
	"io/ioutil"
10 +
)
11 +
12 +
// ReadFileFlags parses the flagset to discover all "fileread" flags and evaluates them.
13 +
//
14 +
// By reading and evaluating it means: attempts to read the file and set the value.
15 +
func ReadFileFlags(flagSet *flag.FlagSet) error {
16 +
	var outerErr error
17 +
	flagSet.VisitAll(func(f *flag.Flag) {
18 +
		if frv, ok := f.Value.(*FileReadValue); ok {
19 +
			if err := frv.readFile(); err != nil {
20 +
				outerErr = fmt.Errorf("reading file flag '%v' failed: %w", f.Name, err)
21 +
			}
22 +
		}
23 +
	})
24 +
	return outerErr
25 +
}
26 +
27 +
// FileReadValue is a flag that wraps another flag and makes it readable from a local file in the filesystem.
28 +
type FileReadValue struct {
29 +
	DynamicFlagValueTag
30 +
	parentFlagName string
31 +
	filePath       string
32 +
	flagSet        *flag.FlagSet
33 +
}
34 +
35 +
// FileReadFlag creates a `Flag` that allows you to pass a flag.
36 +
//
37 +
// If defaultFilePath is non empty, the dflag.ReadFileFlags will expect the file to be there.
38 +
func FileReadFlag(flagSet *flag.FlagSet, parentFlagName string, defaultFilePath string) *FileReadValue {
39 +
	dynValue := &FileReadValue{parentFlagName: parentFlagName, filePath: defaultFilePath, flagSet: flagSet}
40 +
	flagSet.Var(dynValue,
41 +
		parentFlagName+"_path",
42 +
		fmt.Sprintf("Path to read contents to a file to read contents of '%v' from.", parentFlagName))
43 +
	return dynValue
44 +
}
45 +
46 +
func (f *FileReadValue) String() string {
47 +
	return fmt.Sprintf("fileread_for(%v)", f.parentFlagName)
48 +
}
49 +
50 +
// Set updates the value from a string representation of the file path.
51 +
func (f *FileReadValue) Set(path string) error {
52 +
	f.filePath = path
53 +
	return nil
54 +
}
55 +
56 +
func (f *FileReadValue) readFile() error {
57 +
	if f.filePath == "" {
58 +
		return nil
59 +
	}
60 +
	data, err := ioutil.ReadFile(f.filePath)
61 +
	if err != nil {
62 +
		return err
63 +
	}
64 +
	return f.flagSet.Set(f.parentFlagName, string(data))
65 +
}

@@ -19,6 +19,7 @@
Loading
19 19
	"bytes"
20 20
	"context"
21 21
	"crypto/tls"
22 +
	"errors"
22 23
	"fmt"
23 24
	"io"
24 25
	"io/ioutil"
@@ -50,7 +51,7 @@
Loading
50 51
	BufferSizeKb = 128
51 52
	// CheckConnectionClosedHeader indicates whether to check for server side connection closed headers.
52 53
	CheckConnectionClosedHeader = false
53 -
	// 'constants', case doesn't matter for those 3
54 +
	// 'constants', case doesn't matter for those 3.
54 55
	contentLengthHeader   = []byte("\r\ncontent-length:")
55 56
	connectionCloseHeader = []byte("\r\nconnection: close")
56 57
	chunkedHeader         = []byte("\r\nTransfer-Encoding: chunked")
@@ -263,23 +264,24 @@
Loading
263 264
}
264 265
265 266
// newHttpRequest makes a new http GET request for url with User-Agent.
266 -
func newHTTPRequest(o *HTTPOptions) *http.Request {
267 +
func newHTTPRequest(o *HTTPOptions) (*http.Request, error) {
267 268
	method := o.Method()
268 269
	var body io.Reader
269 270
	if method == fnet.POST {
270 271
		body = bytes.NewReader(o.Payload)
271 272
	}
273 +
	// nolint: noctx // TODO fixme?
272 274
	req, err := http.NewRequest(method, o.URL, body)
273 275
	if err != nil {
274 276
		log.Errf("Unable to make %s request for %s : %v", method, o.URL, err)
275 -
		return nil
277 +
		return nil, err
276 278
	}
277 279
	req.Header = o.GenerateHeaders()
278 280
	if o.hostOverride != "" {
279 281
		req.Host = o.hostOverride
280 282
	}
281 283
	if !log.LogDebug() {
282 -
		return req
284 +
		return req, nil
283 285
	}
284 286
	bytes, err := httputil.DumpRequestOut(req, false)
285 287
	if err != nil {
@@ -287,11 +289,11 @@
Loading
287 289
	} else {
288 290
		log.Debugf("For URL %s, sending:\n%s", o.URL, bytes)
289 291
	}
290 -
	return req
292 +
	return req, nil
291 293
}
292 294
293 295
// Client object for making repeated requests of the same URL using the same
294 -
// http client (net/http)
296 +
// http client (net/http).
295 297
type Client struct {
296 298
	url       string
297 299
	req       *http.Request
@@ -299,7 +301,7 @@
Loading
299 301
	transport *http.Transport
300 302
}
301 303
302 -
// Close cleans up any resources used by NewStdClient
304 +
// Close cleans up any resources used by NewStdClient.
303 305
func (c *Client) Close() int {
304 306
	log.Debugf("Close() on %+v", c)
305 307
	if c.req != nil {
@@ -316,14 +318,14 @@
Loading
316 318
	return 0 // TODO: find a way to track std client socket usage.
317 319
}
318 320
319 -
// ChangeURL only for standard client, allows fetching a different URL
321 +
// ChangeURL only for standard client, allows fetching a different URL.
320 322
func (c *Client) ChangeURL(urlStr string) (err error) {
321 323
	c.url = urlStr
322 324
	c.req.URL, err = url.Parse(urlStr)
323 325
	return err
324 326
}
325 327
326 -
// Fetch fetches the byte and code for pre created client
328 +
// Fetch fetches the byte and code for pre created client.
327 329
func (c *Client) Fetch() (int, []byte, int) {
328 330
	// req can't be null (client itself would be null in that case)
329 331
	resp, err := c.client.Do(c.req)
@@ -340,11 +342,11 @@
Loading
340 342
		}
341 343
	}
342 344
	data, err = ioutil.ReadAll(resp.Body)
343 -
	resp.Body.Close() //nolint(errcheck)
345 +
	resp.Body.Close()
344 346
	if err != nil {
345 347
		log.Errf("Unable to read response for %s : %v", c.url, err)
346 348
		code := resp.StatusCode
347 -
		if code == http.StatusOK {
349 +
		if codeIsOK(code) {
348 350
			code = http.StatusNoContent
349 351
			log.Warnf("Ok code despite read error, switching code to %d", code)
350 352
		}
@@ -356,8 +358,8 @@
Loading
356 358
}
357 359
358 360
// NewClient creates either a standard or fast client (depending on
359 -
// the DisableFastClient flag)
360 -
func NewClient(o *HTTPOptions) Fetcher {
361 +
// the DisableFastClient flag).
362 +
func NewClient(o *HTTPOptions) (Fetcher, error) {
361 363
	o.Init(o.URL) // For completely new options
362 364
	// For changes to options after init
363 365
	o.URLSchemeCheck()
@@ -368,11 +370,11 @@
Loading
368 370
}
369 371
370 372
// NewStdClient creates a client object that wraps the net/http standard client.
371 -
func NewStdClient(o *HTTPOptions) *Client {
373 +
func NewStdClient(o *HTTPOptions) (*Client, error) {
372 374
	o.Init(o.URL) // also normalizes NumConnections etc to be valid.
373 -
	req := newHTTPRequest(o)
375 +
	req, err := newHTTPRequest(o)
374 376
	if req == nil {
375 -
		return nil
377 +
		return nil, err
376 378
	}
377 379
	tr := http.Transport{
378 380
		MaxIdleConns:        o.NumConnections,
@@ -395,7 +397,7 @@
Loading
395 397
		tr.TLSClientConfig = &tls.Config{}
396 398
		if o.Insecure {
397 399
			log.LogVf("using insecure https")
398 -
			tr.TLSClientConfig.InsecureSkipVerify = true
400 +
			tr.TLSClientConfig.InsecureSkipVerify = true // nolint: gosec // only in Insecure mode
399 401
		}
400 402
		if len(o.Cert) > 0 && len(o.Key) > 0 {
401 403
			cert, err := tls.LoadX509KeyPair(o.Cert, o.Key)
@@ -421,7 +423,7 @@
Loading
421 423
			return http.ErrUseLastResponse
422 424
		}
423 425
	}
424 -
	return &client
426 +
	return &client, nil
425 427
}
426 428
427 429
// FetchURL fetches the data at the given url using the standard client and default options.
@@ -438,7 +440,7 @@
Loading
438 440
// Fetch creates a client an performs a fetch according to the http options passed in.
439 441
// To be used only for single fetches or when performance doesn't matter as the client is closed at the end.
440 442
func Fetch(httpOptions *HTTPOptions) (int, []byte) {
441 -
	cli := NewClient(httpOptions)
443 +
	cli, _ := NewClient(httpOptions)
442 444
	code, data, _ := cli.Fetch()
443 445
	cli.Close()
444 446
	return code, data
@@ -466,7 +468,7 @@
Loading
466 468
	reqTimeout   time.Duration
467 469
}
468 470
469 -
// Close cleans up any resources used by FastClient
471 +
// Close cleans up any resources used by FastClient.
470 472
func (c *FastClient) Close() int {
471 473
	log.Debugf("Closing %p %s socket count %d", c, c.url, c.socketCount)
472 474
	if c.socket != nil {
@@ -481,7 +483,7 @@
Loading
481 483
// NewFastClient makes a basic, efficient http 1.0/1.1 client.
482 484
// This function itself doesn't need to be super efficient as it is created at
483 485
// the beginning and then reused many times.
484 -
func NewFastClient(o *HTTPOptions) Fetcher {
486 +
func NewFastClient(o *HTTPOptions) (Fetcher, error) {
485 487
	method := o.Method()
486 488
	payloadLen := len(o.Payload)
487 489
	o.Init(o.URL)
@@ -493,35 +495,40 @@
Loading
493 495
	url, err := url.Parse(o.URL)
494 496
	if err != nil {
495 497
		log.Errf("Bad url '%s' : %v", o.URL, err)
496 -
		return nil
498 +
		return nil, err
497 499
	}
498 500
	if url.Scheme != "http" {
499 501
		log.Errf("Only http is supported with the optimized client, use -stdclient for url %s", o.URL)
500 -
		return nil
502 +
		return nil, fmt.Errorf("only http for fast client")
501 503
	}
502 504
	// note: Host includes the port
503 -
	bc := FastClient{url: o.URL, host: url.Host, hostname: url.Hostname(), port: url.Port(),
504 -
		http10: o.HTTP10, halfClose: o.AllowHalfClose}
505 +
	bc := FastClient{
506 +
		url: o.URL, host: url.Host, hostname: url.Hostname(), port: url.Port(),
507 +
		http10: o.HTTP10, halfClose: o.AllowHalfClose,
508 +
	}
505 509
	bc.buffer = make([]byte, BufferSizeKb*1024)
506 510
	if bc.port == "" {
507 511
		bc.port = url.Scheme // ie http which turns into 80 later
508 512
		log.LogVf("No port specified, using %s", bc.port)
509 513
	}
510 514
	var addr net.Addr
511 -
	if o.UnixDomainSocket != "" {
515 +
	if o.UnixDomainSocket != "" { // nolint: nestif
512 516
		log.Infof("Using unix domain socket %v instead of %v %v", o.UnixDomainSocket, bc.hostname, bc.port)
513 517
		uds := &net.UnixAddr{Name: o.UnixDomainSocket, Net: fnet.UnixDomainSocket}
514 518
		addr = uds
515 519
	} else {
520 +
		var tAddr *net.TCPAddr // strangely we get a non nil wrap of nil if assigning to addr directly
521 +
		var err error
516 522
		if o.Resolve != "" {
517 -
			addr = fnet.Resolve(o.Resolve, bc.port)
523 +
			tAddr, err = fnet.Resolve(o.Resolve, bc.port)
518 524
		} else {
519 -
			addr = fnet.Resolve(bc.hostname, bc.port)
525 +
			tAddr, err = fnet.Resolve(bc.hostname, bc.port)
520 526
		}
521 -
	}
522 -
	if addr == nil {
523 -
		// Error already logged
524 -
		return nil
527 +
		if tAddr == nil {
528 +
			// Error already logged
529 +
			return nil, err
530 +
		}
531 +
		addr = tAddr
525 532
	}
526 533
	bc.dest = addr
527 534
	// Create the bytes for the request:
@@ -547,16 +554,16 @@
Loading
547 554
	bc.reqTimeout = o.HTTPReqTimeOut
548 555
	w := bufio.NewWriter(&buf)
549 556
	// This writes multiple valued headers properly (unlike calling Get() to do it ourselves)
550 -
	o.GenerateHeaders().Write(w) // nolint: errcheck,gas
551 -
	w.Flush()                    // nolint: errcheck,gas
557 +
	_ = o.GenerateHeaders().Write(w)
558 +
	w.Flush()
552 559
	buf.WriteString("\r\n")
553 -
	//Add the payload to http body
560 +
	// Add the payload to http body
554 561
	if payloadLen > 0 {
555 562
		buf.Write(o.Payload)
556 563
	}
557 564
	bc.req = buf.Bytes()
558 565
	log.Debugf("Created client:\n%+v\n%s", bc.dest, bc.req)
559 -
	return &bc
566 +
	return &bc, nil
560 567
}
561 568
562 569
// return the result from the state.
@@ -572,21 +579,7 @@
Loading
572 579
		log.Errf("Unable to connect to %v : %v", c.dest, err)
573 580
		return nil
574 581
	}
575 -
	tcpSock, ok := socket.(*net.TCPConn)
576 -
	if !ok {
577 -
		log.LogVf("Not setting socket options on non tcp socket %v", socket.RemoteAddr())
578 -
		return socket
579 -
	}
580 -
	// For now those errors are not critical/breaking
581 -
	if err = tcpSock.SetNoDelay(true); err != nil {
582 -
		log.Warnf("Unable to connect to set tcp no delay %v %v : %v", socket, c.dest, err)
583 -
	}
584 -
	if err = tcpSock.SetWriteBuffer(len(c.req)); err != nil {
585 -
		log.Warnf("Unable to connect to set write buffer %d %v %v : %v", len(c.req), socket, c.dest, err)
586 -
	}
587 -
	if err = tcpSock.SetReadBuffer(len(c.buffer)); err != nil {
588 -
		log.Warnf("Unable to connect to read buffer %d %v %v : %v", len(c.buffer), socket, c.dest, err)
589 -
	}
582 +
	fnet.SetSocketBuffers(socket, len(c.buffer), len(c.req))
590 583
	return socket
591 584
}
592 585
@@ -622,7 +615,7 @@
Loading
622 615
		if reuse {
623 616
			// it's ok for the (idle) socket to die once, auto reconnect:
624 617
			log.Infof("Closing dead socket %v (%v)", conn, err)
625 -
			conn.Close() // nolint: errcheck,gas
618 +
			conn.Close()
626 619
			c.errorCount++
627 620
			return c.Fetch() // recurse once
628 621
		}
@@ -633,7 +626,7 @@
Loading
633 626
		log.Errf("Short write to %v %v : %d instead of %d", conn, c.dest, n, len(c.req))
634 627
		return c.returnRes()
635 628
	}
636 -
	if !c.keepAlive && c.halfClose {
629 +
	if !c.keepAlive && c.halfClose { // nolint: nestif
637 630
		tcpConn, ok := conn.(*net.TCPConn)
638 631
		if ok {
639 632
			if err = tcpConn.CloseWrite(); err != nil {
@@ -655,8 +648,13 @@
Loading
655 648
	return c.returnRes()
656 649
}
657 650
651 +
func codeIsOK(code int) bool {
652 +
	// TODO: make this configurable
653 +
	return (code >= 200 && code <= 299) || code == http.StatusTeapot
654 +
}
655 +
658 656
// Response reading:
659 -
// TODO: refactor - unwiedly/ugly atm
657 +
// nolint: nestif,funlen,gocognit,gocyclo // TODO: refactor - unwiedly/ugly atm.
660 658
func (c *FastClient) readResponse(conn net.Conn, reusedSocket bool) {
661 659
	max := len(c.buffer)
662 660
	parsedHeaders := false
@@ -684,7 +682,7 @@
Loading
684 682
					c.code = RetryOnce // special "retry once" code
685 683
					return
686 684
				}
687 -
				if err == io.EOF && c.size != 0 {
685 +
				if errors.Is(err, io.EOF) && c.size != 0 {
688 686
					// handled below as possibly normal end of stream after we read something
689 687
					break
690 688
				}
@@ -703,9 +701,9 @@
Loading
703 701
		// at least parse the http retcode:
704 702
		if !parsedHeaders && c.parseHeaders && c.size >= retcodeOffset+3 {
705 703
			// even if the bytes are garbage we'll get a non 200 code (bytes are unsigned)
706 -
			c.code = ParseDecimal(c.buffer[retcodeOffset : retcodeOffset+3]) //TODO do that only once...
707 -
			// TODO handle 100 Continue
708 -
			if c.code != http.StatusOK {
704 +
			c.code = ParseDecimal(c.buffer[retcodeOffset : retcodeOffset+3]) // TODO do that only once...
705 +
			// TODO handle 100 Continue, make the "ok" codes configurable
706 +
			if !codeIsOK(c.code) {
709 707
				log.Warnf("Parsed non ok code %d (%v)", c.code, string(c.buffer[:retcodeOffset+3]))
710 708
				break
711 709
			}
@@ -832,7 +830,7 @@
Loading
832 830
		}
833 831
	} // end of big for loop
834 832
	// Figure out whether to keep or close the socket:
835 -
	if keepAlive && c.code == http.StatusOK {
833 +
	if keepAlive && codeIsOK(c.code) {
836 834
		c.socket = conn // keep the open socket
837 835
	} else {
838 836
		if err := conn.Close(); err != nil {

@@ -0,0 +1,95 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"flag"
8 +
	"fmt"
9 +
	"regexp"
10 +
	"sync/atomic"
11 +
	"unsafe"
12 +
)
13 +
14 +
// DynString creates a `Flag` that represents `string` which is safe to change dynamically at runtime.
15 +
func DynString(flagSet *flag.FlagSet, name string, value string, usage string) *DynStringValue {
16 +
	dynValue := &DynStringValue{ptr: unsafe.Pointer(&value)}
17 +
	flagSet.Var(dynValue, name, usage)
18 +
	return dynValue
19 +
}
20 +
21 +
// DynStringValue is a flag-related `time.Duration` value wrapper.
22 +
type DynStringValue struct {
23 +
	DynamicFlagValueTag
24 +
	ptr          unsafe.Pointer
25 +
	validator    func(string) error
26 +
	notifier     func(oldValue string, newValue string)
27 +
	syncNotifier bool
28 +
}
29 +
30 +
// Get retrieves the value in a thread-safe manner.
31 +
func (d *DynStringValue) Get() string {
32 +
	if d.ptr == nil {
33 +
		return ""
34 +
	}
35 +
	ptr := atomic.LoadPointer(&d.ptr)
36 +
	return *(*string)(ptr)
37 +
}
38 +
39 +
// Set updates the value from a string representation in a thread-safe manner.
40 +
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
41 +
// optional validator.
42 +
// If a notifier is set on the value, it will be invoked in a separate go-routine.
43 +
func (d *DynStringValue) Set(val string) error {
44 +
	if d.validator != nil {
45 +
		if err := d.validator(val); err != nil {
46 +
			return err
47 +
		}
48 +
	}
49 +
	oldPtr := atomic.SwapPointer(&d.ptr, unsafe.Pointer(&val))
50 +
	if d.notifier != nil {
51 +
		if d.syncNotifier {
52 +
			d.notifier(*(*string)(oldPtr), val)
53 +
		} else {
54 +
			go d.notifier(*(*string)(oldPtr), val)
55 +
		}
56 +
	}
57 +
	return nil
58 +
}
59 +
60 +
// WithValidator adds a function that checks values before they're set.
61 +
// Any error returned by the validator will lead to the value being rejected.
62 +
// Validators are executed on the same go-routine as the call to `Set`.
63 +
func (d *DynStringValue) WithValidator(validator func(string) error) *DynStringValue {
64 +
	d.validator = validator
65 +
	return d
66 +
}
67 +
68 +
// WithNotifier adds a function is called every time a new value is successfully set.
69 +
// Each notifier is executed in a new go-routine.
70 +
func (d *DynStringValue) WithNotifier(notifier func(oldValue string, newValue string)) *DynStringValue {
71 +
	d.notifier = notifier
72 +
	return d
73 +
}
74 +
75 +
// WithSyncNotifier adds a function is called synchronously every time a new value is successfully set.
76 +
func (d *DynStringValue) WithSyncNotifier(notifier func(oldValue string, newValue string)) *DynStringValue {
77 +
	d.notifier = notifier
78 +
	d.syncNotifier = true
79 +
	return d
80 +
}
81 +
82 +
// String represents the canonical representation of the type.
83 +
func (d *DynStringValue) String() string {
84 +
	return fmt.Sprintf("%v", d.Get())
85 +
}
86 +
87 +
// ValidateDynStringMatchesRegex returns a validator function that checks all flag's values against regex.
88 +
func ValidateDynStringMatchesRegex(matcher *regexp.Regexp) func(string) error {
89 +
	return func(value string) error {
90 +
		if !matcher.MatchString(value) {
91 +
			return fmt.Errorf("value %v must match regex %v", value, matcher)
92 +
		}
93 +
		return nil
94 +
	}
95 +
}

@@ -0,0 +1,76 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"flag"
8 +
	"fmt"
9 +
	"strings"
10 +
	"sync/atomic"
11 +
	"time"
12 +
)
13 +
14 +
// DynDuration creates a `Flag` that represents `time.Duration` which is safe to change dynamically at runtime.
15 +
func DynDuration(flagSet *flag.FlagSet, name string, value time.Duration, usage string) *DynDurationValue {
16 +
	dynValue := &DynDurationValue{ptr: (*int64)(&value)}
17 +
	flagSet.Var(dynValue, name, usage)
18 +
	return dynValue
19 +
}
20 +
21 +
// DynDurationValue is a flag-related `time.Duration` value wrapper.
22 +
type DynDurationValue struct {
23 +
	DynamicFlagValueTag
24 +
	ptr       *int64
25 +
	validator func(time.Duration) error
26 +
	notifier  func(oldValue time.Duration, newValue time.Duration)
27 +
}
28 +
29 +
// Get retrieves the value in a thread-safe manner.
30 +
func (d *DynDurationValue) Get() time.Duration {
31 +
	if d.ptr == nil {
32 +
		return (time.Duration)(0)
33 +
	}
34 +
	return (time.Duration)(atomic.LoadInt64(d.ptr))
35 +
}
36 +
37 +
// Set updates the value from a string representation in a thread-safe manner.
38 +
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
39 +
// optional validator.
40 +
// If a notifier is set on the value, it will be invoked in a separate go-routine.
41 +
func (d *DynDurationValue) Set(input string) error {
42 +
	v, err := time.ParseDuration(strings.TrimSpace(input))
43 +
	if err != nil {
44 +
		return err
45 +
	}
46 +
	if d.validator != nil {
47 +
		if err := d.validator(v); err != nil {
48 +
			return err
49 +
		}
50 +
	}
51 +
	oldPtr := atomic.SwapInt64(d.ptr, (int64)(v))
52 +
	if d.notifier != nil {
53 +
		go d.notifier((time.Duration)(oldPtr), v)
54 +
	}
55 +
	return nil
56 +
}
57 +
58 +
// WithValidator adds a function that checks values before they're set.
59 +
// Any error returned by the validator will lead to the value being rejected.
60 +
// Validators are executed on the same go-routine as the call to `Set`.
61 +
func (d *DynDurationValue) WithValidator(validator func(time.Duration) error) *DynDurationValue {
62 +
	d.validator = validator
63 +
	return d
64 +
}
65 +
66 +
// WithNotifier adds a function is called every time a new value is successfully set.
67 +
// Each notifier is executed in a new go-routine.
68 +
func (d *DynDurationValue) WithNotifier(notifier func(oldValue time.Duration, newValue time.Duration)) *DynDurationValue {
69 +
	d.notifier = notifier
70 +
	return d
71 +
}
72 +
73 +
// String represents the canonical representation of the type.
74 +
func (d *DynDurationValue) String() string {
75 +
	return fmt.Sprintf("%v", d.Get())
76 +
}

@@ -0,0 +1,123 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"encoding/json"
8 +
	"flag"
9 +
	"reflect"
10 +
	"sync/atomic"
11 +
	"unsafe"
12 +
)
13 +
14 +
// DynJSON creates a `Flag` that is backed by an arbitrary JSON which is safe to change dynamically at runtime.
15 +
// The `value` must be a pointer to a struct that is JSON (un)marshallable.
16 +
// New values based on the default constructor of `value` type will be created on each update.
17 +
func DynJSON(flagSet *flag.FlagSet, name string, value interface{}, usage string) *DynJSONValue {
18 +
	reflectVal := reflect.ValueOf(value)
19 +
	if reflectVal.Kind() != reflect.Ptr || reflectVal.Elem().Kind() != reflect.Struct {
20 +
		panic("DynJSON value must be a pointer to a struct")
21 +
	}
22 +
	dynValue := &DynJSONValue{
23 +
		ptr:        unsafe.Pointer(reflectVal.Pointer()),
24 +
		structType: reflectVal.Type().Elem(),
25 +
		flagSet:    flagSet,
26 +
		flagName:   name,
27 +
	}
28 +
	flagSet.Var(dynValue, name, usage)
29 +
	flagSet.Lookup(name).DefValue = dynValue.usageString()
30 +
	return dynValue
31 +
}
32 +
33 +
// DynJSONValue is a flag-related JSON struct value wrapper.
34 +
type DynJSONValue struct {
35 +
	DynamicFlagValueTag
36 +
	structType reflect.Type
37 +
	ptr        unsafe.Pointer
38 +
	validator  func(interface{}) error
39 +
	notifier   func(oldValue interface{}, newValue interface{})
40 +
	flagName   string
41 +
	flagSet    *flag.FlagSet
42 +
}
43 +
44 +
// IsJSON always return true (method is present for the DynamicJSONFlagValue interface tagging).
45 +
func (d *DynJSONValue) IsJSON() bool {
46 +
	return true
47 +
}
48 +
49 +
// Get retrieves the value in its original JSON struct type in a thread-safe manner.
50 +
func (d *DynJSONValue) Get() interface{} {
51 +
	if d.ptr == nil {
52 +
		return ""
53 +
	}
54 +
	return d.unsafeToStoredType(atomic.LoadPointer(&d.ptr))
55 +
}
56 +
57 +
// Set updates the value from a string representation in a thread-safe manner.
58 +
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
59 +
// optional validator.
60 +
// If a notifier is set on the value, it will be invoked in a separate go-routine.
61 +
func (d *DynJSONValue) Set(input string) error {
62 +
	someStruct := reflect.New(d.structType).Interface()
63 +
	if err := json.Unmarshal([]byte(input), someStruct); err != nil {
64 +
		return err
65 +
	}
66 +
	if d.validator != nil {
67 +
		if err := d.validator(someStruct); err != nil {
68 +
			return err
69 +
		}
70 +
	}
71 +
	oldPtr := atomic.SwapPointer(&d.ptr, unsafe.Pointer(reflect.ValueOf(someStruct).Pointer()))
72 +
	if d.notifier != nil {
73 +
		go d.notifier(d.unsafeToStoredType(oldPtr), someStruct)
74 +
	}
75 +
	return nil
76 +
}
77 +
78 +
// WithValidator adds a function that checks values before they're set.
79 +
// Any error returned by the validator will lead to the value being rejected.
80 +
// Validators are executed on the same go-routine as the call to `Set`.
81 +
func (d *DynJSONValue) WithValidator(validator func(interface{}) error) *DynJSONValue {
82 +
	d.validator = validator
83 +
	return d
84 +
}
85 +
86 +
// WithNotifier adds a function is called every time a new value is successfully set.
87 +
// Each notifier is executed in a new go-routine.
88 +
func (d *DynJSONValue) WithNotifier(notifier func(oldValue interface{}, newValue interface{})) *DynJSONValue {
89 +
	d.notifier = notifier
90 +
	return d
91 +
}
92 +
93 +
// WithFileFlag adds an companion <name>_path flag that allows this value to be read from a file with dflag.ReadFileFlags.
94 +
//
95 +
// This is useful for reading large JSON files as flags. If the companion flag's value (whether default or overwritten)
96 +
// is set to empty string, nothing is read.
97 +
//
98 +
// Flag value reads are subject to notifiers and validators.
99 +
func (d *DynJSONValue) WithFileFlag(defaultPath string) (*DynJSONValue, *FileReadValue) {
100 +
	return d, FileReadFlag(d.flagSet, d.flagName, defaultPath)
101 +
}
102 +
103 +
// String returns the canonical string representation of the type.
104 +
func (d *DynJSONValue) String() string {
105 +
	out, err := json.Marshal(d.Get())
106 +
	if err != nil {
107 +
		return "ERR"
108 +
	}
109 +
	return string(out)
110 +
}
111 +
112 +
func (d *DynJSONValue) usageString() string {
113 +
	s := d.String()
114 +
	if len(s) > 128 {
115 +
		return "{ ... truncated ... }"
116 +
	}
117 +
	return s
118 +
}
119 +
120 +
func (d *DynJSONValue) unsafeToStoredType(p unsafe.Pointer) interface{} {
121 +
	n := reflect.NewAt(d.structType, p)
122 +
	return n.Interface()
123 +
}

@@ -0,0 +1,112 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"encoding/csv"
8 +
	"flag"
9 +
	"fmt"
10 +
	"strings"
11 +
	"sync/atomic"
12 +
	"unsafe"
13 +
)
14 +
15 +
// DynStringSet creates a `Flag` that represents `map[string]struct{}` which is safe to change dynamically at runtime.
16 +
// Unlike `pflag.StringSlice`, consecutive sets don't append to the slice, but override it.
17 +
func DynStringSet(flagSet *flag.FlagSet, name string, value []string, usage string) *DynStringSetValue {
18 +
	set := buildStringSet(value)
19 +
	dynValue := &DynStringSetValue{ptr: unsafe.Pointer(&set)}
20 +
	flagSet.Var(dynValue, name, usage)
21 +
	return dynValue
22 +
}
23 +
24 +
// DynStringSetValue is a flag-related `map[string]struct{}` value wrapper.
25 +
type DynStringSetValue struct {
26 +
	DynamicFlagValueTag
27 +
	ptr       unsafe.Pointer
28 +
	validator func(map[string]struct{}) error
29 +
	notifier  func(oldValue map[string]struct{}, newValue map[string]struct{})
30 +
}
31 +
32 +
// Get retrieves the value in a thread-safe manner.
33 +
func (d *DynStringSetValue) Get() map[string]struct{} {
34 +
	if d.ptr == nil {
35 +
		return make(map[string]struct{})
36 +
	}
37 +
	p := (*map[string]struct{})(atomic.LoadPointer(&d.ptr))
38 +
	return *p
39 +
}
40 +
41 +
// Set updates the value from a string representation in a thread-safe manner.
42 +
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
43 +
// optional validator.
44 +
// If a notifier is set on the value, it will be invoked in a separate go-routine.
45 +
func (d *DynStringSetValue) Set(val string) error {
46 +
	v, err := csv.NewReader(strings.NewReader(val)).Read()
47 +
	if err != nil {
48 +
		return err
49 +
	}
50 +
	s := buildStringSet(v)
51 +
	if d.validator != nil {
52 +
		if err := d.validator(s); err != nil {
53 +
			return err
54 +
		}
55 +
	}
56 +
	oldPtr := atomic.SwapPointer(&d.ptr, unsafe.Pointer(&s))
57 +
	if d.notifier != nil {
58 +
		go d.notifier(*(*map[string]struct{})(oldPtr), s)
59 +
	}
60 +
	return nil
61 +
}
62 +
63 +
// Contains returns whether the specified string is in the flag.
64 +
func (d *DynStringSetValue) Contains(val string) bool {
65 +
	v := d.Get()
66 +
	_, ok := v[val]
67 +
	return ok
68 +
}
69 +
70 +
// WithValidator adds a function that checks values before they're set.
71 +
// Any error returned by the validator will lead to the value being rejected.
72 +
// Validators are executed on the same go-routine as the call to `Set`.
73 +
func (d *DynStringSetValue) WithValidator(validator func(map[string]struct{}) error) *DynStringSetValue {
74 +
	d.validator = validator
75 +
	return d
76 +
}
77 +
78 +
// WithNotifier adds a function that is called every time a new value is successfully set.
79 +
// Each notifier is executed asynchronously in a new go-routine.
80 +
func (d *DynStringSetValue) WithNotifier(notifier func(oldValue map[string]struct{},
81 +
	newValue map[string]struct{})) *DynStringSetValue {
82 +
	d.notifier = notifier
83 +
	return d
84 +
}
85 +
86 +
// String represents the canonical representation of the type.
87 +
func (d *DynStringSetValue) String() string {
88 +
	v := d.Get()
89 +
	arr := make([]string, 0, len(v))
90 +
	for k := range v {
91 +
		arr = append(arr, k)
92 +
	}
93 +
	return fmt.Sprintf("%v", arr)
94 +
}
95 +
96 +
// ValidateDynStringSetMinElements validates that the given string slice has at least x elements.
97 +
func ValidateDynStringSetMinElements(count int) func(map[string]struct{}) error {
98 +
	return func(value map[string]struct{}) error {
99 +
		if len(value) < count {
100 +
			return fmt.Errorf("value slice %v must have at least %v elements", value, count)
101 +
		}
102 +
		return nil
103 +
	}
104 +
}
105 +
106 +
func buildStringSet(items []string) map[string]struct{} {
107 +
	res := map[string]struct{}{}
108 +
	for _, item := range items {
109 +
		res[item] = struct{}{}
110 +
	}
111 +
	return res
112 +
}

@@ -0,0 +1,88 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"flag"
8 +
	"fmt"
9 +
	"strconv"
10 +
	"strings"
11 +
	"sync/atomic"
12 +
	"unsafe"
13 +
)
14 +
15 +
// DynFloat64 creates a `Flag` that represents `float64` which is safe to change dynamically at runtime.
16 +
func DynFloat64(flagSet *flag.FlagSet, name string, value float64, usage string) *DynFloat64Value {
17 +
	dynValue := &DynFloat64Value{ptr: unsafe.Pointer(&value)}
18 +
	flagSet.Var(dynValue, name, usage)
19 +
	return dynValue
20 +
}
21 +
22 +
// DynFloat64Value is a flag-related `float64` value wrapper.
23 +
type DynFloat64Value struct {
24 +
	DynamicFlagValueTag
25 +
	ptr       unsafe.Pointer
26 +
	validator func(float64) error
27 +
	notifier  func(oldValue float64, newValue float64)
28 +
}
29 +
30 +
// Get retrieves the value in a thread-safe manner.
31 +
func (d *DynFloat64Value) Get() float64 {
32 +
	if d.ptr == nil {
33 +
		return 0.0
34 +
	}
35 +
	p := (*float64)(atomic.LoadPointer(&d.ptr))
36 +
	return *p
37 +
}
38 +
39 +
// Set updates the value from a string representation in a thread-safe manner.
40 +
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
41 +
// optional validator.
42 +
// If a notifier is set on the value, it will be invoked in a separate go-routine.
43 +
func (d *DynFloat64Value) Set(input string) error {
44 +
	val, err := strconv.ParseFloat(strings.TrimSpace(input), 64)
45 +
	if err != nil {
46 +
		return err
47 +
	}
48 +
	if d.validator != nil {
49 +
		if err := d.validator(val); err != nil {
50 +
			return err
51 +
		}
52 +
	}
53 +
	oldPtr := atomic.SwapPointer(&d.ptr, unsafe.Pointer(&val))
54 +
	if d.notifier != nil {
55 +
		go d.notifier(*(*float64)(oldPtr), val)
56 +
	}
57 +
	return nil
58 +
}
59 +
60 +
// WithValidator adds a function that checks values before they're set.
61 +
// Any error returned by the validator will lead to the value being rejected.
62 +
// Validators are executed on the same go-routine as the call to `Set`.
63 +
func (d *DynFloat64Value) WithValidator(validator func(float64) error) *DynFloat64Value {
64 +
	d.validator = validator
65 +
	return d
66 +
}
67 +
68 +
// WithNotifier adds a function is called every time a new value is successfully set.
69 +
// Each notifier is executed in a new go-routine.
70 +
func (d *DynFloat64Value) WithNotifier(notifier func(oldValue float64, newValue float64)) *DynFloat64Value {
71 +
	d.notifier = notifier
72 +
	return d
73 +
}
74 +
75 +
// String returns the canonical string representation of the type.
76 +
func (d *DynFloat64Value) String() string {
77 +
	return fmt.Sprintf("%v", d.Get())
78 +
}
79 +
80 +
// ValidateDynFloat64Range returns a validator that checks if the float value is in range.
81 +
func ValidateDynFloat64Range(fromInclusive float64, toInclusive float64) func(float64) error {
82 +
	return func(value float64) error {
83 +
		if value > toInclusive || value < fromInclusive {
84 +
			return fmt.Errorf("value %v not in [%v, %v] range", value, fromInclusive, toInclusive)
85 +
		}
86 +
		return nil
87 +
	}
88 +
}

@@ -21,17 +21,15 @@
Loading
21 21
	"os"
22 22
	"runtime"
23 23
	"runtime/pprof"
24 -
	"time"
25 -
26 -
	"google.golang.org/grpc"
27 -
	"google.golang.org/grpc/credentials"
28 -
	"google.golang.org/grpc/health/grpc_health_v1"
29 -
30 24
	"strings"
25 +
	"time"
31 26
32 27
	"fortio.org/fortio/fnet"
33 28
	"fortio.org/fortio/log"
34 29
	"fortio.org/fortio/periodic"
30 +
	"google.golang.org/grpc"
31 +
	"google.golang.org/grpc/credentials"
32 +
	"google.golang.org/grpc/health/grpc_health_v1"
35 33
)
36 34
37 35
// Dial dials grpc using insecure or tls transport security when serverAddr
@@ -69,7 +67,7 @@
Loading
69 67
	return conn, err
70 68
}
71 69
72 -
// TODO: refactor common parts between http and grpc runners
70 +
// TODO: refactor common parts between http and grpc runners.
73 71
74 72
// GRPCRunnerResults is the aggregated result of an GRPCRunner.
75 73
// Also is the internal type used per thread/goroutine.
@@ -129,6 +127,7 @@
Loading
129 127
}
130 128
131 129
// RunGRPCTest runs an http test and returns the aggregated stats.
130 +
// nolint: funlen, gocognit
132 131
func RunGRPCTest(o *GRPCRunnerOptions) (*GRPCRunnerResults, error) {
133 132
	if o.Streams < 1 {
134 133
		o.Streams = 1
@@ -178,7 +177,7 @@
Loading
178 177
		}
179 178
		grpcstate[i].Ping = o.UsePing
180 179
		var err error
181 -
		if o.UsePing {
180 +
		if o.UsePing { // nolint: nestif
182 181
			grpcstate[i].clientP = NewPingServerClient(conn)
183 182
			if grpcstate[i].clientP == nil {
184 183
				return nil, fmt.Errorf("unable to create ping client %d for %s", i, o.Destination)
@@ -211,7 +210,9 @@
Loading
211 210
			log.Critf("Unable to create .cpu profile: %v", err)
212 211
			return nil, err
213 212
		}
214 -
		pprof.StartCPUProfile(fc) //nolint: gas,errcheck
213 +
		if err = pprof.StartCPUProfile(fc); err != nil {
214 +
			log.Critf("Unable to start cpu profile: %v", err)
215 +
		}
215 216
	}
216 217
	total.RunnerResults = r.Run()
217 218
	if o.Profiler != "" {
@@ -221,9 +222,11 @@
Loading
221 222
			log.Critf("Unable to create .mem profile: %v", err)
222 223
			return nil, err
223 224
		}
224 -
		runtime.GC()               // get up-to-date statistics
225 -
		pprof.WriteHeapProfile(fm) // nolint:gas,errcheck
226 -
		fm.Close()                 // nolint:gas,errcheck
225 +
		runtime.GC() // get up-to-date statistics
226 +
		if err = pprof.WriteHeapProfile(fm); err != nil {
227 +
			log.Critf("Unable to write heap profile: %v", err)
228 +
		}
229 +
		fm.Close()
227 230
		fmt.Printf("Wrote profile data to %s.{cpu|mem}\n", o.Profiler)
228 231
	}
229 232
	// Numthreads may have reduced
@@ -258,7 +261,7 @@
Loading
258 261
// prefix is removed from dest if one exists and the port number is set to
259 262
// StandardHTTPPort for http, StandardHTTPSPort for https, or DefaultGRPCPort
260 263
// if http, https, or :port is not specified in dest.
261 -
// TODO: change/fix this (NormalizePort and more)
264 +
// TODO: change/fix this (NormalizePort and more).
262 265
func grpcDestination(dest string) (parsedDest string) {
263 266
	var port string
264 267
	// strip any unintentional http/https scheme prefixes from dest

@@ -0,0 +1,86 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"flag"
8 +
	"fmt"
9 +
	"strconv"
10 +
	"strings"
11 +
	"sync/atomic"
12 +
)
13 +
14 +
// DynInt64 creates a `Flag` that represents `int64` which is safe to change dynamically at runtime.
15 +
func DynInt64(flagSet *flag.FlagSet, name string, value int64, usage string) *DynInt64Value {
16 +
	dynValue := &DynInt64Value{ptr: &value}
17 +
	flagSet.Var(dynValue, name, usage)
18 +
	return dynValue
19 +
}
20 +
21 +
// DynInt64Value is a flag-related `int64` value wrapper.
22 +
type DynInt64Value struct {
23 +
	DynamicFlagValueTag
24 +
	ptr       *int64
25 +
	validator func(int64) error
26 +
	notifier  func(oldValue int64, newValue int64)
27 +
}
28 +
29 +
// Get retrieves the value in a thread-safe manner.
30 +
func (d *DynInt64Value) Get() int64 {
31 +
	if d.ptr == nil {
32 +
		return 0
33 +
	}
34 +
	return atomic.LoadInt64(d.ptr)
35 +
}
36 +
37 +
// Set updates the value from a string representation in a thread-safe manner.
38 +
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
39 +
// optional validator.
40 +
// If a notifier is set on the value, it will be invoked in a separate go-routine.
41 +
func (d *DynInt64Value) Set(input string) error {
42 +
	val, err := strconv.ParseInt(strings.TrimSpace(input), 0, 64)
43 +
	if err != nil {
44 +
		return err
45 +
	}
46 +
	if d.validator != nil {
47 +
		if err := d.validator(val); err != nil {
48 +
			return err
49 +
		}
50 +
	}
51 +
	oldVal := atomic.SwapInt64(d.ptr, val)
52 +
	if d.notifier != nil {
53 +
		go d.notifier(oldVal, val)
54 +
	}
55 +
	return nil
56 +
}
57 +
58 +
// WithValidator adds a function that checks values before they're set.
59 +
// Any error returned by the validator will lead to the value being rejected.
60 +
// Validators are executed on the same go-routine as the call to `Set`.
61 +
func (d *DynInt64Value) WithValidator(validator func(int64) error) *DynInt64Value {
62 +
	d.validator = validator
63 +
	return d
64 +
}
65 +
66 +
// WithNotifier adds a function is called every time a new value is successfully set.
67 +
// Each notifier is executed in a new go-routine.
68 +
func (d *DynInt64Value) WithNotifier(notifier func(oldValue int64, newValue int64)) *DynInt64Value {
69 +
	d.notifier = notifier
70 +
	return d
71 +
}
72 +
73 +
// String returns the canonical string representation of the type.
74 +
func (d *DynInt64Value) String() string {
75 +
	return fmt.Sprintf("%v", d.Get())
76 +
}
77 +
78 +
// ValidateDynInt64Range returns a validator function that checks if the integer value is in range.
79 +
func ValidateDynInt64Range(fromInclusive int64, toInclusive int64) func(int64) error {
80 +
	return func(value int64) error {
81 +
		if value > toInclusive || value < fromInclusive {
82 +
			return fmt.Errorf("value %v not in [%v, %v] range", value, fromInclusive, toInclusive)
83 +
		}
84 +
		return nil
85 +
	}
86 +
}

@@ -0,0 +1,89 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package dflag
5 +
6 +
import (
7 +
	"encoding/csv"
8 +
	"flag"
9 +
	"fmt"
10 +
	"strings"
11 +
	"sync/atomic"
12 +
	"unsafe"
13 +
)
14 +
15 +
// DynStringSlice creates a `Flag` that represents `[]string` which is safe to change dynamically at runtime.
16 +
// Unlike `pflag.StringSlice`, consecutive sets don't append to the slice, but override it.
17 +
func DynStringSlice(flagSet *flag.FlagSet, name string, value []string, usage string) *DynStringSliceValue {
18 +
	dynValue := &DynStringSliceValue{ptr: unsafe.Pointer(&value)}
19 +
	flagSet.Var(dynValue, name, usage)
20 +
	return dynValue
21 +
}
22 +
23 +
// DynStringSliceValue is a flag-related `time.Duration` value wrapper.
24 +
type DynStringSliceValue struct {
25 +
	DynamicFlagValueTag
26 +
	ptr       unsafe.Pointer
27 +
	validator func([]string) error
28 +
	notifier  func(oldValue []string, newValue []string)
29 +
}
30 +
31 +
// Get retrieves the value in a thread-safe manner.
32 +
func (d *DynStringSliceValue) Get() []string {
33 +
	if d.ptr == nil {
34 +
		return []string{}
35 +
	}
36 +
	p := (*[]string)(atomic.LoadPointer(&d.ptr))
37 +
	return *p
38 +
}
39 +
40 +
// Set updates the value from a string representation in a thread-safe manner.
41 +
// This operation may return an error if the provided `input` doesn't parse, or the resulting value doesn't pass an
42 +
// optional validator.
43 +
// If a notifier is set on the value, it will be invoked in a separate go-routine.
44 +
func (d *DynStringSliceValue) Set(val string) error {
45 +
	v, err := csv.NewReader(strings.NewReader(val)).Read()
46 +
	if err != nil {
47 +
		return err
48 +
	}
49 +
	if d.validator != nil {
50 +
		if err := d.validator(v); err != nil {
51 +
			return err
52 +
		}
53 +
	}
54 +
	oldPtr := atomic.SwapPointer(&d.ptr, unsafe.Pointer(&v))
55 +
	if d.notifier != nil {
56 +
		go d.notifier(*(*[]string)(oldPtr), v)
57 +
	}
58 +
	return nil
59 +
}
60 +
61 +
// WithValidator adds a function that checks values before they're set.
62 +
// Any error returned by the validator will lead to the value being rejected.
63 +
// Validators are executed on the same go-routine as the call to `Set`.
64 +
func (d *DynStringSliceValue) WithValidator(validator func([]string) error) *DynStringSliceValue {
65 +
	d.validator = validator
66 +
	return d
67 +
}
68 +
69 +
// WithNotifier adds a function that is called every time a new value is successfully set.
70 +
// Each notifier is executed asynchronously in a new go-routine.
71 +
func (d *DynStringSliceValue) WithNotifier(notifier func(oldValue []string, newValue []string)) *DynStringSliceValue {
72 +
	d.notifier = notifier
73 +
	return d
74 +
}
75 +
76 +
// String represents the canonical representation of the type.
77 +
func (d *DynStringSliceValue) String() string {
78 +
	return fmt.Sprintf("%v", d.Get())
79 +
}
80 +
81 +
// ValidateDynStringSliceMinElements validates that the given string slice has at least x elements.
82 +
func ValidateDynStringSliceMinElements(count int) func([]string) error {
83 +
	return func(value []string) error {
84 +
		if len(value) < count {
85 +
			return fmt.Errorf("value slice %v must have at least %v elements", value, count)
86 +
		}
87 +
		return nil
88 +
	}
89 +
}

@@ -0,0 +1,222 @@
Loading
1 +
// Copyright 2015 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
package endpoint
5 +
6 +
import (
7 +
	"bytes"
8 +
	"encoding/json"
9 +
	"flag"
10 +
	"fmt"
11 +
	"html/template"
12 +
	"net/http"
13 +
	"strings"
14 +
15 +
	"fortio.org/fortio/dflag"
16 +
	"fortio.org/fortio/fhttp"
17 +
	"fortio.org/fortio/log"
18 +
)
19 +
20 +
// FlagsEndpoint is a collection of `http.HandlerFunc` that serve debug pages about a given `FlagSet.
21 +
type FlagsEndpoint struct {
22 +
	flagSet *flag.FlagSet
23 +
	setURL  string
24 +
}
25 +
26 +
// NewFlagsEndpoint creates a new debug `http.HandlerFunc` collection for a given `FlagSet`
27 +
// and an optional URL for Setter (needs to be secured). if setURL is empty, no setter function
28 +
// will be enabled.
29 +
func NewFlagsEndpoint(flagSet *flag.FlagSet, setURL string) *FlagsEndpoint {
30 +
	return &FlagsEndpoint{flagSet: flagSet, setURL: setURL}
31 +
}
32 +
33 +
// HTTPErrf logs and returns an error on the response.
34 +
func HTTPErrf(resp http.ResponseWriter, statusCode int, message string, rest ...interface{}) {
35 +
	resp.WriteHeader(statusCode)
36 +
	resp.Header().Set("Content-Type", "text/plain; charset=UTF-8")
37 +
	log.Errf(message, rest...)
38 +
	_, _ = resp.Write([]byte(fmt.Sprintf(message, rest...)))
39 +
}
40 +
41 +
// SetFlag updates a dynamic flag to a new value.
42 +
func (e *FlagsEndpoint) SetFlag(resp http.ResponseWriter, req *http.Request) {
43 +
	fhttp.LogRequest(req, "SetFlag")
44 +
	if e.setURL == "" {
45 +
		HTTPErrf(resp, http.StatusForbidden, "setting flags is not enabled")
46 +
		return
47 +
	}
48 +
	name := req.URL.Query().Get("name")
49 +
	value := req.URL.Query().Get("value")
50 +
	f := e.flagSet.Lookup(name)
51 +
	if f == nil {
52 +
		HTTPErrf(resp, http.StatusForbidden, "Flag %q not found", name)
53 +
		return
54 +
	}
55 +
	if !dflag.IsFlagDynamic(f) {
56 +
		HTTPErrf(resp, http.StatusBadRequest, "Trying to set non dynamic flag %q", name)
57 +
		return
58 +
	}
59 +
	if err := e.flagSet.Set(name, value); err != nil {
60 +
		HTTPErrf(resp, http.StatusNotAcceptable, "Error setting %q to %q: %v", name, value, err)
61 +
		return
62 +
	}
63 +
	resp.Header().Set("Content-Type", "text/plain; charset=UTF-8")
64 +
	_, _ = resp.Write([]byte(fmt.Sprintf("Success %q -> %q", name, value)))
65 +
}
66 +
67 +
// ListFlags provides an HTML and JSON `http.HandlerFunc` that lists all Flags of a `FlagSet`.
68 +
// Additional URL query parameters can be used such as `type=[dynamic,static]` or `only_changed=true`.
69 +
func (e *FlagsEndpoint) ListFlags(resp http.ResponseWriter, req *http.Request) {
70 +
	fhttp.LogRequest(req, "ListFlags")
71 +
72 +
	onlyChanged := req.URL.Query().Get("only_changed") != ""
73 +
	onlyDynamic := req.URL.Query().Get("type") == "dynamic"
74 +
	onlyStatic := req.URL.Query().Get("type") == "static"
75 +
76 +
	flagSetJSON := &flagSetJSON{}
77 +
	e.flagSet.VisitAll(func(f *flag.Flag) {
78 +
		if onlyChanged && f.Value.String() == f.DefValue { // not exactly the same as "changed" (!)
79 +
			return
80 +
		}
81 +
		if onlyDynamic && !dflag.IsFlagDynamic(f) {
82 +
			return
83 +
		}
84 +
		if onlyStatic && dflag.IsFlagDynamic(f) {
85 +
			return
86 +
		}
87 +
		flagSetJSON.Flags = append(flagSetJSON.Flags, flagToJSON(f))
88 +
	})
89 +
	flagSetJSON.ChecksumDynamic = fmt.Sprintf("%x", dflag.ChecksumFlagSet(e.flagSet, dflag.IsFlagDynamic))
90 +
	flagSetJSON.ChecksumStatic = fmt.Sprintf("%x", dflag.ChecksumFlagSet(e.flagSet,
91 +
		func(f *flag.Flag) bool { return !dflag.IsFlagDynamic(f) }))
92 +
	flagSetJSON.FlagSetURL = e.setURL
93 +
94 +
	if requestIsBrowser(req) && req.URL.Query().Get("format") != "json" {
95 +
		resp.WriteHeader(http.StatusOK)
96 +
		resp.Header().Add("Content-Type", "text/html")
97 +
		if err := dflagListTemplate.Execute(resp, flagSetJSON); err != nil {
98 +
			log.Fatalf("Bad template evaluation: %v", err)
99 +
		}
100 +
	} else {
101 +
		resp.Header().Add("Content-Type", "application/json")
102 +
		out, err := json.MarshalIndent(&flagSetJSON, "", "  ")
103 +
		if err != nil {
104 +
			resp.WriteHeader(http.StatusInternalServerError)
105 +
			return
106 +
		}
107 +
		resp.WriteHeader(http.StatusOK)
108 +
		_, _ = resp.Write(out)
109 +
	}
110 +
}
111 +
112 +
func requestIsBrowser(req *http.Request) bool {
113 +
	return strings.Contains(req.Header.Get("Accept"), "html")
114 +
}
115 +
116 +
// nolint: lll
117 +
var dflagListTemplate = template.Must(template.New("dflag_list").Parse(
118 +
	`
119 +
<html><head>
120 +
<title>Flags List</title>
121 +
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.css" rel="stylesheet">
122 +
123 +
</head>
124 +
<body>
125 +
<div class="container-fluid">
126 +
<div class="col-md-10 col-md-offset-1">
127 +
	<h1>Flags Debug View</h1>
128 +
	<p>
129 +
	This page presents the configuration flags of this server (<a href="?format=json">JSON</a>).
130 +
	</p>
131 +
	<p>
132 +
	You can easily filter only <a href="?only_changed=true"><span class="label label-primary">changed</span> flag</a> or filter flags by type:
133 +
	</p>
134 +
	<ul>
135 +
	  <li><a href="?type=dynamic"><span class="label label-success">dynamic</span></a> - flags tweakable dynamically - checksum <code>{{ .ChecksumDynamic }}</code></li>
136 +
	  <li><a href="?type=static"><span class="label label-default">static</span></a> - initialization-time only flags - checksum <code>{{ .ChecksumStatic }}</code></li>
137 +
	</ul>
138 +
139 +
140 +
141 +
	{{range $flag := .Flags }}
142 +
		<div class="panel panel-default">
143 +
          <div class="panel-heading">
144 +
            <code>{{ $flag.Name }}</code>
145 +
            {{ if $flag.IsChanged }}<span class="label label-primary">changed</span>{{ end }}
146 +
            {{ if $flag.IsDynamic }}
147 +
                <span class="label label-success">dynamic</span>
148 +
            {{ else }}
149 +
                <span class="label label-default">static</span>
150 +
            {{ end }}
151 +
152 +
          </div>
153 +
		  <div class="panel-body">
154 +
		    <dl class="dl-horizontal" style="margin-bottom: 0px">
155 +
			  <dt>Description</dt>
156 +
			  <dd><small>{{ $flag.Description }}</small></dd>
157 +
			  <dt>Default</dt>
158 +
			  <dd><pre style="font-size: 8pt">{{ $flag.DefaultValue }}</pre></dd>
159 +
			  <dt>Current</dt>
160 +
			  {{ if and $flag.IsDynamic (ne $.FlagSetURL "") }}
161 +
			  <form action="{{ $.FlagSetURL }}">
162 +
			  <input type="hidden" name="name" value="{{ $flag.Name }}" />
163 +
				  {{ if $flag.IsJSON }}
164 +
					  <dd><pre class="success" style="font-size: 8pt"><textarea name="value">{{ $flag.CurrentValue }}</textarea></pre><input type="submit" value="Update"/></dd>
165 +
				  {{ else }}
166 +
					  <dd><pre class="success" style="font-size: 8pt"><input type="text" name="value" value="{{ $flag.CurrentValue }}" /></pre></dd>
167 +
				  {{ end }}
168 +
			  </form>
169 +
			  {{ else }}
170 +
			  <dd><pre class="success" style="font-size: 8pt">{{ $flag.CurrentValue }}</pre></dd>
171 +
			  {{ end }}
172 +
		    </dl>
173 +
		  </div>
174 +
		</div>
175 +
	{{end}}
176 +
</div></div>
177 +
</body>
178 +
</html>
179 +
`))
180 +
181 +
type flagSetJSON struct {
182 +
	ChecksumStatic  string      `json:"checksum_static"`
183 +
	ChecksumDynamic string      `json:"checksum_dynamic"`
184 +
	FlagSetURL      string      `json:"set_url"`
185 +
	Flags           []*flagJSON `json:"flags"`
186 +
}
187 +
188 +
type flagJSON struct {
189 +
	Name         string `json:"name"`
190 +
	Description  string `json:"description"`
191 +
	CurrentValue string `json:"current_value"`
192 +
	DefaultValue string `json:"default_value"`
193 +
194 +
	IsChanged bool `json:"is_changed"`
195 +
	IsDynamic bool `json:"is_dynamic"`
196 +
	IsJSON    bool `json:"is_json"`
197 +
}
198 +
199 +
func flagToJSON(f *flag.Flag) *flagJSON {
200 +
	fj := &flagJSON{
201 +
		Name:         f.Name,
202 +
		Description:  f.Usage,
203 +
		CurrentValue: f.Value.String(),
204 +
		DefaultValue: f.DefValue,
205 +
		IsChanged:    f.Value.String() != f.DefValue,
206 +
		IsDynamic:    dflag.IsFlagDynamic(f),
207 +
	}
208 +
	if dj, ok := f.Value.(dflag.DynamicJSONFlagValue); ok {
209 +
		fj.IsJSON = dj.IsJSON() // could assert true
210 +
		fj.CurrentValue = prettyPrintJSON(fj.CurrentValue)
211 +
		fj.DefaultValue = prettyPrintJSON(fj.DefaultValue)
212 +
	}
213 +
	return fj
214 +
}
215 +
216 +
func prettyPrintJSON(input string) string {
217 +
	out := &bytes.Buffer{}
218 +
	if err := json.Indent(out, []byte(input), "", "  "); err != nil {
219 +
		return "PRETTY_ERROR"
220 +
	}
221 +
	return out.String()
222 +
}

@@ -0,0 +1,195 @@
Loading
1 +
// Copyright 2016 Michal Witkowski. All Rights Reserved.
2 +
// See LICENSE for licensing terms.
3 +
4 +
// Package kubernetes provides an a K8S ConfigMap watcher for the jobs systems.
5 +
6 +
package configmap
7 +
8 +
import (
9 +
	"errors"
10 +
	"flag"
11 +
	"fmt"
12 +
	"io/ioutil"
13 +
	"path"
14 +
	"strings"
15 +
16 +
	"fortio.org/fortio/dflag"
17 +
	"fortio.org/fortio/log"
18 +
	"github.com/fsnotify/fsnotify"
19 +
)
20 +
21 +
const (
22 +
	k8sInternalsPrefix = ".."
23 +
	k8sDataSymlink     = "..data"
24 +
)
25 +
26 +
var (
27 +
	errFlagNotDynamic = fmt.Errorf("flag is not dynamic")
28 +
	errFlagNotFound   = fmt.Errorf("flag not found")
29 +
)
30 +
31 +
// Updater is the encapsulation of the directory watcher.
32 +
// TODO: hide details, just return opaque interface.
33 +
type Updater struct {
34 +
	started    bool
35 +
	dirPath    string
36 +
	parentPath string
37 +
	watcher    *fsnotify.Watcher
38 +
	flagSet    *flag.FlagSet
39 +
	done       chan bool
40 +
}
41 +
42 +
// Setup is a combination/shortcut for New+Initialize+Start.
43 +
func Setup(flagSet *flag.FlagSet, dirPath string) (*Updater, error) {
44 +
	u, err := New(flagSet, dirPath)
45 +
	if err != nil {
46 +
		return nil, err
47 +
	}
48 +
	err = u.Initialize()
49 +
	if err != nil {
50 +
		return nil, err
51 +
	}
52 +
	if err := u.Start(); err != nil {
53 +
		return nil, err
54 +
	}
55 +
	log.Infof("Configmap flag value watching initialized on %v", dirPath)
56 +
	return u, nil
57 +
}
58 +
59 +
// New creates an Updater for the directory.
60 +
func New(flagSet *flag.FlagSet, dirPath string) (*Updater, error) {
61 +
	watcher, err := fsnotify.NewWatcher()
62 +
	if err != nil {
63 +
		return nil, fmt.Errorf("dflag: error initializing fsnotify watcher")
64 +
	}
65 +
	return &Updater{
66 +
		flagSet:    flagSet,
67 +
		dirPath:    path.Clean(dirPath),
68 +
		parentPath: path.Clean(path.Join(dirPath, "..")), // add parent in case the dirPath is a symlink itself
69 +
		watcher:    watcher,
70 +
	}, nil
71 +
}
72 +
73 +
// Initialize reads the values from the directory for the first time.
74 +
func (u *Updater) Initialize() error {
75 +
	if u.started {
76 +
		return fmt.Errorf("dflag: already initialized updater")
77 +
	}
78 +
	return u.readAll( /* allowNonDynamic */ false)
79 +
}
80 +
81 +
// Start kicks off the go routine that watches the directory for updates of values.
82 +
func (u *Updater) Start() error {
83 +
	if u.started {
84 +
		return fmt.Errorf("dflag: updater already started")
85 +
	}
86 +
	if err := u.watcher.Add(u.parentPath); err != nil {
87 +
		return fmt.Errorf("unable to add parent dir %v to watch: %w", u.parentPath, err)
88 +
	}
89 +
	if err := u.watcher.Add(u.dirPath); err != nil { // add the dir itself.
90 +
		return fmt.Errorf("unable to add config dir %v to watch: %w", u.dirPath, err)
91 +
	}
92 +
	log.Infof("Now watching %v and %v", u.parentPath, u.dirPath)
93 +
	u.started = true
94 +
	u.done = make(chan bool)
95 +
	go u.watchForUpdates()
96 +
	return nil
97 +
}
98 +
99 +
// Stop stops the auto-updating go-routine.
100 +
func (u *Updater) Stop() error {
101 +
	if !u.started {
102 +
		return fmt.Errorf("dflag: not updating")
103 +
	}
104 +
	u.done <- true
105 +
	_ = u.watcher.Remove(u.dirPath)
106 +
	_ = u.watcher.Remove(u.parentPath)
107 +
	return nil
108 +
}
109 +
110 +
func (u *Updater) readAll(dynamicOnly bool) error {
111 +
	files, err := ioutil.ReadDir(u.dirPath)
112 +
	if err != nil {
113 +
		return fmt.Errorf("dflag: updater initialization: %w", err)
114 +
	}
115 +
	errorStrings := []string{}
116 +
	for _, f := range files {
117 +
		if strings.HasPrefix(path.Base(f.Name()), ".") {
118 +
			// skip random ConfigMap internals and dot files
119 +
			continue
120 +
		}
121 +
		fullPath := path.Join(u.dirPath, f.Name())
122 +
		if err := u.readFlagFile(fullPath, dynamicOnly); err != nil {
123 +
			if errors.Is(err, errFlagNotDynamic) && dynamicOnly {
124 +
				// ignore
125 +
			} else {
126 +
				errorStrings = append(errorStrings, fmt.Sprintf("flag %v: %v", f.Name(), err.Error()))
127 +
			}
128 +
		}
129 +
	}
130 +
	if len(errorStrings) > 0 {
131 +
		return fmt.Errorf("encountered %d errors while parsing flags from directory  \n  %v",
132 +
			len(errorStrings), strings.Join(errorStrings, "\n"))
133 +
	}
134 +
	return nil
135 +
}
136 +
137 +
func (u *Updater) readFlagFile(fullPath string, dynamicOnly bool) error {
138 +
	flagName := path.Base(fullPath)
139 +
	flag := u.flagSet.Lookup(flagName)
140 +
	if flag == nil {
141 +
		return errFlagNotFound
142 +
	}
143 +
	if dynamicOnly && !dflag.IsFlagDynamic(flag) {
144 +
		return errFlagNotDynamic
145 +
	}
146 +
	content, err := ioutil.ReadFile(fullPath)
147 +
	if err != nil {
148 +
		return err
149 +
	}
150 +
	str := string(content)
151 +
	log.Infof("updating %v to %q", flagName, str)
152 +
	// do not call flag.Value.Set, instead go through flagSet.Set to change "changed" state.
153 +
	return u.flagSet.Set(flagName, str)
154 +
}
155 +
156 +
func (u *Updater) watchForUpdates() {
157 +
	log.Infof("Starting watching")
158 +
	for {
159 +
		select {
160 +
		case event := <-u.watcher.Events:
161 +
			log.LogVf("ConfigMap got fsnotify %v ", event)
162 +
			if event.Name == u.dirPath || event.Name == path.Join(u.dirPath, k8sDataSymlink) { // nolint: nestif
163 +
				// case of the whole directory being re-symlinked
164 +
				switch event.Op {
165 +
				case fsnotify.Create:
166 +
					if err := u.watcher.Add(u.dirPath); err != nil { // add the dir itself.
167 +
						log.Errf("unable to add config dir %v to watch: %v", u.dirPath, err)
168 +
					}
169 +
					log.Infof("dflag: Re-reading flags after ConfigMap update.")
170 +
					if err := u.readAll( /* dynamicOnly */ true); err != nil {
171 +
						log.Errf("dflag: directory reload yielded errors: %v", err.Error())
172 +
					}
173 +
				case fsnotify.Remove, fsnotify.Chmod, fsnotify.Rename, fsnotify.Write:
174 +
				}
175 +
			} else if strings.HasPrefix(event.Name, u.dirPath) && !isK8sInternalDirectory(event.Name) {
176 +
				log.LogVf("ConfigMap got prefix %v", event)
177 +
				switch event.Op {
178 +
				case fsnotify.Create, fsnotify.Write, fsnotify.Rename, fsnotify.Remove:
179 +
					flagName := path.Base(event.Name)
180 +
					if err := u.readFlagFile(event.Name, true); err != nil {
181 +
						log.Errf("dflag: failed setting flag %s: %v", flagName, err.Error())
182 +
					}
183 +
				case fsnotify.Chmod:
184 +
				}
185 +
			}
186 +
		case <-u.done:
187 +
			return
188 +
		}
189 +
	}
190 +
}
191 +
192 +
func isK8sInternalDirectory(filePath string) bool {
193 +
	basePath := path.Base(filePath)
194 +
	return strings.HasPrefix(basePath, k8sInternalsPrefix)
195 +
}

@@ -0,0 +1,278 @@
Loading
1 +
// Copyright 2020 Fortio Authors
2 +
//
3 +
// Licensed under the Apache License, Version 2.0 (the "License");
4 +
// you may not use this file except in compliance with the License.
5 +
// You may obtain a copy of the License at
6 +
//
7 +
//     http://www.apache.org/licenses/LICENSE-2.0
8 +
//
9 +
// Unless required by applicable law or agreed to in writing, software
10 +
// distributed under the License is distributed on an "AS IS" BASIS,
11 +
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 +
// See the License for the specific language governing permissions and
13 +
// limitations under the License.
14 +
15 +
// Tee off traffic
16 +
17 +
package fhttp // import "fortio.org/fortio/fhttp"
18 +
19 +
import (
20 +
	"bufio"
21 +
	"bytes"
22 +
	"fmt"
23 +
	"io"
24 +
	"io/ioutil"
25 +
	"net"
26 +
	"net/http"
27 +
	"net/textproto"
28 +
	"strconv"
29 +
	"strings"
30 +
	"sync"
31 +
32 +
	"fortio.org/fortio/fnet"
33 +
	"fortio.org/fortio/log"
34 +
)
35 +
36 +
var (
37 +
	// EnvoyRequestID is the header set by envoy and we need to propagate for distributed tracing.
38 +
	EnvoyRequestID = textproto.CanonicalMIMEHeaderKey("x-request-id")
39 +
	// TraceHeader is the single aggregated open tracing header to propagate when present.
40 +
	TraceHeader = textproto.CanonicalMIMEHeaderKey("b3")
41 +
	// TraceHeadersPrefix is the prefix for the multi header version of open zipkin.
42 +
	TraceHeadersPrefix = textproto.CanonicalMIMEHeaderKey("x-b3-")
43 +
)
44 +
45 +
// TargetConf is the structure to configure one of the multiple targets for MultiServer.
46 +
type TargetConf struct {
47 +
	Destination  string // Destination URL or base
48 +
	MirrorOrigin bool   // wether to use the incoming request as URI and data params to outgoing one (proxy like)
49 +
	//	Return       bool   // Will return the result of this target
50 +
}
51 +
52 +
// MultiServerConfig configures the MultiServer and holds the http client it uses for proxying.
53 +
type MultiServerConfig struct {
54 +
	Targets []TargetConf
55 +
	Serial  bool // Serialize or parallel queries
56 +
	//	Javascript bool // return data as UI suitable
57 +
	Name   string
58 +
	client *http.Client
59 +
}
60 +
61 +
func makeMirrorRequest(baseURL string, r *http.Request, data []byte) *http.Request {
62 +
	url := baseURL + r.RequestURI
63 +
	bodyReader := ioutil.NopCloser(bytes.NewReader(data))
64 +
	req, err := http.NewRequestWithContext(r.Context(), r.Method, url, bodyReader)
65 +
	if err != nil {
66 +
		log.Warnf("new mirror request error for %q: %v", url, err)
67 +
		return nil
68 +
	}
69 +
	// Copy all headers
70 +
	// Host header is not in Header so safe to copy
71 +
	copyHeaders(req, r, true)
72 +
	return req
73 +
}
74 +
75 +
func copyHeaders(req, r *http.Request, all bool) {
76 +
	// Copy only trace headers unless all is true.
77 +
	for k, v := range r.Header {
78 +
		if all || k == EnvoyRequestID || k == TraceHeader || strings.HasPrefix(k, TraceHeadersPrefix) {
79 +
			for _, vv := range v {
80 +
				req.Header.Add(k, vv)
81 +
			}
82 +
			log.Debugf("Adding header %q = %q", k, v)
83 +
		} else {
84 +
			log.Debugf("Skipping header %q", k)
85 +
		}
86 +
	}
87 +
}
88 +
89 +
func makeSimpleRequest(url string, r *http.Request) *http.Request {
90 +
	req, err := http.NewRequestWithContext(r.Context(), "GET", url, nil)
91 +
	if err != nil {
92 +
		log.Warnf("new request error for %q: %v", url, err)
93 +
		return nil
94 +
	}
95 +
	// Copy only trace headers
96 +
	copyHeaders(req, r, false)
97 +
	req.Header.Add("User-Agent", userAgent)
98 +
	return req
99 +
}
100 +
101 +
// TeeHandler common part between TeeSerialHandler and TeeParallelHandler.
102 +
func (mcfg *MultiServerConfig) TeeHandler(w http.ResponseWriter, r *http.Request) {
103 +
	if log.LogVerbose() {
104 +
		LogRequest(r, mcfg.Name)
105 +
	}
106 +
	data, err := ioutil.ReadAll(r.Body)
107 +
	if err != nil {
108 +
		log.Errf("Error reading on %v: %v", r, err)
109 +
		http.Error(w, err.Error(), http.StatusInternalServerError)
110 +
		return
111 +
	}
112 +
	r.Body.Close()
113 +
	if mcfg.Serial {
114 +
		mcfg.TeeSerialHandler(w, r, data)
115 +
	} else {
116 +
		mcfg.TeeParallelHandler(w, r, data)
117 +
	}
118 +
}
119 +
120 +
func setupRequest(r *http.Request, i int, t TargetConf, data []byte) *http.Request {
121 +
	var req *http.Request
122 +
	if t.MirrorOrigin {
123 +
		req = makeMirrorRequest(t.Destination, r, data)
124 +
	} else {
125 +
		req = makeSimpleRequest(t.Destination, r)
126 +
	}
127 +
	if req == nil {
128 +
		// error already logged
129 +
		return nil
130 +
	}
131 +
	req.Header.Add("X-On-Behalf-Of", r.RemoteAddr)
132 +
	req.Header.Add("X-Fortio-Multi-ID", strconv.Itoa(i+1))
133 +
	log.LogVf("Going to %s", req.URL.String())
134 +
	return req
135 +
}
136 +
137 +
// TeeSerialHandler handles teeing off traffic in serial (one at a time) mode.
138 +
func (mcfg *MultiServerConfig) TeeSerialHandler(w http.ResponseWriter, r *http.Request, data []byte) {
139 +
	first := true
140 +
	for i, t := range mcfg.Targets {
141 +
		req := setupRequest(r, i, t, data)
142 +
		if req == nil {
143 +
			continue
144 +
		}
145 +
		url := req.URL.String()
146 +
		resp, err := mcfg.client.Do(req)
147 +
		if err != nil {
148 +
			msg := fmt.Sprintf("Error for %s: %v", url, err)
149 +
			log.Warnf(msg)
150 +
			if first {
151 +
				w.WriteHeader(http.StatusServiceUnavailable)
152 +
				first = false
153 +
			}
154 +
			_, _ = w.Write([]byte(msg))
155 +
			_, _ = w.Write([]byte("\n"))
156 +
			continue
157 +
		}
158 +
		if first {
159 +
			w.WriteHeader(resp.StatusCode)
160 +
			first = false
161 +
		}
162 +
		w, err := fnet.Copy(w, resp.Body)
163 +
		if err != nil {
164 +
			log.Warnf("Error copying response for %s: %v", url, err)
165 +
		}
166 +
		log.LogVf("copied %d from %s - code %d", w, url, resp.StatusCode)
167 +
		_ = resp.Body.Close()
168 +
	}
169 +
}
170 +
171 +
func singleRequest(client *http.Client, w io.Writer, req *http.Request, statusPtr *int) {
172 +
	url := req.URL.String()
173 +
	resp, err := client.Do(req)
174 +
	if err != nil {
175 +
		msg := fmt.Sprintf("Error for %s: %v", url, err)
176 +
		log.Warnf(msg)
177 +
		_, _ = w.Write([]byte(msg))
178 +
		_, _ = w.Write([]byte{'\n'})
179 +
		*statusPtr = -1
180 +
		return
181 +
	}
182 +
	*statusPtr = resp.StatusCode
183 +
	bw, err := fnet.Copy(w, resp.Body)
184 +
	if err != nil {
185 +
		log.Warnf("Error copying response for %s: %v", url, err)
186 +
	}
187 +
	log.LogVf("sr copied %d from %s - code %d", bw, url, resp.StatusCode)
188 +
	_ = resp.Body.Close()
189 +
}
190 +
191 +
// TeeParallelHandler handles teeing off traffic in parallel (one goroutine each) mode.
192 +
func (mcfg *MultiServerConfig) TeeParallelHandler(w http.ResponseWriter, r *http.Request, data []byte) {
193 +
	var wg sync.WaitGroup
194 +
	numTargets := len(mcfg.Targets)
195 +
	ba := make([]bytes.Buffer, numTargets)
196 +
	sa := make([]int, numTargets)
197 +
	for i := 0; i < numTargets; i++ {
198 +
		req := setupRequest(r, i, mcfg.Targets[i], data)
199 +
		if req == nil {
200 +
			continue
201 +
		}
202 +
		wg.Add(1)
203 +
		go func(client *http.Client, buffer *bytes.Buffer, request *http.Request, statusPtr *int) {
204 +
			writer := bufio.NewWriter(buffer)
205 +
			singleRequest(client, writer, request, statusPtr)
206 +
			writer.Flush()
207 +
			wg.Done()
208 +
		}(mcfg.client, &ba[i], req, &sa[i])
209 +
	}
210 +
	wg.Wait()
211 +
	// Get overall status only ok if all OK, first non ok sets status
212 +
	status := http.StatusOK
213 +
	for i := 0; i < numTargets; i++ {
214 +
		if sa[i] != http.StatusOK {
215 +
			status = sa[i]
216 +
			break
217 +
		}
218 +
	}
219 +
	if status <= 0 {
220 +
		status = http.StatusServiceUnavailable
221 +
	}
222 +
	w.WriteHeader(status)
223 +
	// Send all the data back to back
224 +
	for i := 0; i < numTargets; i++ {
225 +
		bw, err := w.Write(ba[i].Bytes())
226 +
		log.Debugf("For %d, wrote %d bytes - status %d", i, bw, sa[i])
227 +
		if err != nil {
228 +
			log.Warnf("Error writing back to %s: %v", r.RemoteAddr, err)
229 +
			break
230 +
		}
231 +
	}
232 +
}
233 +
234 +
// createClient http client for connection reuse.
235 +
func createClient() *http.Client {
236 +
	client := &http.Client{
237 +
		Transport: &http.Transport{
238 +
			// TODO make configurable, should be fine for now for most but extreme -c values
239 +
			MaxIdleConnsPerHost: 128, // must be more than incoming parallelization; divided by number of fan out if using parallel mode
240 +
			MaxIdleConns:        256,
241 +
		},
242 +
	}
243 +
	return client
244 +
}
245 +
246 +
// MultiServer starts fan out http server on the given port.
247 +
// Returns the mux and addr where the listening socket is bound.
248 +
// The port can be retrieved from it when requesting the 0 port as
249 +
// input for dynamic http server.
250 +
func MultiServer(port string, cfg *MultiServerConfig) (*http.ServeMux, net.Addr) {
251 +
	hName := cfg.Name
252 +
	if hName == "" {
253 +
		hName = "Multi on " + port // port could be :0 for dynamic...
254 +
	}
255 +
	mux, addr := HTTPServer(hName, port)
256 +
	if addr == nil {
257 +
		return nil, nil // error already logged
258 +
	}
259 +
	aStr := addr.String()
260 +
	if cfg.Name == "" {
261 +
		// get actual bound port in case of :0
262 +
		cfg.Name = "Multi on " + aStr
263 +
	}
264 +
	cfg.client = createClient()
265 +
	for i := range cfg.Targets {
266 +
		t := &cfg.Targets[i]
267 +
		if t.MirrorOrigin {
268 +
			t.Destination = strings.TrimSuffix(t.Destination, "/") // remove trailing / because we will concatenate the request URI
269 +
		}
270 +
		if !strings.HasPrefix(t.Destination, "https://") && !strings.HasPrefix(t.Destination, "http://") {
271 +
			log.Infof("Assuming http:// on missing scheme for '%s'", t.Destination)
272 +
			t.Destination = "http://" + t.Destination
273 +
		}
274 +
	}
275 +
	log.Infof("Multi-server on %s running with %+v", aStr, cfg)
276 +
	mux.HandleFunc("/", cfg.TeeHandler)
277 +
	return mux, addr
278 +
}

@@ -54,8 +54,8 @@
Loading
54 54
	Run(tid int)
55 55
}
56 56
57 -
// MakeRunners creates an array of NumThreads identical Runnable instances.
58 -
// (for the (rare/test) cases where there is no unique state needed)
57 +
// MakeRunners creates an array of NumThreads identical Runnable instances
58 +
// (for the (rare/test) cases where there is no unique state needed).
59 59
func (r *RunnerOptions) MakeRunners(rr Runnable) {
60 60
	log.Infof("Making %d clone of %+v", r.NumThreads, rr)
61 61
	if len(r.Runners) < r.NumThreads {
@@ -216,51 +216,53 @@
Loading
216 216
	if r.Runners == nil {
217 217
		r.Runners = make([]Runnable, r.NumThreads)
218 218
	}
219 -
	if r.Stop == nil {
220 -
		r.Stop = NewAborter()
221 -
		runnerChan := r.Stop.StopChan // need a copy to not race with assignement to nil
222 -
		go func() {
223 -
			gAbortMutex.Lock()
224 -
			gOutstandingRuns++
225 -
			n := gOutstandingRuns
226 -
			if gAbortChan == nil {
227 -
				log.LogVf("WATCHER %d First outstanding run starting, catching signal", n)
228 -
				gAbortChan = make(chan os.Signal, 1)
229 -
				signal.Notify(gAbortChan, os.Interrupt)
230 -
			}
231 -
			abortChan := gAbortChan
232 -
			gAbortMutex.Unlock()
233 -
			log.LogVf("WATCHER %d starting new watcher for signal! chan  g %v r %v (%d)", n, abortChan, runnerChan, runtime.NumGoroutine())
234 -
			select {
235 -
			case _, ok := <-abortChan:
236 -
				log.LogVf("WATCHER %d got interrupt signal! %v", n, ok)
237 -
				if ok {
238 -
					gAbortMutex.Lock()
239 -
					if gAbortChan != nil {
240 -
						log.LogVf("WATCHER %d closing %v to notify all", n, gAbortChan)
241 -
						close(gAbortChan)
242 -
						gAbortChan = nil
243 -
					}
244 -
					gAbortMutex.Unlock()
219 +
	if r.Stop != nil {
220 +
		return
221 +
	}
222 +
	// nil aborter (last normalization step:)
223 +
	r.Stop = NewAborter()
224 +
	runnerChan := r.Stop.StopChan // need a copy to not race with assignement to nil
225 +
	go func() {
226 +
		gAbortMutex.Lock()
227 +
		gOutstandingRuns++
228 +
		n := gOutstandingRuns
229 +
		if gAbortChan == nil {
230 +
			log.LogVf("WATCHER %d First outstanding run starting, catching signal", n)
231 +
			gAbortChan = make(chan os.Signal, 1)
232 +
			signal.Notify(gAbortChan, os.Interrupt)
233 +
		}
234 +
		abortChan := gAbortChan
235 +
		gAbortMutex.Unlock()
236 +
		log.LogVf("WATCHER %d starting new watcher for signal! chan  g %v r %v (%d)", n, abortChan, runnerChan, runtime.NumGoroutine())
237 +
		select {
238 +
		case _, ok := <-abortChan:
239 +
			log.LogVf("WATCHER %d got interrupt signal! %v", n, ok)
240 +
			if ok {
241 +
				gAbortMutex.Lock()
242 +
				if gAbortChan != nil {
243 +
					log.LogVf("WATCHER %d closing %v to notify all", n, gAbortChan)
244 +
					close(gAbortChan)
245 +
					gAbortChan = nil
245 246
				}
246 -
				r.Abort()
247 -
			case <-runnerChan:
248 -
				log.LogVf("WATCHER %d r.Stop readable", n)
249 -
				// nothing to do, stop happened
250 -
			}
251 -
			log.LogVf("WATCHER %d End of go routine", n)
252 -
			gAbortMutex.Lock()
253 -
			gOutstandingRuns--
254 -
			if gOutstandingRuns == 0 {
255 -
				log.LogVf("WATCHER %d Last watcher: resetting signal handler", n)
256 -
				gAbortChan = nil
257 -
				signal.Reset(os.Interrupt)
258 -
			} else {
259 -
				log.LogVf("WATCHER %d isn't the last one, %d left", n, gOutstandingRuns)
247 +
				gAbortMutex.Unlock()
260 248
			}
261 -
			gAbortMutex.Unlock()
262 -
		}()
263 -
	}
249 +
			r.Abort()
250 +
		case <-runnerChan:
251 +
			log.LogVf("WATCHER %d r.Stop readable", n)
252 +
			// nothing to do, stop happened
253 +
		}
254 +
		log.LogVf("WATCHER %d End of go routine", n)
255 +
		gAbortMutex.Lock()
256 +
		gOutstandingRuns--
257 +
		if gOutstandingRuns == 0 {
258 +
			log.LogVf("WATCHER %d Last watcher: resetting signal handler", n)
259 +
			gAbortChan = nil
260 +
			signal.Reset(os.Interrupt)
261 +
		} else {
262 +
			log.LogVf("WATCHER %d isn't the last one, %d left", n, gOutstandingRuns)
263 +
		}
264 +
		gAbortMutex.Unlock()
265 +
	}()
264 266
}
265 267
266 268
// Abort safely aborts the run by closing the channel and resetting that channel
@@ -273,7 +275,7 @@
Loading
273 275
	}
274 276
}
275 277
276 -
// internal version, returning the concrete implementation. logical std::move
278 +
// internal version, returning the concrete implementation. logical std::move.
277 279
func newPeriodicRunner(opts *RunnerOptions) *periodicRunner {
278 280
	r := &periodicRunner{*opts} // by default just copy the input params
279 281
	opts.ReleaseRunners()
@@ -295,90 +297,101 @@
Loading
295 297
	return &r.RunnerOptions // sort of returning this here
296 298
}
297 299
300 +
func (r *periodicRunner) runQPSSetup() (requestedDuration string, requestedQPS string, numCalls int64, leftOver int64) {
301 +
	// r.Duration will be 0 if endless flag has been provided. Otherwise it will have the provided duration time.
302 +
	hasDuration := (r.Duration > 0)
303 +
	// r.Exactly is > 0 if we use Exactly iterations instead of the duration.
304 +
	useExactly := (r.Exactly > 0)
305 +
	requestedDuration = "until stop"
306 +
	requestedQPS = fmt.Sprintf("%.9g", r.QPS)
307 +
	if !hasDuration && !useExactly {
308 +
		// Always print that as we need ^C to interrupt, in that case the user need to notice
309 +
		_, _ = fmt.Fprintf(r.Out, "Starting at %g qps with %d thread(s) [gomax %d] until interrupted\n",
310 +
			r.QPS, r.NumThreads, runtime.GOMAXPROCS(0))
311 +
		return
312 +
	}
313 +
	// else:
314 +
	requestedDuration = fmt.Sprint(r.Duration)
315 +
	numCalls = int64(r.QPS * r.Duration.Seconds())
316 +
	if useExactly {
317 +
		numCalls = r.Exactly
318 +
		requestedDuration = fmt.Sprintf("exactly %d calls", numCalls)
319 +
	}
320 +
	if numCalls < 2 {
321 +
		log.Warnf("Increasing the number of calls to the minimum of 2 with 1 thread. total duration will increase")
322 +
		numCalls = 2
323 +
		r.NumThreads = 1
324 +
	}
325 +
	if int64(2*r.NumThreads) > numCalls {
326 +
		newN := int(numCalls / 2)
327 +
		log.Warnf("Lowering number of threads - total call %d -> lowering from %d to %d threads", numCalls, r.NumThreads, newN)
328 +
		r.NumThreads = newN
329 +
	}
330 +
	numCalls /= int64(r.NumThreads)
331 +
	totalCalls := numCalls * int64(r.NumThreads)
332 +
	if useExactly {
333 +
		leftOver = r.Exactly - totalCalls
334 +
		if log.Log(log.Warning) {
335 +
			_, _ = fmt.Fprintf(r.Out, "Starting at %g qps with %d thread(s) [gomax %d] : exactly %d, %d calls each (total %d + %d)\n",
336 +
				r.QPS, r.NumThreads, runtime.GOMAXPROCS(0), r.Exactly, numCalls, totalCalls, leftOver)
337 +
		}
338 +
	} else {
339 +
		if log.Log(log.Warning) {
340 +
			_, _ = fmt.Fprintf(r.Out, "Starting at %g qps with %d thread(s) [gomax %d] for %v : %d calls each (total %d)\n",
341 +
				r.QPS, r.NumThreads, runtime.GOMAXPROCS(0), r.Duration, numCalls, totalCalls)
342 +
		}
343 +
	}
344 +
	return requestedDuration, requestedQPS, numCalls, leftOver
345 +
}
346 +
347 +
func (r *periodicRunner) runNoQPSSetup() (requestedDuration string, numCalls int64, leftOver int64) {
348 +
	// r.Duration will be 0 if endless flag has been provided. Otherwise it will have the provided duration time.
349 +
	hasDuration := (r.Duration > 0)
350 +
	// r.Exactly is > 0 if we use Exactly iterations instead of the duration.
351 +
	useExactly := (r.Exactly > 0)
352 +
	if !useExactly && !hasDuration {
353 +
		// Always log something when waiting for ^C
354 +
		_, _ = fmt.Fprintf(r.Out, "Starting at max qps with %d thread(s) [gomax %d] until interrupted\n",
355 +
			r.NumThreads, runtime.GOMAXPROCS(0))
356 +
		return
357 +
	}
358 +
	// else:
359 +
	if log.Log(log.Warning) {
360 +
		_, _ = fmt.Fprintf(r.Out, "Starting at max qps with %d thread(s) [gomax %d] ",
361 +
			r.NumThreads, runtime.GOMAXPROCS(0))
362 +
	}
363 +
	if useExactly {
364 +
		requestedDuration = fmt.Sprintf("exactly %d calls", r.Exactly)
365 +
		numCalls = r.Exactly / int64(r.NumThreads)
366 +
		leftOver = r.Exactly % int64(r.NumThreads)
367 +
		if log.Log(log.Warning) {
368 +
			_, _ = fmt.Fprintf(r.Out, "for %s (%d per thread + %d)\n", requestedDuration, numCalls, leftOver)
369 +
		}
370 +
	} else {
371 +
		requestedDuration = fmt.Sprint(r.Duration)
372 +
		if log.Log(log.Warning) {
373 +
			_, _ = fmt.Fprintf(r.Out, "for %s\n", requestedDuration)
374 +
		}
375 +
	}
376 +
	return
377 +
}
378 +
298 379
// Run starts the runner.
299 380
func (r *periodicRunner) Run() RunnerResults {
300 381
	r.Stop.Lock()
301 382
	runnerChan := r.Stop.StopChan // need a copy to not race with assignement to nil
302 383
	r.Stop.Unlock()
303 384
	useQPS := (r.QPS > 0)
304 -
	// r.Duration will be 0 if endless flag has been provided. Otherwise it will have the provided duration time.
305 -
	hasDuration := (r.Duration > 0)
306 385
	// r.Exactly is > 0 if we use Exactly iterations instead of the duration.
307 386
	useExactly := (r.Exactly > 0)
308 387
	var numCalls int64
309 388
	var leftOver int64 // left over from r.Exactly / numThreads
389 +
	var requestedDuration string
310 390
	requestedQPS := "max"
311 -
	requestedDuration := "until stop"
312 391
	if useQPS {
313 -
		requestedQPS = fmt.Sprintf("%.9g", r.QPS)
314 -
		if hasDuration || useExactly {
315 -
			requestedDuration = fmt.Sprint(r.Duration)
316 -
			numCalls = int64(r.QPS * r.Duration.Seconds())
317 -
			if useExactly {
318 -
				numCalls = r.Exactly
319 -
				requestedDuration = fmt.Sprintf("exactly %d calls", numCalls)
320 -
			}
321 -
			if numCalls < 2 {
322 -
				log.Warnf("Increasing the number of calls to the minimum of 2 with 1 thread. total duration will increase")
323 -
				numCalls = 2
324 -
				r.NumThreads = 1
325 -
			}
326 -
			if int64(2*r.NumThreads) > numCalls {
327 -
				newN := int(numCalls / 2)
328 -
				log.Warnf("Lowering number of threads - total call %d -> lowering from %d to %d threads", numCalls, r.NumThreads, newN)
329 -
				r.NumThreads = newN
330 -
			}
331 -
			numCalls /= int64(r.NumThreads)
332 -
			totalCalls := numCalls * int64(r.NumThreads)
333 -
			if useExactly {
334 -
				leftOver = r.Exactly - totalCalls
335 -
				if log.Log(log.Warning) {
336 -
					// nolint: gas
337 -
					_, _ = fmt.Fprintf(r.Out, "Starting at %g qps with %d thread(s) [gomax %d] : exactly %d, %d calls each (total %d + %d)\n",
338 -
						r.QPS, r.NumThreads, runtime.GOMAXPROCS(0), r.Exactly, numCalls, totalCalls, leftOver)
339 -
				}
340 -
			} else {
341 -
				if log.Log(log.Warning) {
342 -
					// nolint: gas
343 -
					_, _ = fmt.Fprintf(r.Out, "Starting at %g qps with %d thread(s) [gomax %d] for %v : %d calls each (total %d)\n",
344 -
						r.QPS, r.NumThreads, runtime.GOMAXPROCS(0), r.Duration, numCalls, totalCalls)
345 -
				}
346 -
			}
347 -
		} else {
348 -
			// Always print that as we need ^C to interrupt, in that case the user need to notice
349 -
			// nolint: gas
350 -
			_, _ = fmt.Fprintf(r.Out, "Starting at %g qps with %d thread(s) [gomax %d] until interrupted\n",
351 -
				r.QPS, r.NumThreads, runtime.GOMAXPROCS(0))
352 -
			numCalls = 0
353 -
		}
392 +
		requestedDuration, requestedQPS, numCalls, leftOver = r.runQPSSetup()
354 393
	} else {
355 -
		if !useExactly && !hasDuration {
356 -
			// Always log something when waiting for ^C
357 -
			// nolint: gas
358 -
			_, _ = fmt.Fprintf(r.Out, "Starting at max qps with %d thread(s) [gomax %d] until interrupted\n",
359 -
				r.NumThreads, runtime.GOMAXPROCS(0))
360 -
		} else {
361 -
			if log.Log(log.Warning) {
362 -
				// nolint: gas
363 -
				_, _ = fmt.Fprintf(r.Out, "Starting at max qps with %d thread(s) [gomax %d] ",
364 -
					r.NumThreads, runtime.GOMAXPROCS(0))
365 -
			}
366 -
			if useExactly {
367 -
				requestedDuration = fmt.Sprintf("exactly %d calls", r.Exactly)
368 -
				numCalls = r.Exactly / int64(r.NumThreads)
369 -
				leftOver = r.Exactly % int64(r.NumThreads)
370 -
				if log.Log(log.Warning) {
371 -
					// nolint: gas
372 -
					_, _ = fmt.Fprintf(r.Out, "for %s (%d per thread + %d)\n", requestedDuration, numCalls, leftOver)
373 -
				}
374 -
			} else {
375 -
				requestedDuration = fmt.Sprint(r.Duration)
376 -
				if log.Log(log.Warning) {
377 -
					// nolint: gas
378 -
					_, _ = fmt.Fprintf(r.Out, "for %s\n", requestedDuration)
379 -
				}
380 -
			}
381 -
		}
394 +
		requestedDuration, numCalls, leftOver = r.runNoQPSSetup()
382 395
	}
383 396
	runnersLen := len(r.Runners)
384 397
	if runnersLen == 0 {
@@ -425,17 +438,16 @@
Loading
425 438
	elapsed := time.Since(start)
426 439
	actualQPS := float64(functionDuration.Count) / elapsed.Seconds()
427 440
	if log.Log(log.Warning) {
428 -
		// nolint: gas
429 441
		_, _ = fmt.Fprintf(r.Out, "Ended after %v : %d calls. qps=%.5g\n", elapsed, functionDuration.Count, actualQPS)
430 442
	}
431 -
	if useQPS {
443 +
	if useQPS { // nolint: nestif
432 444
		percentNegative := 100. * float64(sleepTime.Hdata[0]) / float64(sleepTime.Count)
433 445
		// Somewhat arbitrary percentage of time the sleep was behind so we
434 446
		// may want to know more about the distribution of sleep time and warn the
435 447
		// user.
436 448
		if percentNegative > 5 {
437 449
			sleepTime.Print(r.Out, "Aggregated Sleep Time", []float64{50})
438 -
			_, _ = fmt.Fprintf(r.Out, "WARNING %.2f%% of sleep were falling behind\n", percentNegative) // nolint: gas
450 +
			_, _ = fmt.Fprintf(r.Out, "WARNING %.2f%% of sleep were falling behind\n", percentNegative)
439 451
		} else {
440 452
			if log.Log(log.Verbose) {
441 453
				sleepTime.Print(r.Out, "Aggregated Sleep Time", []float64{50})
@@ -448,14 +460,16 @@
Loading
448 460
	if useExactly && actualCount != r.Exactly {
449 461
		requestedDuration += fmt.Sprintf(", interrupted after %d", actualCount)
450 462
	}
451 -
	result := RunnerResults{r.RunType, r.Labels, start, requestedQPS, requestedDuration,
452 -
		actualQPS, elapsed, r.NumThreads, version.Short(), functionDuration.Export().CalcPercentiles(r.Percentiles), r.Exactly, r.Jitter}
463 +
	result := RunnerResults{
464 +
		r.RunType, r.Labels, start, requestedQPS, requestedDuration,
465 +
		actualQPS, elapsed, r.NumThreads, version.Short(), functionDuration.Export().CalcPercentiles(r.Percentiles), r.Exactly, r.Jitter,
466 +
	}
453 467
	if log.Log(log.Warning) {
454 468
		result.DurationHistogram.Print(r.Out, "Aggregated Function Time")
455 469
	} else {
456 470
		functionDuration.Counter.Print(r.Out, "Aggregated Function Time")
457 471
		for _, p := range result.DurationHistogram.Percentiles {
458 -
			_, _ = fmt.Fprintf(r.Out, "# target %g%% %.6g\n", p.Percentile, p.Value) // nolint: gas
472 +
			_, _ = fmt.Fprintf(r.Out, "# target %g%% %.6g\n", p.Percentile, p.Value)
459 473
		}
460 474
	}
461 475
	select {
@@ -468,7 +482,8 @@
Loading
468 482
	return result
469 483
}
470 484
471 -
// runOne runs in 1 go routine.
485 +
// runOne runs in 1 go routine (or main one when -c 1 == single threaded mode).
486 +
// nolint: gocognit // we should try to simplify it though.
472 487
func runOne(id int, runnerChan chan struct{},
473 488
	funcTimes *stats.Histogram, sleepTimes *stats.Histogram, numCalls int64, start time.Time, r *periodicRunner) {
474 489
	var i int64
@@ -499,7 +514,7 @@
Loading
499 514
		funcTimes.Record(time.Since(fStart).Seconds())
500 515
		i++
501 516
		// if using QPS / pre calc expected call # mode:
502 -
		if useQPS {
517 +
		if useQPS { // nolint: nestif
503 518
			if (useExactly || hasDuration) && i >= numCalls {
504 519
				break // expected exit for that mode
505 520
			}
@@ -556,13 +571,13 @@
Loading
556 571
		d.Hour(), d.Minute(), d.Second())
557 572
}
558 573
559 -
// getJitter returns a jitter time that is (+/-)10% of the duration t if t is >0
574 +
// getJitter returns a jitter time that is (+/-)10% of the duration t if t is >0.
560 575
func getJitter(t time.Duration) time.Duration {
561 -
	if t <= 0 {
576 +
	i := int64(float64(t)/10. + 0.5) // rounding to nearest instead of truncate
577 +
	if i <= 0 {
562 578
		return time.Duration(0)
563 579
	}
564 -
	i := int64(t / 10)
565 -
	j := rand.Int63n(2*i) - i
580 +
	j := rand.Int63n(2*i+1) - i // nolint:gosec // trying to be fast not crypto secure here
566 581
	return time.Duration(j)
567 582
}
568 583

@@ -42,7 +42,7 @@
Loading
42 42
	c.RecordN(v, 1)
43 43
}
44 44
45 -
// RecordN efficiently records the same value N times
45 +
// RecordN efficiently records the same value N times.
46 46
func (c *Counter) RecordN(v float64, n int) {
47 47
	isFirst := (c.Count == 0)
48 48
	c.Count += int64(n)
@@ -78,7 +78,7 @@
Loading
78 78
79 79
// Print prints stats.
80 80
func (c *Counter) Print(out io.Writer, msg string) {
81 -
	_, _ = fmt.Fprintf(out, "%s : count %d avg %.8g +/- %.4g min %g max %g sum %.9g\n", // nolint(errorcheck)
81 +
	_, _ = fmt.Fprintf(out, "%s : count %d avg %.8g +/- %.4g min %g max %g sum %.9g\n",
82 82
		msg, c.Count, c.Avg(), c.StdDev(), c.Min, c.Max, c.Sum)
83 83
}
84 84
@@ -121,7 +121,7 @@
Loading
121 121
// The intervals are ]prev,current] so for "90" (previous is 80) the values in that bucket are >80 and <=90
122 122
// that way a cumulative % up to that bucket means X% of the data <= 90 (or 100-X% > 90), works well for max too
123 123
// There are 2 special buckets - the first one is from min to and including 0,
124 -
// one after the last for value > last and up to max
124 +
// one after the last for value > last and up to max.
125 125
var (
126 126
	histogramBucketValues = []int32{
127 127
		0, 1, 2, 3, 4, 5, 6,
@@ -174,14 +174,14 @@
Loading
174 174
	Count   int64   // How many in this bucket
175 175
}
176 176
177 -
// Percentile value for the percentile
177 +
// Percentile value for the percentile.
178 178
type Percentile struct {
179 179
	Percentile float64 // For this Percentile
180 180
	Value      float64 // value at that Percentile
181 181
}
182 182
183 183
// HistogramData is the exported Histogram data, a sorted list of intervals
184 -
// covering [Min, Max]. Pure data, so Counter for instance is flattened
184 +
// covering [Min, Max]. Pure data, so Counter for instance is flattened.
185 185
type HistogramData struct {
186 186
	Count       int64
187 187
	Min         float64
@@ -194,20 +194,21 @@
Loading
194 194
}
195 195
196 196
// NewHistogram creates a new histogram (sets up the buckets).
197 -
// Divider value can not be zero, otherwise returns zero
198 -
func NewHistogram(Offset float64, Divider float64) *Histogram {
197 +
// Divider value can not be zero, otherwise returns zero.
198 +
func NewHistogram(offset float64, divider float64) *Histogram {
199 199
	h := new(Histogram)
200 -
	h.Offset = Offset
201 -
	if Divider == 0 {
200 +
	h.Offset = offset
201 +
	if divider == 0 {
202 202
		return nil
203 203
	}
204 -
	h.Divider = Divider
204 +
	h.Divider = divider
205 205
	h.Hdata = make([]int32, numBuckets)
206 206
	return h
207 207
}
208 208
209 209
// Val2Bucket values are kept in two different structure
210 -
// val2Bucket allows you reach between 0 and 1000 in constant time
210 +
// val2Bucket allows you reach between 0 and 1000 in constant time.
211 +
// nolint: gochecknoinits // we need to init these.
211 212
func init() {
212 213
	val2Bucket = make([]int, maxArrayValue)
213 214
	maxArrayValueIndex = -1
@@ -234,10 +235,10 @@
Loading
234 235
}
235 236
236 237
// lookUpIdx looks for scaledValue's index in histogramBucketValues
237 -
// TODO: change linear time to O(log(N)) with binary search
238 +
// TODO: change linear time to O(log(N)) with binary search.
238 239
func lookUpIdx(scaledValue int) int {
239 240
	scaledValue32 := int32(scaledValue)
240 -
	if scaledValue32 < maxArrayValue { //constant
241 +
	if scaledValue32 < maxArrayValue { // constant
241 242
		return val2Bucket[scaledValue]
242 243
	}
243 244
	for i := maxArrayValueIndex; i < numValues; i++ {