Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

metrics: Synchronous metrics #29775

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

zeim839
Copy link
Contributor

@zeim839 zeim839 commented May 14, 2024

This pull-request is based on #27918, which was not accepted because go-ethereum/metrics was being refactored at the time.

It removes concurrent EWMAs and Meters; rates are synchronously computed whenever Update() or Snapshot() are called. We introduce sampling periods to imitate clock ticks: events arriving within a 5s period are accumulated in an 'uncounted' variable, which is used to calculate the EWMA per-second rate once the 5s period elapses.

// NewEWMA constructs a new EWMA with the given alpha and sampling period.
func NewEWMA(alpha float64, period time.Duration) EWMA {
	return &StandardEWMA{alpha: alpha, period: period, ts: time.Now()}
}

// We imitate the current implementation by specifying 5-second clock ticks.
// Updates that arrive within 5s of each other are accumulated, and the EWMA
// is updated once the next 5s interval begins.
func NewEWMA1() EWMA {
        // create an EWMA with a 5-second sampling period.
	return NewEWMA(1-math.Exp(-5.0/60.0/1), 5*time.Second)
}

m := NewEWMA1()
m.Update(10)

// Returns 0 because a whole sampling period has not yet elapsed, the EWMA is
// still accumulating events.
fmt.Println(m.Snapshot().Rate())

for range time.Tick(5 * time.Seconds) {
        // Rate will begin at 10 and then exponentially decline. Rate is only manually
        // updated whenever Snapshot() or Update() is called, no need for a clock.
        fmt.Println(m1.Snapshot().Rate())
}

Consequently, Timer and Meter (which use EWMAs) Stop()'s are removed because they are not handled by a concurrent routine. To maintain consistency, Meter is also modified so that its rateMean is updated only once per 5s (i.e. it changes in tandem with the underlying EWMAs):

// Mark records the occurrence of n events.
func (m *StandardMeter) Mark(n int64) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	// Synchronize rateMean so that it's only updated after
	// a sampling period elapses.
	if elapsed := time.Since(m.lastMark); elapsed >= 5*time.Second {
		m.lastMark = m.lastMark.Add(elapsed)
		m.count += m.uncounted
		m.uncounted = 0
	}

	m.uncounted += n
	m.a1.Update(n)
	m.a5.Update(n)
	m.a15.Update(n)
}

Testing has also been modified, such that expected EWMA values are calculated by unit tests instead of having to cross-check hardcoded values.

Why?

In the worst case, complexity remains the same and you're computing EWMAs every 5 seconds (but without a separate goroutine).

In the best case, stale EWMAs are garbage collected (you dont have to worry about calling Stop()) and you've saved yourself some processing power.

@zeim839 zeim839 changed the title Synchronous metrics metrics: Synchronous metrics May 14, 2024
@zeim839 zeim839 marked this pull request as ready for review May 14, 2024 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant