diff --git a/core/clock.go b/core/clock.go index 927363a..e0511ef 100644 --- a/core/clock.go +++ b/core/clock.go @@ -1,3 +1,20 @@ +// Package core provides timing and clock functionality for traffic control operations. +// +// IMPORTANT: Before using any timing functions (Duration2TcTime, Time2Tick, etc.), +// you must initialize the clock parameters by calling InitializeClock(). +// +// Example usage: +// import "github.com/florianl/go-tc/core" +// +// func main() { +// if err := core.InitializeClock(); err != nil { +// log.Printf("Warning: failed to initialize clock: %v", err) +// } +// +// // Now you can use timing functions +// ticks := core.Time2Tick(1000) +// } + package core import ( @@ -8,6 +25,9 @@ import ( var ( tickInUSec float64 clockFactor float64 + + // isSet indicates whether the clock parameters have been initialized. + isSet bool ) const ( @@ -15,6 +35,39 @@ const ( timeUnitsPerSec = 1000000 ) +// InitializeClock initializes the clock parameters by reading from /proc/net/psched on Linux. +// On non-Linux platforms, it sets default values (1.0 for both parameters). +// This function must be called before using any of the timing functions. +// It returns an error if the clock parameters cannot be read on Linux. +func InitializeClock() error { + isSet = true + return initializeClock() +} + +// SetClockParameters allows manual configuration of the clock parameters. +// This is useful for testing or when custom clock values are needed. +// clockFactor is the clock resolution factor, tickInUSec is the tick to microsecond conversion factor. +func SetClockParameters(newClockFactor, newTickInUSec float64) { + isSet = true + clockFactor = newClockFactor + tickInUSec = newTickInUSec +} + +// IsClockInitialized returns true if the clock parameters have been initialized. +func IsClockInitialized() bool { + return isSet +} + +// GetClockFactor returns the current clock factor value. +func GetClockFactor() float64 { + return clockFactor +} + +// GetTickInUSec returns the current tick in microseconds conversion factor. +func GetTickInUSec() float64 { + return tickInUSec +} + // Duration2TcTime implements iproute2/tc/q_netem.c:get_ticks(). // It converts a given duration into a time value that can be converted to ticks with Time2Tick(). // On error it returns syscall.EINVAL. diff --git a/core/clock_linux.go b/core/clock_linux.go index 8163504..9b7c758 100644 --- a/core/clock_linux.go +++ b/core/clock_linux.go @@ -8,12 +8,11 @@ import ( "os" ) -func init() { +// initializeClock reads clock parameters from /proc/net/psched and initializes the global variables. +func initializeClock() error { var err error clockFactor, tickInUSec, err = readPsched() - if err != nil { - fmt.Fprintf(os.Stderr, "%s", err) - } + return err } func readPsched() (float64, float64, error) { diff --git a/core/clock_other.go b/core/clock_other.go index d948344..fa59cb1 100644 --- a/core/clock_other.go +++ b/core/clock_other.go @@ -3,7 +3,9 @@ package core -func init() { +// initializeClock sets default clock parameters for non-Linux platforms. +func initializeClock() error { clockFactor = 1.0 tickInUSec = 1.0 + return nil } diff --git a/core/clock_test.go b/core/clock_test.go index 423e9e7..305d32d 100644 --- a/core/clock_test.go +++ b/core/clock_test.go @@ -2,12 +2,20 @@ package core import ( "errors" + "fmt" "syscall" "testing" "time" ) func TestClock(t *testing.T) { + // Initialize clock for testing + if err := InitializeClock(); err != nil { + t.Logf("Warning: failed to initialize clock: %v", err) + // Set fallback values for testing + SetClockParameters(1.0, 1.0) + } + t.Run("Tick", func(t *testing.T) { tick := Time2Tick(0xC0FFEE) time := Tick2Time(tick) @@ -70,3 +78,29 @@ func TestDuration2TcTime(t *testing.T) { }) } } + +// ExampleInitializeClock demonstrates the clock initialization API +func ExampleInitializeClock() { + // Initialize clock parameters + if err := InitializeClock(); err != nil { + fmt.Printf("Warning: failed to initialize clock: %v\n", err) + // You can optionally set fallback values if initialization fails + SetClockParameters(1.0, 1.0) + } + + // Set known values for predictable output + SetClockParameters(1.0, 1.0) + + // Now you can use timing functions + ticks := Time2Tick(1000) + fmt.Printf("Time2Tick(1000) = %d\n", ticks) + + // You can also get the current clock parameters + clockFactor := GetClockFactor() + tickInUSec := GetTickInUSec() + fmt.Printf("Clock Factor: %.1f, Tick in µs: %.1f\n", clockFactor, tickInUSec) + + // Output: + // Time2Tick(1000) = 1000 + // Clock Factor: 1.0, Tick in µs: 1.0 +} diff --git a/example_netem_test.go b/example_netem_test.go index b3603de..bfb777d 100644 --- a/example_netem_test.go +++ b/example_netem_test.go @@ -95,6 +95,12 @@ func ExampleNetem() { } func ExampleNetem_with_delay() { + // Initialize clock parameters + if err := core.InitializeClock(); err != nil { + fmt.Fprintf(os.Stderr, "could not initialize clock: %v\n", err) + return + } + tcIface := "ExampleNetemDelay" rtnl, err := setupDummyInterface(tcIface) diff --git a/q_netem_test.go b/q_netem_test.go index 5de426b..da10539 100644 --- a/q_netem_test.go +++ b/q_netem_test.go @@ -10,6 +10,13 @@ import ( ) func TestNetem(t *testing.T) { + // Initialize clock parameters for timing functions + if err := core.InitializeClock(); err != nil { + t.Logf("Warning: failed to initialize clock: %v", err) + // Set fallback values for testing + core.SetClockParameters(1.0, 1.0) + } + delayDist := []int16{9, 7, 5, 3, 1} tests := map[string]struct { val Netem diff --git a/q_tbf_test.go b/q_tbf_test.go index dba2acb..6a1ac28 100644 --- a/q_tbf_test.go +++ b/q_tbf_test.go @@ -4,6 +4,7 @@ import ( "errors" "testing" + "github.com/florianl/go-tc/core" "github.com/google/go-cmp/cmp" ) @@ -30,6 +31,13 @@ func TestTbf(t *testing.T) { }}}, } + // Initialize clock parameters for timing functions + if err := core.InitializeClock(); err != nil { + t.Logf("Warning: failed to initialize clock: %v", err) + // Set fallback values for testing + core.SetClockParameters(1.0, 1.0) + } + for name, testcase := range tests { t.Run(name, func(t *testing.T) { data, err1 := marshalTbf(&testcase.val) diff --git a/ratetable.go b/ratetable.go index 0243313..0242a71 100644 --- a/ratetable.go +++ b/ratetable.go @@ -11,6 +11,10 @@ import ( // iproute2/tc/tc_core.c:tc_calc_rtable() func generateRateTable(pol *Policy) ([]byte, error) { + if !core.IsClockInitialized() { + return nil, fmt.Errorf("generateRateTable: use " + + "github.com/florianl/go-tc/core.InitializeClock() first") + } var rate [256]uint32 if pol == nil { diff --git a/ratetable_linux_test.go b/ratetable_linux_test.go index fa1352e..2541c36 100644 --- a/ratetable_linux_test.go +++ b/ratetable_linux_test.go @@ -7,6 +7,7 @@ import ( "errors" "testing" + "github.com/florianl/go-tc/core" "github.com/florianl/go-tc/internal/unix" ) @@ -184,6 +185,12 @@ func TestGenerateRateTable(t *testing.T) { expect: rate8kbitBurst5kbPeakrate12kbitMpu64Mtu1464Drop, }, } + // Initialize clock parameters for timing functions + if err := core.InitializeClock(); err != nil { + t.Logf("Warning: failed to initialize clock: %v", err) + // Set fallback values for testing + core.SetClockParameters(1.0, 1.0) + } for name, testcase := range tests { t.Run(name, func(t *testing.T) {