Merge pull request #14002 from luiscape:luis/allign-clocks-in-capprofiling

PiperOrigin-RevId: 968200399
This commit is contained in:
gVisor bot
2026-08-20 19:33:48 -07:00
15 changed files with 264 additions and 40 deletions
+1 -1
View File
@@ -93,7 +93,7 @@ func Boot() (*kernel.Kernel, error) {
// Create timekeeper.
tk := kernel.NewTimekeeper()
params := kernel.NewVDSOParamPage(k.MemoryFile(), vdso.ParamPage.FileRange())
tk.SetClocks(time.NewCalibratedClocks(), params)
tk.SetClocks(time.NewCalibratedClocks(false), params)
creds := auth.NewRootCredentials(auth.NewRootUserNamespace())
+9
View File
@@ -1936,6 +1936,15 @@ func (k *Kernel) MonotonicClock() ktime.SampledClock {
return k.timekeeper.monotonicClock
}
// MonotonicRawClock returns the system CLOCK_MONOTONIC_RAW clock. When it is
// not enabled as a distinct clock this is the same as MonotonicClock.
func (k *Kernel) MonotonicRawClock() ktime.SampledClock {
if k.timekeeper.monotonicRawClock != nil {
return k.timekeeper.monotonicRawClock
}
return k.timekeeper.monotonicClock
}
// Syslog returns the syslog.
func (k *Kernel) Syslog() *syslog {
return &k.syslog
+36 -9
View File
@@ -59,6 +59,12 @@ type Timekeeper struct {
// monotonicClock is a ktime.Clock based on timekeeper's Monotonic.
monotonicClock *timekeeperClock
// monotonicRawClock is a ktime.Clock based on timekeeper's MonotonicRaw.
// It is non-nil only when the clock source tracks a distinct
// CLOCK_MONOTONIC_RAW; otherwise CLOCK_MONOTONIC_RAW aliases
// CLOCK_MONOTONIC. It is derived by SetClocks, anew on restore.
monotonicRawClock *timekeeperClock `state:"nosave"`
// bootTime is the realtime when the system "booted". i.e., when
// SetClocks was called in the initial (not restored) run.
bootTime ktime.Time
@@ -158,6 +164,14 @@ func (t *Timekeeper) SetClocks(c sentrytime.Clocks, params *VDSOParamPage) {
t.clocks = c
// Serve CLOCK_MONOTONIC_RAW as a distinct clock iff the clock source
// tracks it (see NewCalibratedClocks). Deriving this from the source keeps
// boot and restore coherent: a restored sandbox follows its current
// configuration, not the checkpointed one.
if c.MonotonicRawEnabled() {
t.monotonicRawClock = &timekeeperClock{tk: t, c: sentrytime.MonotonicRaw}
}
// Compute the offset of the monotonic clock from the base Clocks.
//
// In a fresh (not restored) sentry, monotonic time starts at zero.
@@ -213,20 +227,30 @@ func (t *Timekeeper) update(parked bool) {
// Call Update within a Write block to prevent the VDSO from using the old
// params between Update and Write.
if err := t.params.Write(func() vdsoParams {
monotonicParams, monotonicOk, realtimeParams, realtimeOk := t.clocks.Update(parked)
res := t.clocks.Update(parked)
var p vdsoParams
if monotonicOk {
if res.MonotonicOk {
p.monotonicReady = 1
p.monotonicBaseCycles = int64(monotonicParams.BaseCycles)
p.monotonicBaseRef = int64(monotonicParams.BaseRef) + t.monotonicOffset
p.monotonicFrequency = monotonicParams.Frequency
p.monotonicBaseCycles = int64(res.Monotonic.BaseCycles)
p.monotonicBaseRef = int64(res.Monotonic.BaseRef) + t.monotonicOffset
p.monotonicFrequency = res.Monotonic.Frequency
}
if realtimeOk {
if res.RealtimeOk {
p.realtimeReady = 1
p.realtimeBaseCycles = int64(realtimeParams.BaseCycles)
p.realtimeBaseRef = int64(realtimeParams.BaseRef)
p.realtimeFrequency = realtimeParams.Frequency
p.realtimeBaseCycles = int64(res.Realtime.BaseCycles)
p.realtimeBaseRef = int64(res.Realtime.BaseRef)
p.realtimeFrequency = res.Realtime.Frequency
}
if t.monotonicRawClock == nil {
p.monotonicRawAlias = 1
} else if res.MonotonicRawOk {
// Raw tracks absolute host CLOCK_MONOTONIC_RAW, so unlike
// monotonic its base ref is published without monotonicOffset.
p.monotonicRawReady = 1
p.monotonicRawBaseCycles = int64(res.MonotonicRaw.BaseCycles)
p.monotonicRawBaseRef = int64(res.MonotonicRaw.BaseRef)
p.monotonicRawFrequency = res.MonotonicRaw.Frequency
}
return p
}); err != nil {
@@ -397,6 +421,9 @@ func (t *Timekeeper) GetTime(c sentrytime.ClockID) (int64, error) {
}
<-t.restored
}
if c == sentrytime.MonotonicRaw && t.monotonicRawClock == nil {
c = sentrytime.Monotonic
}
// update the calibration if needed and keep the timekeeper calibrated
// during the read
+58 -5
View File
@@ -28,13 +28,20 @@ import (
// mockClocks is a sentrytime.Clocks that simply returns the times in the
// struct.
type mockClocks struct {
monotonic int64
realtime int64
monotonic int64
realtime int64
monotonicRaw int64
monotonicRawEnabled bool
}
// Update implements sentrytime.Clocks.Update. It does nothing.
func (*mockClocks) Update(parked bool) (monotonicParams sentrytime.Parameters, monotonicOk bool, realtimeParam sentrytime.Parameters, realtimeOk bool) {
return
func (*mockClocks) Update(parked bool) sentrytime.UpdateResult {
return sentrytime.UpdateResult{}
}
// MonotonicRawEnabled implements sentrytime.Clocks.MonotonicRawEnabled.
func (c *mockClocks) MonotonicRawEnabled() bool {
return c.monotonicRawEnabled
}
// GetTime implements sentrytime.Clocks.GetTime.
@@ -44,6 +51,11 @@ func (c *mockClocks) GetTime(id sentrytime.ClockID) (int64, error) {
return c.monotonic, nil
case sentrytime.Realtime:
return c.realtime, nil
case sentrytime.MonotonicRaw:
if c.monotonicRawEnabled {
return c.monotonicRaw, nil
}
return 0, linuxerr.EINVAL
default:
return 0, linuxerr.EINVAL
}
@@ -64,7 +76,7 @@ func stateTestClocklessTimekeeper(tb testing.TB) (*Timekeeper, *VDSOParamPage) {
func stateTestTimekeeper(tb testing.TB) *Timekeeper {
t, params := stateTestClocklessTimekeeper(tb)
t.SetClocks(sentrytime.NewCalibratedClocks(), params)
t.SetClocks(sentrytime.NewCalibratedClocks(false), params)
return t
}
@@ -153,3 +165,44 @@ func TestTimekeeperMonotonicJumpBackwards(t *testing.T) {
t.Errorf("GetTime got %d want 100000", now)
}
}
// TestTimekeeperMonotonicRawEnabled tests that when the clock source tracks a
// distinct CLOCK_MONOTONIC_RAW, GetTime exposes it directly (absolute, not
// starting at zero), while CLOCK_MONOTONIC is unaffected.
func TestTimekeeperMonotonicRawEnabled(t *testing.T) {
c := &mockClocks{
monotonic: 100000,
monotonicRaw: 999999,
monotonicRawEnabled: true,
}
tk, params := stateTestClocklessTimekeeper(t)
tk.SetClocks(c, params)
defer tk.Destroy()
// Monotonic is unaffected: still starts at zero.
if now, err := tk.GetTime(sentrytime.Monotonic); err != nil || now != 0 {
t.Errorf("GetTime(Monotonic) got (%d, %v) want (0, nil)", now, err)
}
// MonotonicRaw exposes the raw source's absolute value.
if now, err := tk.GetTime(sentrytime.MonotonicRaw); err != nil || now != 999999 {
t.Errorf("GetTime(MonotonicRaw) got (%d, %v) want (999999, nil)", now, err)
}
}
// TestTimekeeperMonotonicRawDisabled tests that when the clock source does not
// track CLOCK_MONOTONIC_RAW, it aliases CLOCK_MONOTONIC.
func TestTimekeeperMonotonicRawDisabled(t *testing.T) {
c := &mockClocks{
monotonic: 100000,
}
tk, params := stateTestClocklessTimekeeper(t)
tk.SetClocks(c, params)
defer tk.Destroy()
// MonotonicRaw aliases Monotonic: both start at zero.
if now, err := tk.GetTime(sentrytime.MonotonicRaw); err != nil || now != 0 {
t.Errorf("GetTime(MonotonicRaw) got (%d, %v) want (0, nil)", now, err)
}
}
+6
View File
@@ -40,6 +40,12 @@ type vdsoParams struct {
realtimeBaseCycles int64
realtimeBaseRef int64
realtimeFrequency uint64
monotonicRawAlias uint64
monotonicRawReady uint64
monotonicRawBaseCycles int64
monotonicRawBaseRef int64
monotonicRawFrequency uint64
}
// VDSOParamPage manages a VDSO parameter page.
+3 -2
View File
@@ -123,9 +123,10 @@ func getClock(t *kernel.Task, clockID int32) (ktime.Clock, error) {
switch clockID {
case linux.CLOCK_REALTIME, linux.CLOCK_REALTIME_COARSE:
return t.Kernel().RealtimeClock(), nil
case linux.CLOCK_MONOTONIC_RAW:
return t.Kernel().MonotonicRawClock(), nil
case linux.CLOCK_MONOTONIC, linux.CLOCK_MONOTONIC_COARSE,
linux.CLOCK_MONOTONIC_RAW, linux.CLOCK_BOOTTIME:
// CLOCK_MONOTONIC approximates CLOCK_MONOTONIC_RAW.
linux.CLOCK_BOOTTIME:
// CLOCK_BOOTTIME is internally mapped to CLOCK_MONOTONIC, as:
// - CLOCK_BOOTTIME should behave as CLOCK_MONOTONIC while also
// including suspend time.
+6
View File
@@ -105,6 +105,12 @@ func TimerCreate(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uin
sevp := args[1].Pointer()
timerIDp := args[2].Pointer()
// Linux's clock_monotonic_raw k_clock only defines clock_getres and
// clock_get_timespec, so timer_create(CLOCK_MONOTONIC_RAW) is rejected.
if clockID == linux.CLOCK_MONOTONIC_RAW {
return 0, nil, linuxerr.EOPNOTSUPP
}
c, err := getClock(t, clockID)
if err != nil {
return 0, nil, err
+31 -7
View File
@@ -234,22 +234,41 @@ type CalibratedClocks struct {
// realtime is the realtime equivalent of monotonic.
realtime *CalibratedClock
// monotonicRaw tracks the host CLOCK_MONOTONIC_RAW clock. It is nil unless
// enabled via NewCalibratedClocks.
monotonicRaw *CalibratedClock
}
// NewCalibratedClocks creates a CalibratedClocks.
func NewCalibratedClocks() *CalibratedClocks {
return &CalibratedClocks{
// NewCalibratedClocks creates a CalibratedClocks. If monotonicRawEnabled is
// true, host CLOCK_MONOTONIC_RAW is additionally tracked as a distinct clock
// (addressable as MonotonicRaw).
func NewCalibratedClocks(monotonicRawEnabled bool) *CalibratedClocks {
c := &CalibratedClocks{
monotonic: NewCalibratedClock(Monotonic),
realtime: NewCalibratedClock(Realtime),
}
if monotonicRawEnabled {
c.monotonicRaw = NewCalibratedClock(MonotonicRaw)
}
return c
}
// Update implements Clocks.Update.
func (c *CalibratedClocks) Update(parked bool) (Parameters, bool, Parameters, bool) {
monotonicParams, monotonicOk := c.monotonic.Update(parked)
realtimeParams, realtimeOk := c.realtime.Update(parked)
func (c *CalibratedClocks) Update(parked bool) UpdateResult {
var res UpdateResult
res.Monotonic, res.MonotonicOk = c.monotonic.Update(parked)
res.Realtime, res.RealtimeOk = c.realtime.Update(parked)
if c.monotonicRaw != nil {
res.MonotonicRaw, res.MonotonicRawOk = c.monotonicRaw.Update(parked)
}
return monotonicParams, monotonicOk, realtimeParams, realtimeOk
return res
}
// MonotonicRawEnabled implements Clocks.MonotonicRawEnabled.
func (c *CalibratedClocks) MonotonicRawEnabled() bool {
return c.monotonicRaw != nil
}
// GetTime implements Clocks.GetTime.
@@ -259,6 +278,11 @@ func (c *CalibratedClocks) GetTime(id ClockID) (int64, error) {
return c.monotonic.GetTime()
case Realtime:
return c.realtime.GetTime()
case MonotonicRaw:
if c.monotonicRaw != nil {
return c.monotonicRaw.GetTime()
}
return 0, linuxerr.EINVAL
default:
return 0, linuxerr.EINVAL
}
+5 -2
View File
@@ -23,8 +23,9 @@ type ClockID int32
// These are the supported Linux clock identifiers.
const (
Realtime ClockID = iota
Monotonic
Realtime ClockID = 0
Monotonic ClockID = 1
MonotonicRaw ClockID = 4
)
// String implements fmt.Stringer.String.
@@ -34,6 +35,8 @@ func (c ClockID) String() string {
return "Realtime"
case Monotonic:
return "Monotonic"
case MonotonicRaw:
return "MonotonicRaw"
default:
return strconv.Itoa(int(c))
}
+23 -1
View File
@@ -14,6 +14,21 @@
package time
// UpdateResult holds the timekeeping parameters produced by Clocks.Update.
//
// Each *Ok field reports whether the corresponding *Params were successfully
// calibrated and may be published to the VDSO. MonotonicRaw is only populated
// by clock sources that track a distinct CLOCK_MONOTONIC_RAW; otherwise
// MonotonicRawOk is false.
type UpdateResult struct {
Monotonic Parameters
MonotonicOk bool
Realtime Parameters
RealtimeOk bool
MonotonicRaw Parameters
MonotonicRawOk bool
}
// Clocks represents a clock source that contains both a monotonic and realtime
// clock.
type Clocks interface {
@@ -23,11 +38,18 @@ type Clocks interface {
// Update should be called at approximately ApproxUpdateInterval.
//
// parked indicates that the clock was not read for at least ApproxUpdateInterval
Update(parked bool) (monotonicParams Parameters, monotonicOk bool, realtimeParam Parameters, realtimeOk bool)
Update(parked bool) UpdateResult
// GetTime returns the current time in nanoseconds for the given clock.
//
// Clocks implementations must support at least Monotonic and
// Realtime.
GetTime(c ClockID) (int64, error)
// MonotonicRawEnabled reports whether this clock source tracks a distinct
// CLOCK_MONOTONIC_RAW, i.e. whether GetTime(MonotonicRaw) returns the
// host's absolute CLOCK_MONOTONIC_RAW rather than aliasing Monotonic.
//
// It must return the same value for the lifetime of the Clocks.
MonotonicRawEnabled() bool
}
+18 -1
View File
@@ -545,6 +545,23 @@ func getRootCredentials(spec *specs.Spec, conf *config.Config, userNs *auth.User
return creds
}
// shouldEnableClockMonotonicRaw reports whether CLOCK_MONOTONIC_RAW should be
// exposed as a distinct clock tracking the host's CLOCK_MONOTONIC_RAW, rather
// than aliasing CLOCK_MONOTONIC as it does by default.
//
// This exists for GPU profiling: profilers such as Nsight Systems/CUPTI anchor
// the GPU timeline in the host's CLOCK_MONOTONIC_RAW domain, which drifts from
// CLOCK_MONOTONIC by NTP frequency adjustment. When nvproxy grants
// CapProfiling, the sandbox must therefore serve a CLOCK_MONOTONIC_RAW in that
// same (absolute, unadjusted) domain. It is enabled only in that case.
func shouldEnableClockMonotonicRaw(spec *specs.Spec, conf *config.Config) bool {
if !specutils.NVProxyEnabled(spec, conf) {
return false
}
caps, err := specutils.NVProxyDriverCapsAllowed(conf)
return err == nil && caps&nvconf.CapProfiling != 0
}
// New initializes a new kernel loader configured by spec.
// New also handles setting up a kernel for restoring a container.
func New(args Args) (*Loader, error) {
@@ -733,7 +750,7 @@ func New(args Args) (*Loader, error) {
// Create timekeeper.
tk := kernel.NewTimekeeper()
params := kernel.NewVDSOParamPage(l.k.MemoryFile(), vdso.ParamPage.FileRange())
tk.SetClocks(time.NewCalibratedClocks(), params)
tk.SetClocks(time.NewCalibratedClocks(shouldEnableClockMonotonicRaw(args.Spec, args.Conf)), params)
args.StartupTimer.Reached("timekeeper configured")
if err := enableStrace(args.Conf); err != nil {
+3 -2
View File
@@ -479,15 +479,16 @@ func (r *restorer) restore(l *Loader) error {
}
// Load the state.
clocks := time.NewCalibratedClocks(shouldEnableClockMonotonicRaw(l.root.spec, l.root.conf))
r.timer.Reached("loading kernel")
if r.extractRootFsMode {
if err := l.k.ExtractRootfsUpperLayer(ctx, r.stateFile, r.asyncMFLoader, nil, time.NewCalibratedClocks(), r.rootFsOutputTar); err != nil {
if err := l.k.ExtractRootfsUpperLayer(ctx, r.stateFile, r.asyncMFLoader, nil, clocks, r.rootFsOutputTar); err != nil {
return fmt.Errorf("failed to extract rootfs upper layer: %w", err)
}
r.timer.Reached("rootfs upper layer extracted")
return nil
}
if err := l.k.LoadFrom(ctx, r.stateFile, r.asyncMFLoader, nil, l, time.NewCalibratedClocks(), &vfs.CompleteRestoreOptions{}, r.timer.Fork("kernel load")); err != nil {
if err := l.k.LoadFrom(ctx, r.stateFile, r.asyncMFLoader, nil, l, clocks, &vfs.CompleteRestoreOptions{}, r.timer.Fork("kernel load")); err != nil {
return fmt.Errorf("failed to load kernel: %w", err)
}
r.timer.Reached("kernel loaded")
+4 -2
View File
@@ -35,10 +35,12 @@ int __common_clock_gettime(clockid_t clock, struct timespec* ts) {
ret = ClockRealtime(ts);
break;
case CLOCK_MONOTONIC_RAW:
ret = ClockMonotonicRaw(ts);
break;
case CLOCK_BOOTTIME:
// Fallthrough, CLOCK_BOOTTIME is an alias for CLOCK_MONOTONIC
case CLOCK_MONOTONIC_RAW:
// Fallthrough, CLOCK_MONOTONIC_RAW is an alias for CLOCK_MONOTONIC
case CLOCK_MONOTONIC_COARSE:
// Fallthrough, CLOCK_MONOTONIC_COARSE is an alias for CLOCK_MONOTONIC
case CLOCK_MONOTONIC:
+60 -8
View File
@@ -41,6 +41,12 @@ struct params {
int64_t realtime_base_cycles;
int64_t realtime_base_ref;
uint64_t realtime_frequency;
uint64_t monotonic_raw_alias;
uint64_t monotonic_raw_ready;
int64_t monotonic_raw_base_cycles;
int64_t monotonic_raw_base_ref;
uint64_t monotonic_raw_frequency;
};
// Returns a pointer to the global parameter page.
@@ -91,6 +97,14 @@ inline uint64_t cycles_to_ns(uint64_t frequency, uint64_t cycles) {
return ((unsigned __int128)cycles * mult) >> 32;
}
// compute_time projects a calibrated clock's base to now_cycles.
inline int64_t compute_time(int64_t base_ref, int64_t base_cycles,
uint64_t frequency, int64_t now_cycles) {
int64_t delta_cycles =
(now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
return base_ref + cycles_to_ns(frequency, delta_cycles);
}
// ClockRealtime() is the VDSO implementation of clock_gettime(CLOCK_REALTIME).
int ClockRealtime(struct timespec* ts) {
struct params* params = get_params();
@@ -116,10 +130,8 @@ int ClockRealtime(struct timespec* ts) {
return sys_clock_gettime(CLOCK_REALTIME, ts);
}
int64_t delta_cycles =
(now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles);
*ts = ns_to_timespec(now_ns);
*ts = ns_to_timespec(
compute_time(base_ref, base_cycles, frequency, now_cycles));
return 0;
}
@@ -149,10 +161,50 @@ int ClockMonotonic(struct timespec* ts) {
return sys_clock_gettime(CLOCK_MONOTONIC, ts);
}
int64_t delta_cycles =
(now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles);
*ts = ns_to_timespec(now_ns);
*ts = ns_to_timespec(
compute_time(base_ref, base_cycles, frequency, now_cycles));
return 0;
}
// ClockMonotonicRaw() is the VDSO implementation of
// clock_gettime(CLOCK_MONOTONIC_RAW).
//
// When aliasing, it serves CLOCK_MONOTONIC in the VDSO. Otherwise, if the raw
// params are not yet calibrated, it falls back to a syscall for the brief
// startup/restore window; otherwise it is computed in the VDSO from the raw
// params.
int ClockMonotonicRaw(struct timespec* ts) {
struct params* params = get_params();
uint64_t seq;
uint64_t alias;
uint64_t ready;
int64_t base_ref;
int64_t base_cycles;
uint64_t frequency;
int64_t now_cycles;
do {
seq = read_seqcount_begin(&params->seq_count);
alias = params->monotonic_raw_alias;
ready = params->monotonic_raw_ready;
base_ref = params->monotonic_raw_base_ref;
base_cycles = params->monotonic_raw_base_cycles;
frequency = params->monotonic_raw_frequency;
now_cycles = cycle_clock();
} while (read_seqcount_retry(&params->seq_count, seq));
if (alias) {
// CLOCK_MONOTONIC_RAW aliases CLOCK_MONOTONIC, served in the VDSO.
return ClockMonotonic(ts);
}
if (!ready) {
// The sandbox kernel ensures that we won't compute a time later than this
// once the params are ready.
return sys_clock_gettime(CLOCK_MONOTONIC_RAW, ts);
}
*ts = ns_to_timespec(
compute_time(base_ref, base_cycles, frequency, now_cycles));
return 0;
}
+1
View File
@@ -21,6 +21,7 @@ namespace vdso {
int ClockRealtime(struct timespec* ts);
int ClockMonotonic(struct timespec* ts);
int ClockMonotonicRaw(struct timespec* ts);
} // namespace vdso