gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/time/parameters_test.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package time
    16  
    17  import (
    18  	"math"
    19  	"testing"
    20  	"time"
    21  )
    22  
    23  func TestParametersComputeTime(t *testing.T) {
    24  	testCases := []struct {
    25  		name   string
    26  		params Parameters
    27  		now    TSCValue
    28  		want   int64
    29  	}{
    30  		{
    31  			// Now is the same as the base cycles.
    32  			name: "base-cycles",
    33  			params: Parameters{
    34  				BaseCycles: 10000,
    35  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
    36  				Frequency:  10000,
    37  			},
    38  			now:  10000,
    39  			want: 5000 * time.Millisecond.Nanoseconds(),
    40  		},
    41  		{
    42  			// Now is the behind the base cycles. Time is frozen.
    43  			name: "backwards",
    44  			params: Parameters{
    45  				BaseCycles: 10000,
    46  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
    47  				Frequency:  10000,
    48  			},
    49  			now:  9000,
    50  			want: 5000 * time.Millisecond.Nanoseconds(),
    51  		},
    52  		{
    53  			// Now is ahead of the base cycles.
    54  			name: "ahead",
    55  			params: Parameters{
    56  				BaseCycles: 10000,
    57  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
    58  				Frequency:  10000,
    59  			},
    60  			now:  15000,
    61  			want: 5500 * time.Millisecond.Nanoseconds(),
    62  		},
    63  	}
    64  	for _, tc := range testCases {
    65  		t.Run(tc.name, func(t *testing.T) {
    66  			got, ok := tc.params.ComputeTime(tc.now)
    67  			if !ok {
    68  				t.Errorf("ComputeTime ok got %v want true", got)
    69  			}
    70  			if got != tc.want {
    71  				t.Errorf("ComputeTime got %+v want %+v", got, tc.want)
    72  			}
    73  		})
    74  	}
    75  }
    76  
    77  func TestParametersErrorAdjust(t *testing.T) {
    78  	testCases := []struct {
    79  		name      string
    80  		oldParams Parameters
    81  		now       TSCValue
    82  		newParams Parameters
    83  		want      Parameters
    84  		errorNS   ReferenceNS
    85  		wantErr   bool
    86  	}{
    87  		{
    88  			// newParams are perfectly continuous with oldParams
    89  			// and don't need adjustment.
    90  			name: "continuous",
    91  			oldParams: Parameters{
    92  				BaseCycles: 0,
    93  				BaseRef:    0,
    94  				Frequency:  10000,
    95  			},
    96  			now: 50000,
    97  			newParams: Parameters{
    98  				BaseCycles: 50000,
    99  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   100  				Frequency:  10000,
   101  			},
   102  			want: Parameters{
   103  				BaseCycles: 50000,
   104  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   105  				Frequency:  10000,
   106  			},
   107  		},
   108  		{
   109  			// Same as "continuous", but with now ahead of
   110  			// newParams.BaseCycles. The result is the same as
   111  			// there is no error to correct.
   112  			name: "continuous-nowdiff",
   113  			oldParams: Parameters{
   114  				BaseCycles: 0,
   115  				BaseRef:    0,
   116  				Frequency:  10000,
   117  			},
   118  			now: 60000,
   119  			newParams: Parameters{
   120  				BaseCycles: 50000,
   121  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   122  				Frequency:  10000,
   123  			},
   124  			want: Parameters{
   125  				BaseCycles: 50000,
   126  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   127  				Frequency:  10000,
   128  			},
   129  		},
   130  		{
   131  			// errorAdjust bails out if the TSC goes backwards.
   132  			name: "tsc-backwards",
   133  			oldParams: Parameters{
   134  				BaseCycles: 10000,
   135  				BaseRef:    ReferenceNS(1000 * time.Millisecond.Nanoseconds()),
   136  				Frequency:  10000,
   137  			},
   138  			now: 9000,
   139  			newParams: Parameters{
   140  				BaseCycles: 9000,
   141  				BaseRef:    ReferenceNS(1100 * time.Millisecond.Nanoseconds()),
   142  				Frequency:  10000,
   143  			},
   144  			wantErr: true,
   145  		},
   146  		{
   147  			// errorAdjust bails out if new params are from after now.
   148  			name: "params-after-now",
   149  			oldParams: Parameters{
   150  				BaseCycles: 10000,
   151  				BaseRef:    ReferenceNS(1000 * time.Millisecond.Nanoseconds()),
   152  				Frequency:  10000,
   153  			},
   154  			now: 11000,
   155  			newParams: Parameters{
   156  				BaseCycles: 12000,
   157  				BaseRef:    ReferenceNS(1200 * time.Millisecond.Nanoseconds()),
   158  				Frequency:  10000,
   159  			},
   160  			wantErr: true,
   161  		},
   162  		{
   163  			// Host clock sped up.
   164  			name: "speed-up",
   165  			oldParams: Parameters{
   166  				BaseCycles: 0,
   167  				BaseRef:    0,
   168  				Frequency:  10000,
   169  			},
   170  			now: 45000,
   171  			// Host frequency changed to 9000 immediately after
   172  			// oldParams was returned.
   173  			newParams: Parameters{
   174  				BaseCycles: 45000,
   175  				// From oldParams, we think ref = 4.5s at cycles = 45000.
   176  				BaseRef:   ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   177  				Frequency: 9000,
   178  			},
   179  			want: Parameters{
   180  				BaseCycles: 45000,
   181  				BaseRef:    ReferenceNS(4500 * time.Millisecond.Nanoseconds()),
   182  				// We must decrease the new frequency by 50% to
   183  				// correct 0.5s of error in 1s
   184  				// (ApproxUpdateInterval).
   185  				Frequency: 4500,
   186  			},
   187  			errorNS: ReferenceNS(-500 * time.Millisecond.Nanoseconds()),
   188  		},
   189  		{
   190  			// Host clock sped up, with now ahead of newParams.
   191  			name: "speed-up-nowdiff",
   192  			oldParams: Parameters{
   193  				BaseCycles: 0,
   194  				BaseRef:    0,
   195  				Frequency:  10000,
   196  			},
   197  			now: 50000,
   198  			// Host frequency changed to 9000 immediately after
   199  			// oldParams was returned.
   200  			newParams: Parameters{
   201  				BaseCycles: 45000,
   202  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   203  				Frequency:  9000,
   204  			},
   205  			// nextRef = 6000ms
   206  			// nextCycles = 9000 * (6000ms - 5000ms) + 45000
   207  			// nextCycles = 9000 * (1s) + 45000
   208  			// nextCycles = 54000
   209  			// f = (54000 - 50000) / 1s = 4000
   210  			//
   211  			// ref = 5000ms - (50000 - 45000) / 4000
   212  			// ref = 3.75s
   213  			want: Parameters{
   214  				BaseCycles: 45000,
   215  				BaseRef:    ReferenceNS(3750 * time.Millisecond.Nanoseconds()),
   216  				Frequency:  4000,
   217  			},
   218  			// oldNow = 50000 * 10000 = 5s
   219  			// newNow = (50000 - 45000) / 9000 + 5s = 5.555s
   220  			errorNS: ReferenceNS((5000*time.Millisecond - 5555555555).Nanoseconds()),
   221  		},
   222  		{
   223  			// Host clock sped up. The new parameters are so far
   224  			// ahead that the next update time already passed.
   225  			name: "speed-up-uncorrectable-baseref",
   226  			oldParams: Parameters{
   227  				BaseCycles: 0,
   228  				BaseRef:    0,
   229  				Frequency:  10000,
   230  			},
   231  			now: 50000,
   232  			// Host frequency changed to 5000 immediately after
   233  			// oldParams was returned.
   234  			newParams: Parameters{
   235  				BaseCycles: 45000,
   236  				BaseRef:    ReferenceNS(9000 * time.Millisecond.Nanoseconds()),
   237  				Frequency:  5000,
   238  			},
   239  			// The next update should be at 10s, but newParams
   240  			// already passed 6s.  Thus it is impossible to correct
   241  			// the clock by then.
   242  			wantErr: true,
   243  		},
   244  		{
   245  			// Host clock sped up. The new parameters are moving so
   246  			// fast that the next update should be before now.
   247  			name: "speed-up-uncorrectable-frequency",
   248  			oldParams: Parameters{
   249  				BaseCycles: 0,
   250  				BaseRef:    0,
   251  				Frequency:  10000,
   252  			},
   253  			now: 55000,
   254  			// Host frequency changed to 7500 immediately after
   255  			// oldParams was returned.
   256  			newParams: Parameters{
   257  				BaseCycles: 45000,
   258  				BaseRef:    ReferenceNS(6000 * time.Millisecond.Nanoseconds()),
   259  				Frequency:  7500,
   260  			},
   261  			// The next update should be at 6.5s, but newParams are
   262  			// so far ahead and fast that they reach 6.5s at cycle
   263  			// 48750, which before now! Thus it is impossible to
   264  			// correct the clock by then.
   265  			wantErr: true,
   266  		},
   267  		{
   268  			// Host clock slowed down.
   269  			name: "slow-down",
   270  			oldParams: Parameters{
   271  				BaseCycles: 0,
   272  				BaseRef:    0,
   273  				Frequency:  10000,
   274  			},
   275  			now: 55000,
   276  			// Host frequency changed to 11000 immediately after
   277  			// oldParams was returned.
   278  			newParams: Parameters{
   279  				BaseCycles: 55000,
   280  				// From oldParams, we think ref = 5.5s at cycles = 55000.
   281  				BaseRef:   ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   282  				Frequency: 11000,
   283  			},
   284  			want: Parameters{
   285  				BaseCycles: 55000,
   286  				BaseRef:    ReferenceNS(5500 * time.Millisecond.Nanoseconds()),
   287  				// We must increase the new frequency by 50% to
   288  				// correct 0.5s of error in 1s
   289  				// (ApproxUpdateInterval).
   290  				Frequency: 16500,
   291  			},
   292  			errorNS: ReferenceNS(500 * time.Millisecond.Nanoseconds()),
   293  		},
   294  		{
   295  			// Host clock slowed down, with now ahead of newParams.
   296  			name: "slow-down-nowdiff",
   297  			oldParams: Parameters{
   298  				BaseCycles: 0,
   299  				BaseRef:    0,
   300  				Frequency:  10000,
   301  			},
   302  			now: 60000,
   303  			// Host frequency changed to 11000 immediately after
   304  			// oldParams was returned.
   305  			newParams: Parameters{
   306  				BaseCycles: 55000,
   307  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   308  				Frequency:  11000,
   309  			},
   310  			// nextRef = 7000ms
   311  			// nextCycles = 11000 * (7000ms - 5000ms) + 55000
   312  			// nextCycles = 11000 * (2000ms) + 55000
   313  			// nextCycles = 77000
   314  			// f = (77000 - 60000) / 1s = 17000
   315  			//
   316  			// ref = 6000ms - (60000 - 55000) / 17000
   317  			// ref = 5705882353ns
   318  			want: Parameters{
   319  				BaseCycles: 55000,
   320  				BaseRef:    ReferenceNS(5705882353),
   321  				Frequency:  17000,
   322  			},
   323  			// oldNow = 60000 * 10000 = 6s
   324  			// newNow = (60000 - 55000) / 11000 + 5s = 5.4545s
   325  			errorNS: ReferenceNS((6*time.Second - 5454545454).Nanoseconds()),
   326  		},
   327  		{
   328  			// Host time went backwards.
   329  			name: "time-backwards",
   330  			oldParams: Parameters{
   331  				BaseCycles: 50000,
   332  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   333  				Frequency:  10000,
   334  			},
   335  			now: 60000,
   336  			newParams: Parameters{
   337  				BaseCycles: 60000,
   338  				// From oldParams, we think ref = 6s at cycles = 60000.
   339  				BaseRef:   ReferenceNS(4000 * time.Millisecond.Nanoseconds()),
   340  				Frequency: 10000,
   341  			},
   342  			want: Parameters{
   343  				BaseCycles: 60000,
   344  				BaseRef:    ReferenceNS(6000 * time.Millisecond.Nanoseconds()),
   345  				// We must increase the frequency by 200% to
   346  				// correct 2s of error in 1s
   347  				// (ApproxUpdateInterval).
   348  				Frequency: 30000,
   349  			},
   350  			errorNS: ReferenceNS(2000 * time.Millisecond.Nanoseconds()),
   351  		},
   352  		{
   353  			// Host time went backwards, with now ahead of newParams.
   354  			name: "time-backwards-nowdiff",
   355  			oldParams: Parameters{
   356  				BaseCycles: 50000,
   357  				BaseRef:    ReferenceNS(5000 * time.Millisecond.Nanoseconds()),
   358  				Frequency:  10000,
   359  			},
   360  			now: 65000,
   361  			// nextRef = 7500ms
   362  			// nextCycles = 10000 * (7500ms - 4000ms) + 60000
   363  			// nextCycles = 10000 * (3500ms) + 60000
   364  			// nextCycles = 95000
   365  			// f = (95000 - 65000) / 1s = 30000
   366  			//
   367  			// ref = 6500ms - (65000 - 60000) / 30000
   368  			// ref = 6333333333ns
   369  			newParams: Parameters{
   370  				BaseCycles: 60000,
   371  				BaseRef:    ReferenceNS(4000 * time.Millisecond.Nanoseconds()),
   372  				Frequency:  10000,
   373  			},
   374  			want: Parameters{
   375  				BaseCycles: 60000,
   376  				BaseRef:    ReferenceNS(6333333334),
   377  				Frequency:  30000,
   378  			},
   379  			// oldNow = 65000 * 10000 = 6.5s
   380  			// newNow = (65000 - 60000) / 10000 + 4s = 4.5s
   381  			errorNS: ReferenceNS(2000 * time.Millisecond.Nanoseconds()),
   382  		},
   383  	}
   384  	for _, tc := range testCases {
   385  		t.Run(tc.name, func(t *testing.T) {
   386  			got, errorNS, err := errorAdjust(tc.oldParams, tc.newParams, tc.now)
   387  			if err != nil && !tc.wantErr {
   388  				t.Errorf("err got %v want nil", err)
   389  			} else if err == nil && tc.wantErr {
   390  				t.Errorf("err got nil want non-nil")
   391  			}
   392  
   393  			if got != tc.want {
   394  				t.Errorf("Parameters got %+v want %+v", got, tc.want)
   395  			}
   396  			if errorNS != tc.errorNS {
   397  				t.Errorf("errorNS got %v want %v", errorNS, tc.errorNS)
   398  			}
   399  		})
   400  	}
   401  }
   402  
   403  func testMuldiv(t *testing.T, v uint64) {
   404  	for i := uint64(1); i <= 1000000; i++ {
   405  		mult := uint64(1000000000)
   406  		div := i * mult
   407  		res, ok := muldiv64(v, mult, div)
   408  		if !ok {
   409  			t.Errorf("Result of %v * %v / %v ok got false want true", v, mult, div)
   410  		}
   411  		if want := v / i; res != want {
   412  			t.Errorf("Bad result of %v * %v / %v: got %v, want %v", v, mult, div, res, want)
   413  		}
   414  	}
   415  }
   416  
   417  func TestMulDiv(t *testing.T) {
   418  	testMuldiv(t, math.MaxUint64)
   419  	for i := int64(-10); i <= 10; i++ {
   420  		testMuldiv(t, uint64(i))
   421  	}
   422  }
   423  
   424  func TestMulDivZero(t *testing.T) {
   425  	if r, ok := muldiv64(2, 4, 0); ok {
   426  		t.Errorf("muldiv64(2, 4, 0) got %d, ok want !ok", r)
   427  	}
   428  
   429  	if r, ok := muldiv64(0, 0, 0); ok {
   430  		t.Errorf("muldiv64(0, 0, 0) got %d, ok want !ok", r)
   431  	}
   432  }
   433  
   434  func TestMulDivOverflow(t *testing.T) {
   435  	testCases := []struct {
   436  		name string
   437  		val  uint64
   438  		mult uint64
   439  		div  uint64
   440  		ok   bool
   441  		ret  uint64
   442  	}{
   443  		{
   444  			name: "2^62",
   445  			val:  1 << 63,
   446  			mult: 4,
   447  			div:  8,
   448  			ok:   true,
   449  			ret:  1 << 62,
   450  		},
   451  		{
   452  			name: "2^64-1",
   453  			val:  0xffffffffffffffff,
   454  			mult: 1,
   455  			div:  1,
   456  			ok:   true,
   457  			ret:  0xffffffffffffffff,
   458  		},
   459  		{
   460  			name: "2^64",
   461  			val:  1 << 63,
   462  			mult: 4,
   463  			div:  2,
   464  			ok:   false,
   465  		},
   466  		{
   467  			name: "2^125",
   468  			val:  1 << 63,
   469  			mult: 1 << 63,
   470  			div:  2,
   471  			ok:   false,
   472  		},
   473  	}
   474  
   475  	for _, tc := range testCases {
   476  		t.Run(tc.name, func(t *testing.T) {
   477  			r, ok := muldiv64(tc.val, tc.mult, tc.div)
   478  			if ok != tc.ok {
   479  				t.Errorf("ok got %v want %v", ok, tc.ok)
   480  			}
   481  			if tc.ok && r != tc.ret {
   482  				t.Errorf("ret got %v want %v", r, tc.ret)
   483  			}
   484  		})
   485  	}
   486  }
   487  
   488  func BenchmarkMuldiv64(b *testing.B) {
   489  	var v uint64 = math.MaxUint64
   490  	for i := uint64(1); i <= 1000000; i++ {
   491  		mult := uint64(1000000000)
   492  		div := i * mult
   493  		res, ok := muldiv64(v, mult, div)
   494  		if !ok {
   495  			b.Errorf("Result of %v * %v / %v ok got false want true", v, mult, div)
   496  		}
   497  		if want := v / i; res != want {
   498  			b.Errorf("Bad result of %v * %v / %v: got %v, want %v", v, mult, div, res, want)
   499  		}
   500  	}
   501  }