mirror of https://github.com/minio/minio.git
220 lines
5.1 KiB
Go
220 lines
5.1 KiB
Go
// Copyright (c) 2015-2021 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/>.
|
|
|
|
//go:generate msgp -file=$GOFILE -unexported
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/minio/madmin-go/v3"
|
|
)
|
|
|
|
const (
|
|
sizeLessThan1KiB = iota
|
|
sizeLessThan1MiB
|
|
sizeLessThan10MiB
|
|
sizeLessThan100MiB
|
|
sizeLessThan1GiB
|
|
sizeGreaterThan1GiB
|
|
// Add new entries here
|
|
|
|
sizeLastElemMarker
|
|
)
|
|
|
|
// sizeToTag converts a size to a tag.
|
|
func sizeToTag(size int64) int {
|
|
switch {
|
|
case size < 1024:
|
|
return sizeLessThan1KiB
|
|
case size < 1024*1024:
|
|
return sizeLessThan1MiB
|
|
case size < 10*1024*1024:
|
|
return sizeLessThan10MiB
|
|
case size < 100*1024*1024:
|
|
return sizeLessThan100MiB
|
|
case size < 1024*1024*1024:
|
|
return sizeLessThan1GiB
|
|
default:
|
|
return sizeGreaterThan1GiB
|
|
}
|
|
}
|
|
|
|
func sizeTagToString(tag int) string {
|
|
switch tag {
|
|
case sizeLessThan1KiB:
|
|
return "LESS_THAN_1_KiB"
|
|
case sizeLessThan1MiB:
|
|
return "LESS_THAN_1_MiB"
|
|
case sizeLessThan10MiB:
|
|
return "LESS_THAN_10_MiB"
|
|
case sizeLessThan100MiB:
|
|
return "LESS_THAN_100_MiB"
|
|
case sizeLessThan1GiB:
|
|
return "LESS_THAN_1_GiB"
|
|
case sizeGreaterThan1GiB:
|
|
return "GREATER_THAN_1_GiB"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// AccElem holds information for calculating an average value
|
|
type AccElem struct {
|
|
Total int64
|
|
Size int64
|
|
N int64
|
|
}
|
|
|
|
// Add a duration to a single element.
|
|
func (a *AccElem) add(dur time.Duration) {
|
|
if dur < 0 {
|
|
dur = 0
|
|
}
|
|
a.Total += int64(dur)
|
|
a.N++
|
|
}
|
|
|
|
// Add a duration to a single element.
|
|
func (a *AccElem) addSize(dur time.Duration, sz int64) {
|
|
if dur < 0 {
|
|
dur = 0
|
|
}
|
|
a.Total += int64(dur)
|
|
a.Size += sz
|
|
a.N++
|
|
}
|
|
|
|
// Merge b into a.
|
|
func (a *AccElem) merge(b AccElem) {
|
|
a.N += b.N
|
|
a.Total += b.Total
|
|
a.Size += b.Size
|
|
}
|
|
|
|
// Avg returns average time spent.
|
|
func (a AccElem) avg() time.Duration {
|
|
if a.N >= 1 && a.Total > 0 {
|
|
return time.Duration(a.Total / a.N)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// asTimedAction returns the element as a madmin.TimedAction.
|
|
func (a AccElem) asTimedAction() madmin.TimedAction {
|
|
return madmin.TimedAction{AccTime: uint64(a.Total), Count: uint64(a.N), Bytes: uint64(a.Size)}
|
|
}
|
|
|
|
// lastMinuteLatency keeps track of last minute latency.
|
|
type lastMinuteLatency struct {
|
|
Totals [60]AccElem
|
|
LastSec int64
|
|
}
|
|
|
|
// Merge data of two lastMinuteLatency structure
|
|
func (l lastMinuteLatency) merge(o lastMinuteLatency) (merged lastMinuteLatency) {
|
|
if l.LastSec > o.LastSec {
|
|
o.forwardTo(l.LastSec)
|
|
merged.LastSec = l.LastSec
|
|
} else {
|
|
l.forwardTo(o.LastSec)
|
|
merged.LastSec = o.LastSec
|
|
}
|
|
|
|
for i := range merged.Totals {
|
|
merged.Totals[i] = AccElem{
|
|
Total: l.Totals[i].Total + o.Totals[i].Total,
|
|
N: l.Totals[i].N + o.Totals[i].N,
|
|
Size: l.Totals[i].Size + o.Totals[i].Size,
|
|
}
|
|
}
|
|
return merged
|
|
}
|
|
|
|
// Add a new duration data
|
|
func (l *lastMinuteLatency) add(t time.Duration) {
|
|
sec := time.Now().Unix()
|
|
l.forwardTo(sec)
|
|
winIdx := sec % 60
|
|
l.Totals[winIdx].add(t)
|
|
l.LastSec = sec
|
|
}
|
|
|
|
// Add a new duration data
|
|
func (l *lastMinuteLatency) addSize(t time.Duration, sz int64) {
|
|
sec := time.Now().Unix()
|
|
l.forwardTo(sec)
|
|
winIdx := sec % 60
|
|
l.Totals[winIdx].addSize(t, sz)
|
|
l.LastSec = sec
|
|
}
|
|
|
|
// Merge all recorded latencies of last minute into one
|
|
func (l *lastMinuteLatency) getTotal() AccElem {
|
|
var res AccElem
|
|
sec := time.Now().Unix()
|
|
l.forwardTo(sec)
|
|
for _, elem := range l.Totals[:] {
|
|
res.merge(elem)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// forwardTo time t, clearing any entries in between.
|
|
func (l *lastMinuteLatency) forwardTo(t int64) {
|
|
if l.LastSec >= t {
|
|
return
|
|
}
|
|
if t-l.LastSec >= 60 {
|
|
l.Totals = [60]AccElem{}
|
|
return
|
|
}
|
|
for l.LastSec != t {
|
|
// Clear next element.
|
|
idx := (l.LastSec + 1) % 60
|
|
l.Totals[idx] = AccElem{}
|
|
l.LastSec++
|
|
}
|
|
}
|
|
|
|
// LastMinuteHistogram keeps track of last minute sizes added.
|
|
type LastMinuteHistogram [sizeLastElemMarker]lastMinuteLatency
|
|
|
|
// Merge safely merges two LastMinuteHistogram structures into one
|
|
func (l LastMinuteHistogram) Merge(o LastMinuteHistogram) (merged LastMinuteHistogram) {
|
|
for i := range l {
|
|
merged[i] = l[i].merge(o[i])
|
|
}
|
|
return merged
|
|
}
|
|
|
|
// Add latency t from object with the specified size.
|
|
func (l *LastMinuteHistogram) Add(size int64, t time.Duration) {
|
|
l[sizeToTag(size)].add(t)
|
|
}
|
|
|
|
// GetAvgData will return the average for each bucket from the last time minute.
|
|
// The number of objects is also included.
|
|
func (l *LastMinuteHistogram) GetAvgData() [sizeLastElemMarker]AccElem {
|
|
var res [sizeLastElemMarker]AccElem
|
|
for i, elem := range l[:] {
|
|
res[i] = elem.getTotal()
|
|
}
|
|
return res
|
|
}
|