Skip to content

Commit 1231f7e

Browse files
database_observability: move time-related functions to a separate file (#3923)
database_observability: move time-related functions to a separate file Move stuff related to time, uptime and overflows to a separate file to make it easier to test and reuse.
1 parent 8805f5c commit 1231f7e

File tree

4 files changed

+117
-106
lines changed

4 files changed

+117
-106
lines changed

‎internal/component/database_observability/mysql/collector/query_sample.go‎

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"database/sql"
66
"fmt"
7-
"math"
87
"time"
98

109
"github.com/go-kit/log"
@@ -293,7 +292,7 @@ func (c *QuerySample) fetchQuerySamples(ctx context.Context) error {
293292
}
294293

295294
serverStartTime := now - uptime
296-
row.TimestampMilliseconds = c.calculateWallTime(serverStartTime, row.TimerEndPicoseconds.Float64)
295+
row.TimestampMilliseconds = calculateWallTime(serverStartTime, row.TimerEndPicoseconds.Float64, uptime)
297296

298297
digestText, err := c.sqlParser.CleanTruncatedText(row.DigestText.String)
299298
if err != nil {
@@ -382,26 +381,6 @@ func (c *QuerySample) fetchQuerySamples(ctx context.Context) error {
382381
return nil
383382
}
384383

385-
func (c *QuerySample) calculateWallTime(serverStartTime, timer float64) float64 {
386-
// timer indicates event timing since server startup.
387-
// The timer value is in picoseconds with a column type of bigint unsigned. This value can overflow after about ~213 days.
388-
// We need to account for this overflow when calculating the timestamp.
389-
390-
// Knowing the number of overflows that occurred, we can calculate how much overflow time to compensate.
391-
previousOverflows := calculateNumberOfOverflows(c.lastUptime)
392-
overflowTime := float64(previousOverflows) * picosecondsOverflowInSeconds
393-
// We then add this overflow compensation to the server start time, and also add the timer value (remember this is counted from server start).
394-
// The resulting value is the timestamp in seconds at which an event happened.
395-
timerSeconds := picosecondsToSeconds(timer)
396-
timestampSeconds := serverStartTime + overflowTime + timerSeconds
397-
398-
return secondsToMilliseconds(timestampSeconds)
399-
}
400-
401-
func calculateNumberOfOverflows(uptime float64) int {
402-
return int(math.Floor(uptime / picosecondsOverflowInSeconds))
403-
}
404-
405384
func (c *QuerySample) determineTimerClauseAndLimit(uptime float64) (string, float64) {
406385
timerClause := endOfTimeline
407386
currentOverflows := calculateNumberOfOverflows(uptime)
@@ -419,39 +398,3 @@ func (c *QuerySample) determineTimerClauseAndLimit(uptime float64) (string, floa
419398

420399
return timerClause, limit
421400
}
422-
423-
// uptimeSinceOverflow calculates the uptime "modulo" overflows (if any): it returns the remainder of the uptime value with any
424-
// overflowed time removed
425-
func uptimeSinceOverflow(uptime float64) float64 {
426-
overflowAdjustment := float64(calculateNumberOfOverflows(uptime)) * picosecondsOverflowInSeconds
427-
return secondsToPicoseconds(uptime - overflowAdjustment)
428-
}
429-
430-
var picosecondsOverflowInSeconds = picosecondsToSeconds(float64(math.MaxUint64))
431-
432-
const (
433-
picosecondsPerSecond float64 = 1e12
434-
millisecondsPerSecond float64 = 1e3
435-
millisecondsPerPicosecond float64 = 1e9
436-
nanosecondsPerMillisecond float64 = 1e6
437-
)
438-
439-
func picosecondsToSeconds(picoseconds float64) float64 {
440-
return picoseconds / picosecondsPerSecond
441-
}
442-
443-
func picosecondsToMilliseconds(picoseconds float64) float64 {
444-
return picoseconds / millisecondsPerPicosecond
445-
}
446-
447-
func millisecondsToNanoseconds(milliseconds float64) float64 {
448-
return milliseconds * nanosecondsPerMillisecond
449-
}
450-
451-
func secondsToPicoseconds(seconds float64) float64 {
452-
return seconds * picosecondsPerSecond
453-
}
454-
455-
func secondsToMilliseconds(seconds float64) float64 {
456-
return seconds * millisecondsPerSecond
457-
}

‎internal/component/database_observability/mysql/collector/query_sample_test.go‎

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2152,54 +2152,6 @@ func TestQuerySample_handles_timer_overflows(t *testing.T) {
21522152
})
21532153
}
21542154

2155-
func TestQuerySample_calculateWallTime(t *testing.T) {
2156-
t.Run("calculates the timestamp at which an event happened", func(t *testing.T) {
2157-
c := &QuerySample{}
2158-
serverStartTime := float64(2)
2159-
timer := 2e12 // Timer indicates event timing, counted since server startup. 2 seconds in picoseconds
2160-
2161-
result := c.calculateWallTime(serverStartTime, timer)
2162-
assert.Equalf(t, float64(4000), result, "got %f, want 4000", result)
2163-
})
2164-
2165-
t.Run("calculates the timestamp, taking into account the overflows", func(t *testing.T) {
2166-
c := &QuerySample{lastUptime: picosecondsToSeconds(math.MaxUint64) + 1}
2167-
serverStartTime := float64(3)
2168-
timer := 2e12 // 2 seconds in picoseconds
2169-
2170-
result := c.calculateWallTime(serverStartTime, timer)
2171-
2172-
assert.Equalf(t, 18446749073.709553, result, "got %f, want 18446749073.709553", result)
2173-
})
2174-
2175-
t.Run("calculates another timestamp when timer approaches overflow", func(t *testing.T) {
2176-
c := &QuerySample{lastUptime: picosecondsToSeconds(math.MaxUint64) + 1}
2177-
serverStartTime := float64(3)
2178-
timer := float64(math.MaxUint64 - 5)
2179-
2180-
result := c.calculateWallTime(serverStartTime, timer)
2181-
2182-
assert.Equalf(t, 3.6893491147419106e+10, result, "got %f, want 3.6893491147419106e+10", result)
2183-
})
2184-
}
2185-
2186-
func TestQuerySample_calculateNumberOfOverflows(t *testing.T) {
2187-
testCases := map[string]struct {
2188-
expected uint64
2189-
uptime float64
2190-
}{
2191-
"0 overflows": {0, 5},
2192-
"1 overflow": {1, picosecondsToSeconds(math.MaxUint64) + 5},
2193-
"2 overflows": {2, picosecondsToSeconds(math.MaxUint64)*2 + 5},
2194-
}
2195-
2196-
for name, tc := range testCases {
2197-
t.Run(name, func(t *testing.T) {
2198-
assert.EqualValues(t, tc.expected, calculateNumberOfOverflows(tc.uptime))
2199-
})
2200-
}
2201-
}
2202-
22032155
func TestQuerySample_calculateTimerClauseAndLimit(t *testing.T) {
22042156
tests := map[string]struct {
22052157
lastUptime float64
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package collector
2+
3+
import "math"
4+
5+
var picosecondsOverflowInSeconds = picosecondsToSeconds(float64(math.MaxUint64))
6+
7+
const (
8+
picosecondsPerSecond float64 = 1e12
9+
millisecondsPerSecond float64 = 1e3
10+
millisecondsPerPicosecond float64 = 1e9
11+
nanosecondsPerMillisecond float64 = 1e6
12+
)
13+
14+
// calculateWallTime calculates the wall-clock timestamp for an event.
15+
// The timerPicoseconds indicates event timing since server startup.
16+
// Since this value can overflow after approximately ~213 days (column type of bigint unsigned),
17+
// this function accounts for overflows by calculating the number of previous overflows and
18+
// compensating accordingly. Returns the timestamp in milliseconds when the event occurred.
19+
func calculateWallTime(serverStartTimeSeconds, timerPicoseconds, uptimeSeconds float64) float64 {
20+
// Knowing the number of overflows that occurred, we can calculate how much overflow time to compensate
21+
previousOverflows := calculateNumberOfOverflows(uptimeSeconds)
22+
overflowTime := float64(previousOverflows) * picosecondsOverflowInSeconds
23+
24+
// We then add this overflow compensation to the server start time, and also add the timer value (remember this is counted from server start).
25+
// The resulting value is the timestamp in seconds at which an event happened.
26+
timerSeconds := picosecondsToSeconds(timerPicoseconds)
27+
timestampSeconds := serverStartTimeSeconds + overflowTime + timerSeconds
28+
29+
return secondsToMilliseconds(timestampSeconds)
30+
}
31+
32+
// calculateNumberOfOverflows calculates how many timer overflows have occurred based on the given uptime.
33+
func calculateNumberOfOverflows(uptimeSeconds float64) int {
34+
return int(math.Floor(uptimeSeconds / picosecondsOverflowInSeconds))
35+
}
36+
37+
// uptimeSinceOverflow calculates the uptime "modulo" overflows (if any): it returns the remainder
38+
// of the uptime value with any overflowed time removed, in picoseconds.
39+
func uptimeSinceOverflow(uptimeSeconds float64) float64 {
40+
overflowAdjustment := float64(calculateNumberOfOverflows(uptimeSeconds)) * picosecondsOverflowInSeconds
41+
return secondsToPicoseconds(uptimeSeconds - overflowAdjustment)
42+
}
43+
44+
func picosecondsToMilliseconds(picoseconds float64) float64 {
45+
return picoseconds / millisecondsPerPicosecond
46+
}
47+
48+
func millisecondsToNanoseconds(milliseconds float64) float64 {
49+
return milliseconds * nanosecondsPerMillisecond
50+
}
51+
52+
func picosecondsToSeconds(picoseconds float64) float64 {
53+
return picoseconds / picosecondsPerSecond
54+
}
55+
56+
func secondsToPicoseconds(seconds float64) float64 {
57+
return seconds * picosecondsPerSecond
58+
}
59+
60+
func secondsToMilliseconds(seconds float64) float64 {
61+
return seconds * millisecondsPerSecond
62+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package collector
2+
3+
import (
4+
"math"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func Test_CalculateWallTime(t *testing.T) {
11+
t.Run("calculates the timestamp at which an event happened", func(t *testing.T) {
12+
serverStartTime := 2.0
13+
timer := secondsToPicoseconds(2)
14+
lastUptime := 0.0
15+
16+
result := calculateWallTime(serverStartTime, timer, lastUptime)
17+
require.Equalf(t, 4000.0, result, "got %f, want 4000", result)
18+
})
19+
20+
t.Run("calculates the timestamp, taking into account the overflows", func(t *testing.T) {
21+
serverStartTime := 3.0
22+
timer := secondsToPicoseconds(2)
23+
lastUptime := picosecondsToSeconds(math.MaxUint64) + 1
24+
25+
result := calculateWallTime(serverStartTime, timer, lastUptime)
26+
require.Equalf(t, 18446749073.709553, result, "got %f, want 18446749073.709553", result)
27+
})
28+
29+
t.Run("calculates another timestamp when timer approaches overflow", func(t *testing.T) {
30+
serverStartTime := 3.0
31+
timer := float64(math.MaxUint64 - 5)
32+
lastUptime := picosecondsToSeconds(math.MaxUint64) + 1
33+
34+
result := calculateWallTime(serverStartTime, timer, lastUptime)
35+
require.Equalf(t, 3.6893491147419106e+10, result, "got %f, want 3.6893491147419106e+10", result)
36+
})
37+
}
38+
39+
func Test_CalculateNumberOfOverflows(t *testing.T) {
40+
testCases := map[string]struct {
41+
expected uint64
42+
uptime float64
43+
}{
44+
"0 overflows": {0, 5},
45+
"1 overflow": {1, picosecondsToSeconds(math.MaxUint64) + 5},
46+
"2 overflows": {2, picosecondsToSeconds(math.MaxUint64)*2 + 5},
47+
}
48+
49+
for name, tc := range testCases {
50+
t.Run(name, func(t *testing.T) {
51+
require.EqualValues(t, tc.expected, calculateNumberOfOverflows(tc.uptime))
52+
})
53+
}
54+
}

0 commit comments

Comments
 (0)