Skip to content

Commit

Permalink
merged ewma and cleaned up meter tests
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenButtolph committed Sep 6, 2020
2 parents ac5db00 + 363d67d commit 666b64a
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 333 deletions.
64 changes: 64 additions & 0 deletions utils/uptime/continuous_meter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package uptime

import (
"math"
"time"
)

// ContinuousFactory implements the Factory interface by returning a continuous
// time meter.
type ContinuousFactory struct{}

// New implements the Factory interface.
func (ContinuousFactory) New(halflife time.Duration) Meter {
return NewMeter(halflife)
}

type continuousMeter struct {
running bool
started time.Time

halflife time.Duration

value float64
lastUpdated time.Time
}

// NewMeter returns a new Meter with the provided halflife
func NewMeter(halflife time.Duration) Meter {
return &continuousMeter{halflife: halflife}
}

func (a *continuousMeter) Start(currentTime time.Time) {
if a.running {
return
}
a.Read(currentTime)
a.running = true
}

func (a *continuousMeter) Stop(currentTime time.Time) {
if !a.running {
return
}
a.Read(currentTime)
a.running = false
}

func (a *continuousMeter) Read(currentTime time.Time) float64 {
timeSincePreviousUpdate := currentTime.Sub(a.lastUpdated)
if timeSincePreviousUpdate <= 0 {
return a.value
}
a.lastUpdated = currentTime

factor := math.Pow(2, -float64(timeSincePreviousUpdate)/float64(a.halflife))
a.value *= factor
if a.running {
a.value += 1 - factor
}
return a.value
}
14 changes: 14 additions & 0 deletions utils/uptime/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package uptime

import (
"time"
)

// Factory returns new meters.
type Factory interface {
// New returns a new meter with the provided halflife.
New(halflife time.Duration) Meter
}
112 changes: 63 additions & 49 deletions utils/uptime/interval_meter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,32 @@ const (
maxSkippedIntervals = 32
)

// IntervalFactory implements the Factory interface by returning an intervale
// based meter.
type IntervalFactory struct{}

// New implements the Factory interface.
func (IntervalFactory) New(halflife time.Duration) Meter {
return NewIntervalMeter(halflife)
}

type intervalMeter struct {
running bool
started time.Time

halflife time.Duration

value float64
nextHalvening time.Time
lastUpdated time.Time
previousValues time.Duration
currentValue time.Duration
nextHalvening time.Time
lastUpdated time.Time
}

// NewIntervalMeter returns a new Meter with the provided halflife
func NewIntervalMeter(halflife time.Duration) Meter {
return &intervalMeter{halflife: halflife}
}

// Start implements the Meter interface
func (a *intervalMeter) Start(currentTime time.Time) {
if a.running {
return
Expand All @@ -36,7 +45,6 @@ func (a *intervalMeter) Start(currentTime time.Time) {
a.running = true
}

// Stop implements the Meter interface
func (a *intervalMeter) Stop(currentTime time.Time) {
if !a.running {
return
Expand All @@ -45,57 +53,63 @@ func (a *intervalMeter) Stop(currentTime time.Time) {
a.running = false
}

// Read implements the Meter interface
func (a *intervalMeter) Read(currentTime time.Time) float64 {
if !currentTime.After(a.lastUpdated) {
return a.value
}
if currentTime.After(a.lastUpdated) {
// try to finish the current round
if !currentTime.Before(a.nextHalvening) {
if a.running {
a.currentValue += a.nextHalvening.Sub(a.lastUpdated)
}
a.lastUpdated = a.nextHalvening
a.nextHalvening = a.nextHalvening.Add(a.halflife)
a.previousValues += a.currentValue >> 1
a.currentValue = 0
a.previousValues >>= 1

// try to finish the current round
if currentTime.After(a.nextHalvening) {
if a.running {
additionalRunningTime := float64(a.nextHalvening.Sub(a.lastUpdated)) / float64(a.halflife)
a.value += additionalRunningTime / 2
}
a.lastUpdated = a.nextHalvening
a.nextHalvening = a.nextHalvening.Add(a.halflife)
a.value /= 2

// try to skip future rounds
if totalTime := currentTime.Sub(a.lastUpdated); totalTime > a.halflife {
numSkippedPeriods := totalTime / a.halflife
if numSkippedPeriods > maxSkippedIntervals {
// If this meter hasn't been read in a long time, avoid
// potential shifting overflow issues and just jump to a
// reasonable value.
if a.running {
a.value = 1
} else {
a.value = 0
// try to skip future rounds
if totalTime := currentTime.Sub(a.lastUpdated); totalTime >= a.halflife {
numSkippedPeriods := totalTime / a.halflife
if numSkippedPeriods > maxSkippedIntervals {
a.lastUpdated = currentTime
a.nextHalvening = currentTime.Add(a.halflife)

// If this meter hasn't been read in a long time, avoid
// potential shifting overflow issues and just jump to a
// reasonable value.
a.currentValue = 0
if a.running {
a.previousValues = a.halflife >> 1
return 1
}
a.previousValues = 0
return 0
}
a.lastUpdated = currentTime
a.nextHalvening = a.lastUpdated.Add(a.halflife)
return a.value
}

invFactor := 1 << uint(numSkippedPeriods)
factor := 1 / float64(invFactor)
a.value *= factor
if a.running {
a.value += 1 - factor
if numSkippedPeriods > 0 {
a.previousValues >>= numSkippedPeriods
if a.running {
additionalRunningTime := a.halflife - (a.halflife >> numSkippedPeriods)
a.previousValues += additionalRunningTime >> 1
}
skippedDuration := numSkippedPeriods * a.halflife
a.lastUpdated = a.lastUpdated.Add(skippedDuration)
a.nextHalvening = a.nextHalvening.Add(skippedDuration)
}
}
a.value /= 2
skippedDuration := a.halflife * numSkippedPeriods
a.lastUpdated = a.lastUpdated.Add(skippedDuration)
a.nextHalvening = a.nextHalvening.Add(skippedDuration)
}

// increment the value for the current round
if a.running {
a.currentValue += currentTime.Sub(a.lastUpdated)
}
a.lastUpdated = currentTime
}

// increment the value for the current round
if a.running {
additionalRunningTime := float64(currentTime.Sub(a.lastUpdated)) / float64(a.halflife)
a.value += additionalRunningTime / 2
spentTime := a.halflife - a.nextHalvening.Sub(a.lastUpdated)
if spentTime == 0 {
return float64(2*a.previousValues) / float64(a.halflife)
}
a.lastUpdated = currentTime
return a.value
spentTime <<= 1
expectedValue := float64(a.currentValue) / float64(spentTime)
return float64(a.previousValues)/float64(a.halflife) + expectedValue
}
39 changes: 0 additions & 39 deletions utils/uptime/interval_meter_benchmark_test.go

This file was deleted.

117 changes: 0 additions & 117 deletions utils/uptime/interval_meter_test.go

This file was deleted.

Loading

0 comments on commit 666b64a

Please sign in to comment.