summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/time/parameters.go
blob: 594b4874b967287438554c2790a3b0ef3681518d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package time

import (
	"fmt"
	"time"

	"gvisor.googlesource.com/gvisor/pkg/log"
)

const (
	// ApproxUpdateInterval is the approximate interval that parameters
	// should be updated at.
	//
	// Error correction assumes that the next update will occur after this
	// much time.
	//
	// If an update occurs before ApproxUpdateInterval passes, it has no
	// adverse effect on error correction behavior.
	//
	// If an update occurs after ApproxUpdateInterval passes, the clock
	// will overshoot its error correction target and begin accumulating
	// error in the other direction.
	//
	// If updates occur after more than 2*ApproxUpdateInterval passes, the
	// clock becomes unstable, accumulating more error than it had
	// originally. Repeated updates after more than 2*ApproxUpdateInterval
	// will cause unbounded increases in error.
	//
	// These statements assume that the host clock does not change. Actual
	// error will depend upon host clock changes.
	//
	// TODO: make error correction more robust to delayed
	// updates.
	ApproxUpdateInterval = 1 * time.Second

	// MaxClockError is the maximum amount of error that the clocks will
	// try to correct.
	//
	// This limit:
	//
	//  * Puts a limit on cases of otherwise unbounded increases in error.
	//
	//  * Avoids unreasonably large frequency adjustments required to
	//    correct large errors over a single update interval.
	MaxClockError = ReferenceNS(ApproxUpdateInterval) / 4
)

// Parameters are the timekeeping parameters needed to compute the current
// time.
type Parameters struct {
	// BaseCycles was the TSC counter value when the time was BaseRef.
	BaseCycles TSCValue

	// BaseRef is the reference clock time in nanoseconds corresponding to
	// BaseCycles.
	BaseRef ReferenceNS

	// Frequency is the frequency of the cycle clock in Hertz.
	Frequency uint64
}

// muldiv64 multiplies two 64-bit numbers, then divides the result by another
// 64-bit number.
//
// It requires that the result fit in 64 bits, but doesn't require that
// intermediate values do; in particular, the result of the multiplication may
// require 128 bits.
//
// It returns !ok if divisor is zero or the result does not fit in 64 bits.
func muldiv64(value, multiplier, divisor uint64) (uint64, bool)

// ComputeTime calculates the current time from a "now" TSC value.
//
// time = ref + (now - base) / f
func (p Parameters) ComputeTime(nowCycles TSCValue) (int64, bool) {
	diffCycles := nowCycles - p.BaseCycles
	if diffCycles < 0 {
		log.Warningf("now cycles %v < base cycles %v", nowCycles, p.BaseCycles)
		diffCycles = 0
	}

	// Overflow "won't ever happen". If diffCycles is the max value
	// (2^63 - 1), then to overflow,
	//
	// frequency <= ((2^63 - 1) * 10^9) / 2^64 = 500Mhz
	//
	// A TSC running at 2GHz takes 201 years to reach 2^63-1. 805 years at
	// 500MHz.
	diffNS, ok := muldiv64(uint64(diffCycles), uint64(time.Second.Nanoseconds()), p.Frequency)
	return int64(uint64(p.BaseRef) + diffNS), ok
}

