Skip to content

Commit

Permalink
Clean up Unix time and frequency conversions
Browse files Browse the repository at this point in the history
  • Loading branch information
marcfrei committed Sep 10, 2024
1 parent 861bb7e commit ab36683
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 25 deletions.
22 changes: 2 additions & 20 deletions base/unixutil/uinxutil_linux.go → base/unixutil/freq.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
package unixutil

import (
"golang.org/x/sys/unix"
)

func NsecToNsecTimeval(nsec int64) unix.Timeval {
sec := nsec / 1e9
nsec = nsec % 1e9
// The field unix.Timeval.Usec must always be non-negative.
if nsec < 0 {
sec -= 1
nsec += 1e9
}
return unix.Timeval{
Sec: sec,
Usec: nsec,
}
}

// In struct timex, freq, ppsfreq, and stabil are (scaled) ppm (parts per
// million) with a 16-bit fractional part, which means that a value of 1 in one
// of those fields actually means 2^-16 ppm, and 2^16=65536 is 1 ppm. This is
// the case for both input values (in the case of freq) and output values.
// See, https://www.man7.org/linux/man-pages/man2/adjtimex.2.html

func FreqToScaledPPM(freq float64) int64 {
return int64(freq * 65536 * 1e6)
func ScaledPPMFromFreq(freq float64) int64 {
return int64(freq * (65536.0 * 1e6))
}

func FreqFromScaledPPM(scaledPPM int64) float64 {
Expand Down
59 changes: 59 additions & 0 deletions base/unixutil/freq_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package unixutil_test

// Based on an Claude AI interaction

import (
"testing"

"example.com/scion-time/base/unixutil"
)

func TestScaledPPMFromFreq(t *testing.T) {
tests := []struct {
name string
freq float64
expected int64
}{
{"Zero frequency", 0, 0},
{"Positive frequency", 1, 65536000000},
{"Negative frequency", -1, -65536000000},
{"Small positive frequency", 0.000001, 65536},
{"Small negative frequency", -0.000001, -65536},
{"Large positive frequency", 1000, 65536000000000},
{"Large negative frequency", -1000, -65536000000000},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := unixutil.ScaledPPMFromFreq(tt.freq)
if result != tt.expected {
t.Errorf("ScaledPPMFromFreq(%f) = %d; want %d", tt.freq, result, tt.expected)
}
})
}
}

func TestFreqFromScaledPPM(t *testing.T) {
tests := []struct {
name string
scaledPPM int64
expected float64
}{
{"Zero scaled PPM", 0, 0},
{"Positive scaled PPM", 65536000000, 1},
{"Negative scaled PPM", -65536000000, -1},
{"Small positive scaled PPM", 65536, 0.000001},
{"Small negative scaled PPM", -65536, -0.000001},
{"Large positive scaled PPM", 65536000000000, 1000},
{"Large negative scaled PPM", -65536000000000, -1000},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := unixutil.FreqFromScaledPPM(tt.scaledPPM)
if result != tt.expected {
t.Errorf("FreqFromScaledPPM(%d) = %f; want %f", tt.scaledPPM, result, tt.expected)
}
})
}
}
19 changes: 19 additions & 0 deletions base/unixutil/timeval_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package unixutil

import (
"golang.org/x/sys/unix"
)

func TimevalFromNsec(nsec int64) unix.Timeval {
sec := nsec / 1e9
nsec = nsec % 1e9
// The field unix.Timeval.Usec must always be non-negative.
if nsec < 0 {
sec -= 1
nsec += 1e9
}
return unix.Timeval{
Sec: sec,
Usec: nsec,
}
}
4 changes: 2 additions & 2 deletions core/sync/adjustments/pi_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (c *PIController) Do(offset time.Duration) {
slog.Duration("offset", offset))
tx = unix.Timex{
Modes: unix.ADJ_SETOFFSET | unix.ADJ_NANO,
Time: unixutil.NsecToNsecTimeval(offset.Nanoseconds()),
Time: unixutil.TimevalFromNsec(offset.Nanoseconds()),
}
_, err = unix.ClockAdjtime(unix.CLOCK_REALTIME, &tx)
if err != nil {
Expand All @@ -103,7 +103,7 @@ func (c *PIController) Do(offset time.Duration) {
slog.Float64("frequency", freq))
tx = unix.Timex{
Modes: unix.ADJ_FREQUENCY,
Freq: unixutil.FreqToScaledPPM(freq),
Freq: unixutil.ScaledPPMFromFreq(freq),
}
_, err = unix.ClockAdjtime(unix.CLOCK_REALTIME, &tx)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion core/sync/adjustments/sys_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (a *SysAdjustment) Do(offset time.Duration) {
slog.Duration("offset", offset))
tx.Modes |= unix.ADJ_SETOFFSET
tx.Modes |= unix.ADJ_NANO
tx.Time = unixutil.NsecToNsecTimeval(offset.Nanoseconds())
tx.Time = unixutil.TimevalFromNsec(offset.Nanoseconds())
} else {
log.LogAttrs(ctx, slog.LevelDebug, "adjusting clock",
slog.Duration("offset", offset))
Expand Down
4 changes: 2 additions & 2 deletions driver/clocks/sysclk_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func setOffset(log *slog.Logger, offset time.Duration) {
"setting time", slog.Duration("offset", offset))
tx := unix.Timex{
Modes: unix.ADJ_SETOFFSET | unix.ADJ_NANO,
Time: unixutil.NsecToNsecTimeval(offset.Nanoseconds()),
Time: unixutil.TimevalFromNsec(offset.Nanoseconds()),
}
_, err := unix.ClockAdjtime(unix.CLOCK_REALTIME, &tx)
if err != nil {
Expand All @@ -92,7 +92,7 @@ func setFrequency(log *slog.Logger, frequency float64) {
"setting frequency", slog.Float64("frequency", frequency))
tx := unix.Timex{
Modes: unix.ADJ_FREQUENCY,
Freq: unixutil.FreqToScaledPPM(frequency),
Freq: unixutil.ScaledPPMFromFreq(frequency),
}
_, err := unix.ClockAdjtime(unix.CLOCK_REALTIME, &tx)
if err != nil {
Expand Down

0 comments on commit ab36683

Please sign in to comment.