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