agones.dev/agones@v1.54.0/test/e2e/framework/perf.go (about)

     1  package framework
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  	"sync"
    10  	"time"
    11  
    12  	"fortio.org/fortio/fhttp"
    13  	"fortio.org/fortio/stats"
    14  	"github.com/sirupsen/logrus"
    15  
    16  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    17  )
    18  
    19  // StatsCollector collects latency and throughput counters.
    20  // The ReportDuration() method is safe for concurrent use by multiple goroutines.
    21  type StatsCollector struct {
    22  	name      string
    23  	outputDir string
    24  	version   string
    25  
    26  	mu              sync.Mutex
    27  	samples         []time.Duration
    28  	statusCounts    map[int]int64
    29  	firstSampleTime time.Time
    30  	lastSampleTime  time.Time
    31  }
    32  
    33  // ReportDuration adds a single time measurement.
    34  func (p *StatsCollector) ReportDuration(d time.Duration, err error) {
    35  	p.mu.Lock()
    36  	defer p.mu.Unlock()
    37  
    38  	n := time.Now()
    39  	if len(p.samples) == 0 {
    40  		p.firstSampleTime = n
    41  	}
    42  	p.lastSampleTime = n
    43  	p.samples = append(p.samples, d)
    44  	if p.statusCounts == nil {
    45  		p.statusCounts = map[int]int64{}
    46  	}
    47  	p.statusCounts[errToHTTPStatusCode(err)]++
    48  }
    49  
    50  func errToHTTPStatusCode(err error) int {
    51  	// crude translation from 'err' to HTTP status code.
    52  	switch {
    53  	case err == nil:
    54  		return http.StatusOK
    55  	case k8serrors.IsNotFound(err):
    56  		return http.StatusNotFound
    57  	case k8serrors.IsConflict(err):
    58  		return http.StatusConflict
    59  	case k8serrors.IsUnauthorized(err):
    60  		return http.StatusUnauthorized
    61  	case k8serrors.IsServiceUnavailable(err):
    62  		return http.StatusServiceUnavailable
    63  	default:
    64  		return http.StatusInternalServerError
    65  	}
    66  }
    67  
    68  // Report outputs performance report to log.
    69  func (p *StatsCollector) Report() {
    70  	if len(p.samples) == 0 {
    71  		return
    72  	}
    73  
    74  	h := stats.NewHistogram(0, 1)
    75  	for _, s := range p.samples {
    76  		h.Record(s.Seconds())
    77  	}
    78  
    79  	var rr fhttp.HTTPRunnerResults
    80  	rr.RunType = "HTTP"
    81  	rr.Labels = fmt.Sprintf("Agones %s_%s", p.name, p.version)
    82  	rr.StartTime = time.Now().UTC()
    83  	rr.ActualDuration = p.lastSampleTime.Sub(p.firstSampleTime)
    84  	rr.DurationHistogram = h.Export()
    85  	rr.DurationHistogram.CalcPercentiles([]float64{50, 90, 95, 99, 99.9})
    86  	rr.RetCodes = map[int]int64{}
    87  	rr.ActualQPS = float64(len(p.samples)) / rr.ActualDuration.Seconds()
    88  
    89  	logrus.
    90  		WithField("avg", rr.DurationHistogram.Avg).
    91  		WithField("count", rr.DurationHistogram.Count).
    92  		WithField("min", rr.DurationHistogram.Min).
    93  		WithField("max", rr.DurationHistogram.Max).
    94  		WithField("p50", rr.DurationHistogram.CalcPercentile(50)).
    95  		WithField("p90", rr.DurationHistogram.CalcPercentile(90)).
    96  		WithField("p95", rr.DurationHistogram.CalcPercentile(95)).
    97  		WithField("p99", rr.DurationHistogram.CalcPercentile(99)).
    98  		WithField("p999", rr.DurationHistogram.CalcPercentile(99.9)).
    99  		WithField("duration", p.lastSampleTime.Sub(p.firstSampleTime).Seconds()).
   100  		Info(p.name)
   101  
   102  	if p.outputDir != "" {
   103  		err := os.MkdirAll(p.outputDir, 0o755)
   104  		if err != nil {
   105  			logrus.WithError(err).Errorf("unable to create a folder: %s", p.outputDir)
   106  			return
   107  		}
   108  
   109  		fname := filepath.Join(p.outputDir, fmt.Sprintf("%s_%s_%s.json", p.name, p.version, rr.StartTime.Format("2006-01-02_1504")))
   110  		f, err := os.Create(fname)
   111  		if err != nil {
   112  			logrus.WithError(err).Errorf("unable to create performance log: %s", fname)
   113  			return
   114  		}
   115  		defer func() {
   116  			if cerr := f.Close(); cerr != nil {
   117  				logrus.Error(cerr)
   118  			}
   119  		}()
   120  
   121  		e := json.NewEncoder(f)
   122  		e.SetIndent("", "  ")
   123  		err = e.Encode(rr)
   124  		if err != nil {
   125  			logrus.Error(err)
   126  		}
   127  	}
   128  }