// errorAdjust returns a new Parameters struct "adjusted" that satisfies:
//
// 1. adjusted.ComputeTime(now) = prevParams.ComputeTime(now)
//   * i.e., the current time does not jump.
//
// 2. adjusted.ComputeTime(TSC at next update) = newParams.ComputeTime(TSC at next update)
//   * i.e., Any error between prevParams and newParams will be corrected over
//     the course of the next update period.
//
// errorAdjust also returns the current clock error.
//
// Preconditions:
// * newParams.BaseCycles >= prevParams.BaseCycles; i.e., TSC must not go
//   backwards.
// * newParams.BaseCycles <= now; i.e., the new parameters be computed at or
//   before now.
func errorAdjust(prevParams Parameters, newParams Parameters, now TSCValue) (Parameters, ReferenceNS, error) {
	if newParams.BaseCycles < prevParams.BaseCycles {
		// Oh dear! Something is very wrong.
		return Parameters{}, 0, fmt.Errorf("TSC went backwards in updated clock params: %v < %v", newParams.BaseCycles, prevParams.BaseCycles)
	}
	if newParams.BaseCycles > now {
		return Parameters{}, 0, fmt.Errorf("parameters contain base cycles later than now: %v > %v", newParams.BaseCycles, now)
	}

	intervalNS := int64(ApproxUpdateInterval.Nanoseconds())
	nsPerSec := uint64(time.Second.Nanoseconds())

	// Current time as computed by prevParams.
	oldNowNS, ok := prevParams.ComputeTime(now)
	if !ok {
		return Parameters{}, 0, fmt.Errorf("old now time computation overflowed. params = %+v, now = %v", prevParams, now)
	}

	// We expect the update ticker to run based on this clock (i.e., it has
	// been using prevParams and will use the returned adjusted
	// parameters). Hence it will decide to fire intervalNS from the
	// current (oldNowNS) "now".
	nextNS := oldNowNS + intervalNS

	if nextNS <= int64(newParams.BaseRef) {
		// The next update time already passed before the new
		// parameters were created! We definitely can't correct the
		// error by then.
		return Parameters{}, 0, fmt.Errorf("unable to correct error in single period. oldNowNS = %v, nextNS = %v, p = %v", oldNowNS, nextNS, newParams)
	}

	// For what TSC value next will newParams.ComputeTime(next) = nextNS?
	//
	// Solve ComputeTime for next:
	//
	// next = newParams.Frequency * (nextNS - newParams.BaseRef) + newParams.BaseCycles
	c, ok := muldiv64(newParams.Frequency, uint64(nextNS-int64(newParams.BaseRef)), nsPerSec)
	if !ok {
		return Parameters{}, 0, fmt.Errorf("%v * (%v - %v) / %v overflows", newParams.Frequency, nextNS, newParams.BaseRef, nsPerSec)
	}

	cycles := TSCValue(c)
	next := cycles + newParams.BaseCycles

	if next <= now {
		// The next update time already passed now with the new
		// parameters! We can't correct the error in a single period.
		return Parameters{}, 0, fmt.Errorf("unable to correct error in single period. oldNowNS = %v, nextNS = %v, now = %v, next = %v", oldNowNS, nextNS, now, next)
	}

	// We want to solve for parameters that satisfy:
	//
	// adjusted.ComputeTime(now) = oldNowNS
	//
	// adjusted.ComputeTime(next) = nextNS
	//
	// i.e., the current time does not change, but by the time we reach
	// next we reach the same time as newParams.

	// We choose to keep BaseCycles fixed.
	adjusted := Parameters{
		BaseCycles: newParams.BaseCycles,
	}

	// We want a slope such that time goes from oldNowNS to nextNS when
	// we reach next.
	//
	// In other words, cycles should increase by next - now in the next
	// interval.

	cycles = next - now
	ns := intervalNS

	// adjusted.Frequency = cycles / ns
	adjusted.Frequency, ok = muldiv64(uint64(cycles), nsPerSec, uint64(ns))
	if !ok {
		return Parameters{}, 0, fmt.Errorf("(%v - %v) * %v / %v overflows", next, now, nsPerSec, ns)
	}

	// Now choose a base reference such that the current time remains the
	// same. Note that this is just ComputeTime, solving for BaseRef:
	//
	// oldNowNS = BaseRef + (now - BaseCycles) / Frequency
	// BaseRef = oldNowNS - (now - BaseCycles) / Frequency
	diffNS, ok := muldiv64(uint64(now-adjusted.BaseCycles), nsPerSec, adjusted.Frequency)
	if !ok {
		return Parameters{}, 0, fmt.Errorf("(%v - %v) * %v / %v overflows", now, adjusted.BaseCycles, nsPerSec, adjusted.Frequency)
	}

	adjusted.BaseRef = ReferenceNS(oldNowNS - int64(diffNS))

	// The error is the difference between the current time and what the
	// new parameters say the current time should be.
	newNowNS, ok := newParams.ComputeTime(now)
	if !ok {
		return Parameters{}, 0, fmt.Errorf("new now time computation overflowed. params = %+v, now = %v", newParams, now)
	}

	errorNS := ReferenceNS(oldNowNS - newNowNS)

	return adjusted, errorNS, nil
}

// logErrorAdjustment logs the clock error and associated error correction
// frequency adjustment.
//
// The log level is determined by the error severity.
func logErrorAdjustment(clock ClockID, errorNS ReferenceNS, orig, adjusted Parameters) {
	fn := log.Debugf
	if int64(errorNS.Magnitude()) > time.Millisecond.Nanoseconds() {
		fn = log.Warningf
	} else if int64(errorNS.Magnitude()) > 10*time.Microsecond.Nanoseconds() {
		fn = log.Infof
	}

	fn("Clock(%v): error: %v ns, adjusted frequency from %v Hz to %v Hz", clock, errorNS, orig.Frequency, adjusted.Frequency)
}