Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions core/clock.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -8,13 +25,49 @@ import (
var (
tickInUSec float64
clockFactor float64

// isSet indicates whether the clock parameters have been initialized.
isSet bool
)

const (
// iproute2/include/utils.h:timeUnitsPerSec
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.
Expand Down
7 changes: 3 additions & 4 deletions core/clock_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion core/clock_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
34 changes: 34 additions & 0 deletions core/clock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
6 changes: 6 additions & 0 deletions example_netem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions q_netem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions q_tbf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"testing"

"github.com/florianl/go-tc/core"
"github.com/google/go-cmp/cmp"
)

Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions ratetable.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions ratetable_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"testing"

"github.com/florianl/go-tc/core"
"github.com/florianl/go-tc/internal/unix"
)

Expand Down Expand Up @@ -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) {
Expand Down