vitess.io/vitess@v0.16.2/go/vt/throttler/interval_history.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package throttler
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  )
    23  
    24  // intervalHistory stores a value per interval over time.
    25  // For example, thread_trottler.go stores the number of requests per 1 second
    26  // interval in an intervalHistory instance.
    27  // This data is used by the MaxReplicationLagModule to determine the historic
    28  // average value between two arbitrary points in time e.g. to find out the
    29  // average actual throttler rate between two replication lag measurements.
    30  // In general, the history should reflect only a short period of time (on the
    31  // order of minutes) and is therefore bounded.
    32  type intervalHistory struct {
    33  	records           []record
    34  	interval          time.Duration
    35  	nextIntervalStart time.Time
    36  }
    37  
    38  func newIntervalHistory(capacity int64, interval time.Duration) *intervalHistory {
    39  	return &intervalHistory{
    40  		records:  make([]record, 0, capacity),
    41  		interval: interval,
    42  	}
    43  }
    44  
    45  // add
    46  // It is up to the programmer to ensure that two add() calls do not cover the
    47  // same interval.
    48  func (h *intervalHistory) add(record record) {
    49  	if record.time.Before(h.nextIntervalStart) {
    50  		panic(fmt.Sprintf("BUG: cannot add record because it is already covered by a previous entry. record: %v next expected interval start: %v", record, h.nextIntervalStart))
    51  	}
    52  	if !record.time.Truncate(h.interval).Equal(record.time) {
    53  		panic(fmt.Sprintf("BUG: cannot add record because it does not start at the beginning of the interval. record: %v", record))
    54  	}
    55  	// TODO(mberlin): Bound the list.
    56  	h.records = append(h.records, record)
    57  	h.nextIntervalStart = record.time.Add(h.interval)
    58  }
    59  
    60  // average returns the average value across all observations which span
    61  // the range [from, to).
    62  // Partially included observations are accounted by their included fraction.
    63  // Missing observations are assumed with the value zero.
    64  func (h *intervalHistory) average(from, to time.Time) float64 {
    65  	// Search only entries whose time of observation is in [start, end).
    66  	// Example: [from, to) = [1.5s, 2.5s) => [start, end) = [1s, 2s)
    67  	start := from.Truncate(h.interval)
    68  	end := to.Truncate(h.interval)
    69  
    70  	sum := 0.0
    71  	count := 0.0
    72  	var nextIntervalStart time.Time
    73  	for i := len(h.records) - 1; i >= 0; i-- {
    74  		t := h.records[i].time
    75  
    76  		if t.After(end) {
    77  			continue
    78  		}
    79  		if t.Before(start) {
    80  			break
    81  		}
    82  
    83  		// Account for intervals which were not recorded.
    84  		if !nextIntervalStart.IsZero() {
    85  			uncoveredRange := nextIntervalStart.Sub(t)
    86  			count += float64(uncoveredRange / h.interval)
    87  		}
    88  
    89  		// If an interval is only partially included, count only that fraction.
    90  		durationAfterTo := t.Add(h.interval).Sub(to)
    91  		if durationAfterTo < 0 {
    92  			durationAfterTo = 0
    93  		}
    94  		durationBeforeFrom := from.Sub(t)
    95  		if durationBeforeFrom < 0 {
    96  			durationBeforeFrom = 0
    97  		}
    98  		weight := float64((h.interval - durationBeforeFrom - durationAfterTo).Nanoseconds()) / float64(h.interval.Nanoseconds())
    99  
   100  		sum += weight * float64(h.records[i].value)
   101  		count += weight
   102  		nextIntervalStart = t.Add(-1 * h.interval)
   103  	}
   104  
   105  	return float64(sum) / count
   106  }