gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/time/sampler_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  	"errors"
    19  	"testing"
    20  )
    21  
    22  // errNoSamples is returned when testReferenceClocks runs out of samples.
    23  var errNoSamples = errors.New("no samples available")
    24  
    25  // testReferenceClocks returns a preset list of samples and cycle counts.
    26  type testReferenceClocks struct {
    27  	samples []sample
    28  	cycles  []TSCValue
    29  }
    30  
    31  // Sample implements referenceClocks.Sample, returning the next sample in the list.
    32  func (t *testReferenceClocks) Sample(_ ClockID) (sample, error) {
    33  	if len(t.samples) == 0 {
    34  		return sample{}, errNoSamples
    35  	}
    36  
    37  	s := t.samples[0]
    38  	if len(t.samples) == 1 {
    39  		t.samples = nil
    40  	} else {
    41  		t.samples = t.samples[1:]
    42  	}
    43  
    44  	return s, nil
    45  }
    46  
    47  // Cycles implements referenceClocks.Cycles, returning the next TSCValue in the list.
    48  func (t *testReferenceClocks) Cycles() TSCValue {
    49  	if len(t.cycles) == 0 {
    50  		return 0
    51  	}
    52  
    53  	c := t.cycles[0]
    54  	if len(t.cycles) == 1 {
    55  		t.cycles = nil
    56  	} else {
    57  		t.cycles = t.cycles[1:]
    58  	}
    59  
    60  	return c
    61  }
    62  
    63  // newTestSampler returns a sampler that collects samples from
    64  // the given sample list and cycle counts from the given cycle list.
    65  func newTestSampler(samples []sample, cycles []TSCValue) *sampler {
    66  	return &sampler{
    67  		clocks: &testReferenceClocks{
    68  			samples: samples,
    69  			cycles:  cycles,
    70  		},
    71  		overhead: defaultOverheadCycles,
    72  	}
    73  }
    74  
    75  // generateSamples generates n samples with the given overhead.
    76  func generateSamples(n int, overhead TSCValue) []sample {
    77  	samples := []sample{{before: 1000000, after: 1000000 + overhead, ref: 100}}
    78  	for i := 0; i < n-1; i++ {
    79  		prev := samples[len(samples)-1]
    80  		samples = append(samples, sample{
    81  			before: prev.before + 1000000,
    82  			after:  prev.after + 1000000,
    83  			ref:    prev.ref + 100,
    84  		})
    85  	}
    86  	return samples
    87  }
    88  
    89  // TestSample ensures that samples can be collected.
    90  func TestSample(t *testing.T) {
    91  	testCases := []struct {
    92  		name    string
    93  		samples []sample
    94  		err     error
    95  	}{
    96  		{
    97  			name: "basic",
    98  			samples: []sample{
    99  				{before: 100000, after: 100000 + defaultOverheadCycles, ref: 100},
   100  			},
   101  			err: nil,
   102  		},
   103  		{
   104  			// Sample with backwards TSC ignored.
   105  			// referenceClock should retry and get errNoSamples.
   106  			name: "backwards-tsc-ignored",
   107  			samples: []sample{
   108  				{before: 100000, after: 90000, ref: 100},
   109  			},
   110  			err: errNoSamples,
   111  		},
   112  		{
   113  			// Sample far above overhead skipped.
   114  			// referenceClock should retry and get errNoSamples.
   115  			name: "reject-overhead",
   116  			samples: []sample{
   117  				{before: 100000, after: 100000 + 5*defaultOverheadCycles, ref: 100},
   118  			},
   119  			err: errNoSamples,
   120  		},
   121  		{
   122  			// Maximum overhead allowed is bounded.
   123  			name: "over-max-overhead",
   124  			// Generate a bunch of samples. The reference clock
   125  			// needs a while to ramp up its expected overhead.
   126  			samples: generateSamples(100, 2*maxOverheadCycles),
   127  			err:     errOverheadTooHigh,
   128  		},
   129  		{
   130  			// Overhead at maximum overhead is allowed.
   131  			name: "max-overhead",
   132  			// Generate a bunch of samples. The reference clock
   133  			// needs a while to ramp up its expected overhead.
   134  			samples: generateSamples(100, maxOverheadCycles),
   135  			err:     nil,
   136  		},
   137  	}
   138  	for _, tc := range testCases {
   139  		t.Run(tc.name, func(t *testing.T) {
   140  			s := newTestSampler(tc.samples, nil)
   141  			err := s.Sample()
   142  			if err != tc.err {
   143  				t.Errorf("Sample err got %v want %v", err, tc.err)
   144  			}
   145  		})
   146  	}
   147  }
   148  
   149  // TestOutliersIgnored tests that referenceClock ignores samples with very high
   150  // overhead.
   151  func TestOutliersIgnored(t *testing.T) {
   152  	s := newTestSampler([]sample{
   153  		{before: 100000, after: 100000 + defaultOverheadCycles, ref: 100},
   154  		{before: 200000, after: 200000 + defaultOverheadCycles, ref: 200},
   155  		{before: 300000, after: 300000 + defaultOverheadCycles, ref: 300},
   156  		{before: 400000, after: 400000 + defaultOverheadCycles, ref: 400},
   157  		{before: 500000, after: 500000 + 5*defaultOverheadCycles, ref: 500}, // Ignored
   158  		{before: 600000, after: 600000 + defaultOverheadCycles, ref: 600},
   159  		{before: 700000, after: 700000 + defaultOverheadCycles, ref: 700},
   160  	}, nil)
   161  
   162  	// Collect 5 samples.
   163  	for i := 0; i < 5; i++ {
   164  		err := s.Sample()
   165  		if err != nil {
   166  			t.Fatalf("Unexpected error while sampling: %v", err)
   167  		}
   168  	}
   169  
   170  	oldest, newest, ok := s.Range()
   171  	if !ok {
   172  		t.Fatalf("Range not ok")
   173  	}
   174  
   175  	if oldest.ref != 100 {
   176  		t.Errorf("oldest.ref got %v want %v", oldest.ref, 100)
   177  	}
   178  
   179  	// We skipped the high-overhead sample.
   180  	if newest.ref != 600 {
   181  		t.Errorf("newest.ref got %v want %v", newest.ref, 600)
   182  	}
   183  }