github.com/minio/madmin-go/v3@v3.0.51/perf-client.go (about) 1 // 2 // Copyright (c) 2015-2023 MinIO, Inc. 3 // 4 // This file is part of MinIO Object Storage stack 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU Affero General Public License as 8 // published by the Free Software Foundation, either version 3 of the 9 // License, or (at your option) any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU Affero General Public License for more details. 15 // 16 // You should have received a copy of the GNU Affero General Public License 17 // along with this program. If not, see <http://www.gnu.org/licenses/>. 18 // 19 20 package madmin 21 22 import ( 23 "context" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "io" 28 "math/rand" 29 "net/http" 30 "net/url" 31 "time" 32 33 "github.com/dustin/go-humanize" 34 ) 35 36 // ClientPerfExtraTime - time for get lock or other 37 type ClientPerfExtraTime struct { 38 TimeSpent int64 `json:"dur,omitempty"` 39 } 40 41 // ClientPerfResult - stats from client to server 42 type ClientPerfResult struct { 43 Endpoint string `json:"endpoint,omitempty"` 44 Error string `json:"error,omitempty"` 45 BytesSend uint64 46 TimeSpent int64 47 } 48 49 // clientPerfReader - wrap the reader 50 type clientPerfReader struct { 51 count uint64 52 startTime time.Time 53 endTime time.Time 54 buf []byte 55 } 56 57 // Start - reader start 58 func (c *clientPerfReader) Start() { 59 buf := make([]byte, 128*humanize.KiByte) 60 rand.Read(buf) 61 c.buf = buf 62 c.startTime = time.Now() 63 } 64 65 // End - reader end 66 func (c *clientPerfReader) End() { 67 c.endTime = time.Now() 68 } 69 70 // Read - reader send data 71 func (c *clientPerfReader) Read(p []byte) (n int, err error) { 72 n = copy(p, c.buf) 73 c.count += uint64(n) 74 return n, nil 75 } 76 77 var _ io.Reader = &clientPerfReader{} 78 79 const ( 80 // MaxClientPerfTimeout for max time out for client perf 81 MaxClientPerfTimeout = time.Second * 30 82 // MinClientPerfTimeout for min time out for client perf 83 MinClientPerfTimeout = time.Second * 5 84 ) 85 86 // ClientPerf - perform net from client to MinIO servers 87 func (adm *AdminClient) ClientPerf(ctx context.Context, dur time.Duration) (result ClientPerfResult, err error) { 88 if dur > MaxClientPerfTimeout { 89 dur = MaxClientPerfTimeout 90 } 91 if dur < MinClientPerfTimeout { 92 dur = MinClientPerfTimeout 93 } 94 ctx, cancel := context.WithTimeout(ctx, dur) 95 defer cancel() 96 queryVals := make(url.Values) 97 reader := &clientPerfReader{} 98 reader.Start() 99 _, err = adm.executeMethod(ctx, http.MethodPost, requestData{ 100 queryValues: queryVals, 101 relPath: adminAPIPrefix + "/speedtest/client/devnull", 102 contentReader: reader, 103 }) 104 reader.End() 105 if errors.Is(err, context.DeadlineExceeded) { 106 err = nil 107 } 108 109 resp, err := adm.executeMethod(context.Background(), http.MethodPost, requestData{ 110 queryValues: queryVals, 111 relPath: adminAPIPrefix + "/speedtest/client/devnull/extratime", 112 }) 113 if err != nil { 114 return ClientPerfResult{}, err 115 } 116 var extraTime ClientPerfExtraTime 117 dec := json.NewDecoder(resp.Body) 118 err = dec.Decode(&extraTime) 119 if err != nil { 120 return ClientPerfResult{}, err 121 } 122 durSpend := reader.endTime.Sub(reader.startTime).Nanoseconds() 123 if extraTime.TimeSpent > 0 { 124 durSpend = durSpend - extraTime.TimeSpent 125 } 126 if durSpend <= 0 { 127 return ClientPerfResult{}, fmt.Errorf("unexpected spent time duration, mostly NTP errors on the server") 128 } 129 return ClientPerfResult{ 130 BytesSend: reader.count, 131 TimeSpent: durSpend, 132 Error: "", 133 Endpoint: adm.endpointURL.String(), 134 }, err 135 }