1 // Copyright (c) 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/media/capture/smooth_event_sampler.h"
7 #include "base/strings/stringprintf.h"
8 #include "testing/gtest/include/gtest/gtest.h"
14 bool AddEventAndConsiderSampling(SmoothEventSampler
* sampler
,
15 base::TimeTicks event_time
) {
16 sampler
->ConsiderPresentationEvent(event_time
);
17 return sampler
->ShouldSample();
20 void SteadyStateSampleAndAdvance(base::TimeDelta vsync
,
21 SmoothEventSampler
* sampler
,
23 ASSERT_TRUE(AddEventAndConsiderSampling(sampler
, *t
));
24 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
25 sampler
->RecordSample();
26 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
27 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
29 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
32 void SteadyStateNoSampleAndAdvance(base::TimeDelta vsync
,
33 SmoothEventSampler
* sampler
,
35 ASSERT_FALSE(AddEventAndConsiderSampling(sampler
, *t
));
36 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
37 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
39 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
));
42 base::TimeTicks
InitialTestTimeTicks() {
43 return base::TimeTicks() + base::TimeDelta::FromSeconds(1);
46 void TestRedundantCaptureStrategy(base::TimeDelta capture_period
,
47 int redundant_capture_goal
,
48 SmoothEventSampler
* sampler
,
50 // Before any events have been considered, we're overdue for sampling.
51 ASSERT_TRUE(sampler
->IsOverdueForSamplingAt(*t
));
53 // Consider the first event. We want to sample that.
54 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
55 ASSERT_TRUE(AddEventAndConsiderSampling(sampler
, *t
));
56 ASSERT_TRUE(sampler
->HasUnrecordedEvent());
57 sampler
->RecordSample();
58 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
60 // After more than 250 ms has passed without considering an event, we should
61 // repeatedly be overdue for sampling. However, once the redundant capture
62 // goal is achieved, we should no longer be overdue for sampling.
63 *t
+= base::TimeDelta::FromMilliseconds(250);
64 for (int i
= 0; i
< redundant_capture_goal
; i
++) {
65 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
66 ASSERT_FALSE(sampler
->HasUnrecordedEvent());
67 ASSERT_TRUE(sampler
->IsOverdueForSamplingAt(*t
))
68 << "Should sample until redundant capture goal is hit";
69 sampler
->RecordSample();
70 *t
+= capture_period
; // Timer fires once every capture period.
72 ASSERT_FALSE(sampler
->IsOverdueForSamplingAt(*t
))
73 << "Should not be overdue once redundant capture goal achieved.";
78 // 60Hz sampled at 30Hz should produce 30Hz. In addition, this test contains
79 // much more comprehensive before/after/edge-case scenarios than the others.
80 TEST(SmoothEventSamplerTest
, Sample60HertzAt30Hertz
) {
81 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
82 const int redundant_capture_goal
= 200;
83 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 60;
85 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
86 base::TimeTicks t
= InitialTestTimeTicks();
88 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
91 // Steady state, we should capture every other vsync, indefinitely.
92 for (int i
= 0; i
< 100; i
++) {
93 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
94 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
95 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
98 // Now pretend we're limited by backpressure in the pipeline. In this scenario
99 // case we are adding events but not sampling them.
100 for (int i
= 0; i
< 20; i
++) {
101 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
102 ASSERT_EQ(i
>= 14, sampler
.IsOverdueForSamplingAt(t
));
103 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
104 ASSERT_TRUE(sampler
.HasUnrecordedEvent());
108 // Now suppose we can sample again. We should be back in the steady state,
109 // but at a different phase.
110 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
111 for (int i
= 0; i
< 100; i
++) {
112 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
113 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
114 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
118 // 50Hz sampled at 30Hz should produce a sequence where some frames are skipped.
119 TEST(SmoothEventSamplerTest
, Sample50HertzAt30Hertz
) {
120 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
121 const int redundant_capture_goal
= 2;
122 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 50;
124 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
125 base::TimeTicks t
= InitialTestTimeTicks();
127 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
130 // Steady state, we should capture 1st, 2nd and 4th frames out of every five
131 // frames, indefinitely.
132 for (int i
= 0; i
< 100; i
++) {
133 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
134 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
135 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
136 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
137 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
138 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
141 // Now pretend we're limited by backpressure in the pipeline. In this scenario
142 // case we are adding events but not sampling them.
143 for (int i
= 0; i
< 20; i
++) {
144 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
145 ASSERT_EQ(i
>= 11, sampler
.IsOverdueForSamplingAt(t
));
146 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
150 // Now suppose we can sample again. We should be back in the steady state
152 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
153 for (int i
= 0; i
< 100; i
++) {
154 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
155 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
156 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
157 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
158 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
159 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
163 // 75Hz sampled at 30Hz should produce a sequence where some frames are skipped.
164 TEST(SmoothEventSamplerTest
, Sample75HertzAt30Hertz
) {
165 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
166 const int redundant_capture_goal
= 32;
167 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 75;
169 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
170 base::TimeTicks t
= InitialTestTimeTicks();
172 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
175 // Steady state, we should capture 1st and 3rd frames out of every five
176 // frames, indefinitely.
177 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
178 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
179 for (int i
= 0; i
< 100; i
++) {
180 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
181 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
182 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
183 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
184 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
185 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
188 // Now pretend we're limited by backpressure in the pipeline. In this scenario
189 // case we are adding events but not sampling them.
190 for (int i
= 0; i
< 20; i
++) {
191 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
192 ASSERT_EQ(i
>= 16, sampler
.IsOverdueForSamplingAt(t
));
193 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
197 // Now suppose we can sample again. We capture the next frame, and not the one
198 // after that, and then we're back in the steady state again.
199 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
200 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
201 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
202 for (int i
= 0; i
< 100; i
++) {
203 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
204 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
205 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
206 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
207 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
208 SteadyStateNoSampleAndAdvance(vsync
, &sampler
, &t
);
212 // 30Hz sampled at 30Hz should produce 30Hz.
213 TEST(SmoothEventSamplerTest
, Sample30HertzAt30Hertz
) {
214 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
215 const int redundant_capture_goal
= 1;
216 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 30;
218 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
219 base::TimeTicks t
= InitialTestTimeTicks();
221 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
224 // Steady state, we should capture every vsync, indefinitely.
225 for (int i
= 0; i
< 200; i
++) {
226 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
227 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
230 // Now pretend we're limited by backpressure in the pipeline. In this scenario
231 // case we are adding events but not sampling them.
232 for (int i
= 0; i
< 10; i
++) {
233 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
234 ASSERT_EQ(i
>= 7, sampler
.IsOverdueForSamplingAt(t
));
235 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
239 // Now suppose we can sample again. We should be back in the steady state.
240 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
241 for (int i
= 0; i
< 100; i
++) {
242 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
243 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
247 // 24Hz sampled at 30Hz should produce 24Hz.
248 TEST(SmoothEventSamplerTest
, Sample24HertzAt30Hertz
) {
249 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
250 const int redundant_capture_goal
= 333;
251 const base::TimeDelta vsync
= base::TimeDelta::FromSeconds(1) / 24;
253 SmoothEventSampler
sampler(capture_period
, redundant_capture_goal
);
254 base::TimeTicks t
= InitialTestTimeTicks();
256 TestRedundantCaptureStrategy(capture_period
, redundant_capture_goal
,
259 // Steady state, we should capture every vsync, indefinitely.
260 for (int i
= 0; i
< 200; i
++) {
261 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
262 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
265 // Now pretend we're limited by backpressure in the pipeline. In this scenario
266 // case we are adding events but not sampling them.
267 for (int i
= 0; i
< 10; i
++) {
268 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
269 ASSERT_EQ(i
>= 6, sampler
.IsOverdueForSamplingAt(t
));
270 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
274 // Now suppose we can sample again. We should be back in the steady state.
275 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
));
276 for (int i
= 0; i
< 100; i
++) {
277 SCOPED_TRACE(base::StringPrintf("Iteration %d", i
));
278 SteadyStateSampleAndAdvance(vsync
, &sampler
, &t
);
282 TEST(SmoothEventSamplerTest
, DoubleDrawAtOneTimeStillDirties
) {
283 const base::TimeDelta capture_period
= base::TimeDelta::FromSeconds(1) / 30;
284 const base::TimeDelta overdue_period
= base::TimeDelta::FromSeconds(1);
286 SmoothEventSampler
sampler(capture_period
, 1);
287 base::TimeTicks t
= InitialTestTimeTicks();
289 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
290 sampler
.RecordSample();
291 ASSERT_FALSE(sampler
.IsOverdueForSamplingAt(t
))
292 << "Sampled last event; should not be dirty.";
295 // Now simulate 2 events with the same clock value.
296 ASSERT_TRUE(AddEventAndConsiderSampling(&sampler
, t
));
297 sampler
.RecordSample();
298 ASSERT_FALSE(AddEventAndConsiderSampling(&sampler
, t
))
299 << "Two events at same time -- expected second not to be sampled.";
300 ASSERT_TRUE(sampler
.IsOverdueForSamplingAt(t
+ overdue_period
))
301 << "Second event should dirty the capture state.";
302 sampler
.RecordSample();
303 ASSERT_FALSE(sampler
.IsOverdueForSamplingAt(t
+ overdue_period
));
313 void ReplayCheckingSamplerDecisions(const DataPoint
* data_points
,
314 size_t num_data_points
,
315 SmoothEventSampler
* sampler
) {
316 base::TimeTicks t
= InitialTestTimeTicks();
317 for (size_t i
= 0; i
< num_data_points
; ++i
) {
318 t
+= base::TimeDelta::FromMicroseconds(
319 static_cast<int64
>(data_points
[i
].increment_ms
* 1000));
320 ASSERT_EQ(data_points
[i
].should_capture
,
321 AddEventAndConsiderSampling(sampler
, t
))
322 << "at data_points[" << i
<< ']';
323 if (data_points
[i
].should_capture
)
324 sampler
->RecordSample();
330 TEST(SmoothEventSamplerTest
, DrawingAt24FpsWith60HzVsyncSampledAt30Hertz
) {
331 // Actual capturing of timing data: Initial instability as a 24 FPS video was
332 // started from a still screen, then clearly followed by steady-state.
333 static const DataPoint data_points
[] = {
334 { true, 1437.93 }, { true, 150.484 }, { true, 217.362 }, { true, 50.161 },
335 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 66.88 },
336 { true, 50.161 }, { false, 0 }, { false, 0 }, { true, 50.16 },
337 { true, 33.441 }, { true, 16.72 }, { false, 16.72 }, { true, 117.041 },
338 { true, 16.72 }, { false, 16.72 }, { true, 50.161 }, { true, 50.16 },
339 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 16.72 },
340 { false, 0 }, { true, 50.161 }, { false, 0 }, { true, 33.44 },
341 { true, 16.72 }, { false, 16.721 }, { true, 66.881 }, { false, 0 },
342 { true, 33.441 }, { true, 16.72 }, { true, 50.16 }, { true, 16.72 },
343 { false, 16.721 }, { true, 50.161 }, { true, 50.16 }, { false, 0 },
344 { true, 33.441 }, { true, 50.337 }, { true, 50.183 }, { true, 16.722 },
345 { true, 50.161 }, { true, 33.441 }, { true, 50.16 }, { true, 33.441 },
346 { true, 50.16 }, { true, 33.441 }, { true, 50.16 }, { true, 33.44 },
347 { true, 50.161 }, { true, 50.16 }, { true, 33.44 }, { true, 33.441 },
348 { true, 50.16 }, { true, 50.161 }, { true, 33.44 }, { true, 33.441 },
349 { true, 50.16 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 },
350 { true, 50.161 }, { true, 33.44 }, { true, 50.161 }, { true, 33.44 },
351 { true, 83.601 }, { true, 16.72 }, { true, 33.44 }, { false, 0 }
354 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
355 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
358 TEST(SmoothEventSamplerTest
, DrawingAt30FpsWith60HzVsyncSampledAt30Hertz
) {
359 // Actual capturing of timing data: Initial instability as a 30 FPS video was
360 // started from a still screen, then followed by steady-state. Drawing
361 // framerate from the video rendering was a bit volatile, but averaged 30 FPS.
362 static const DataPoint data_points
[] = {
363 { true, 2407.69 }, { true, 16.733 }, { true, 217.362 }, { true, 33.441 },
364 { true, 33.44 }, { true, 33.44 }, { true, 33.441 }, { true, 33.44 },
365 { true, 33.44 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 },
366 { true, 16.721 }, { true, 33.44 }, { false, 0 }, { true, 50.161 },
367 { true, 50.16 }, { false, 0 }, { true, 50.161 }, { true, 33.44 },
368 { true, 16.72 }, { false, 0 }, { false, 16.72 }, { true, 66.881 },
369 { false, 0 }, { true, 33.44 }, { true, 16.72 }, { true, 50.161 },
370 { false, 0 }, { true, 33.538 }, { true, 33.526 }, { true, 33.447 },
371 { true, 33.445 }, { true, 33.441 }, { true, 16.721 }, { true, 33.44 },
372 { true, 33.44 }, { true, 50.161 }, { true, 16.72 }, { true, 33.44 },
373 { true, 33.441 }, { true, 33.44 }, { false, 0 }, { false, 16.72 },
374 { true, 66.881 }, { true, 16.72 }, { false, 16.72 }, { true, 50.16 },
375 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { true, 33.44 },
376 { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { false, 0 },
377 { true, 33.44 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 },
378 { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 66.88 },
379 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 },
380 { true, 33.441 }, { true, 33.44 }, { true, 33.44 }, { false, 0 },
381 { true, 16.72 }, { true, 50.161 }, { false, 0 }, { true, 50.16 },
382 { false, 0.001 }, { true, 16.721 }, { true, 66.88 }, { true, 33.44 },
383 { true, 33.441 }, { true, 33.44 }, { true, 50.161 }, { true, 16.72 },
384 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 66.881 },
385 { true, 33.44 }, { true, 16.72 }, { true, 33.441 }, { false, 16.72 },
386 { true, 66.88 }, { true, 16.721 }, { true, 50.16 }, { true, 33.44 },
387 { true, 16.72 }, { true, 33.441 }, { true, 33.44 }, { true, 33.44 }
390 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
391 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
394 TEST(SmoothEventSamplerTest
, DrawingAt60FpsWith60HzVsyncSampledAt30Hertz
) {
395 // Actual capturing of timing data: WebGL Acquarium demo
396 // (http://webglsamples.googlecode.com/hg/aquarium/aquarium.html) which ran
397 // between 55-60 FPS in the steady-state.
398 static const DataPoint data_points
[] = {
399 { true, 16.72 }, { true, 16.72 }, { true, 4163.29 }, { true, 50.193 },
400 { true, 117.041 }, { true, 50.161 }, { true, 50.16 }, { true, 33.441 },
401 { true, 50.16 }, { true, 33.44 }, { false, 0 }, { false, 0 },
402 { true, 50.161 }, { true, 83.601 }, { true, 50.16 }, { true, 16.72 },
403 { true, 33.441 }, { false, 16.72 }, { true, 50.16 }, { true, 16.72 },
404 { false, 0.001 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 },
405 { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
406 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 },
407 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 },
408 { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 },
409 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
410 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 33.44 },
411 { false, 0 }, { true, 16.721 }, { true, 50.161 }, { false, 0 },
412 { true, 33.44 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
413 { false, 0 }, { true, 33.44 }, { false, 16.72 }, { true, 16.72 },
414 { true, 50.16 }, { false, 0 }, { true, 16.721 }, { true, 33.44 },
415 { false, 0 }, { true, 33.44 }, { false, 16.721 }, { true, 16.721 },
416 { true, 50.161 }, { false, 0 }, { true, 16.72 }, { true, 33.44 },
417 { false, 0 }, { true, 33.441 }, { false, 16.72 }, { true, 16.72 },
418 { true, 50.16 }, { false, 0 }, { true, 16.72 }, { true, 33.441 },
419 { true, 33.44 }, { false, 0 }, { true, 33.44 }, { true, 33.441 },
420 { false, 0 }, { true, 33.44 }, { true, 33.441 }, { false, 0 },
421 { true, 33.44 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
422 { true, 16.721 }, { true, 50.161 }, { false, 0 }, { true, 16.72 },
423 { true, 33.44 }, { true, 33.441 }, { false, 0 }, { true, 33.44 },
424 { true, 33.44 }, { false, 0 }, { true, 33.441 }, { false, 16.72 },
425 { true, 16.72 }, { true, 50.16 }, { false, 0 }, { true, 16.72 },
426 { true, 33.441 }, { false, 0 }, { true, 33.44 }, { false, 16.72 },
427 { true, 33.44 }, { false, 0 }, { true, 16.721 }, { true, 50.161 },
428 { false, 0 }, { true, 16.72 }, { true, 33.44 }, { false, 0 },
429 { true, 33.441 }, { false, 16.72 }, { true, 16.72 }, { true, 50.16 }
432 SmoothEventSampler
sampler(base::TimeDelta::FromSeconds(1) / 30, 3);
433 ReplayCheckingSamplerDecisions(data_points
, arraysize(data_points
), &sampler
);
436 } // namespace content