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  }