Commit a77687fd authored by Sami Kyostila's avatar Sami Kyostila Committed by Commit Bot

Clamp performance.now() to 100us.

This patch reduces the resolution of performance.now() from 5us to 100us
and adds pseudorandom jitter on top.

Authors: Ross McIlroy <rmcilroy@chromium.org>, Sami Kyostila <skyostil@chromium.org>
Bug: 798964
Change-Id: I7e401d7beae2efe8074cac8fc094b22e6dc857c2
Reviewed-on: https://chromium-review.googlesource.com/849993
Commit-Queue: Sami Kyöstilä <skyostil@chromium.org>
Reviewed-by: default avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: default avatarJochen Eisinger <jochen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#527008}
parent 52a8eceb
......@@ -24,10 +24,10 @@ function createTest(eventName, dispatchEventFn) {
async_test(function(t) {
document.addEventListener(eventName, t.step_func(function(e) {
const platformTimestamp = eventSender.lastEventTimestamp(); // in seconds
const expectedTimestamp = Math.floor((internals.monotonicTimeToZeroBasedDocumentTime(platformTimestamp) * 1000) / 0.005) * 0.005;
// Use 0.005001 instead of 0.005 to deal with floating point
const expectedTimestamp = Math.floor((internals.monotonicTimeToZeroBasedDocumentTime(platformTimestamp) * 1000) / 0.1) * 0.1;
// Use 0.100001 instead of 0.1 to deal with floating point
// comparison issues.
assert_approx_equals(e.timeStamp, expectedTimestamp, 0.005001);
assert_approx_equals(e.timeStamp, expectedTimestamp, 0.100001);
t.done();
}));
dispatchEventFn();
......
......@@ -14,16 +14,23 @@
performance.measure("a", "a-start", "a-end");
}
function doWork()
{
for(var i = 0; i < 10000; i++) {}
}
function nestedPerformanceMeasure()
{
performance.mark("a-start");
{
performance.mark("b-start");
doWork();
performance.mark("b-end");
{
performance.mark("c-start");
{
performance.mark("d-start");
doWork();
performance.mark("d-end");
}
performance.mark("c-end");
......@@ -51,9 +58,11 @@
function parentMeasureIsOnTop()
{
performance.mark("startTime1");
doWork();
performance.mark("endTime1");
performance.mark("startTime2");
doWork();
performance.mark("endTime2");
performance.measure("durationTime1", "startTime1", "endTime1");
......
Verifies the minimum resolution is 5 microseconds.
Verifies the minimum resolution is 100 microseconds.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS shouldBeNearZeroOrOne < 1e-10 || Math.abs(shouldBeNearZeroOrOne - 1) < 1e-10 is true
PASS shouldBeNearZeroOrOne < 1e-10 || Math.abs(shouldBeNearZeroOrOne - 1) < 1e-10 is true
PASS shouldBeNearZeroOrOne < 1e-5 || Math.abs(shouldBeNearZeroOrOne - 1) < 1e-5 is true
PASS shouldBeNearZeroOrOne < 1e-5 || Math.abs(shouldBeNearZeroOrOne - 1) < 1e-5 is true
PASS successfullyParsed is true
TEST COMPLETE
......
......@@ -6,7 +6,7 @@
<p id="description"></p>
<div id="console"></div>
<script>
description("Verifies the minimum resolution is 5 microseconds.");
description("Verifies the minimum resolution is 100 microseconds.");
function testTimeResolution(highResTimeFunc) {
var t0 = highResTimeFunc();
......@@ -15,10 +15,10 @@ function testTimeResolution(highResTimeFunc) {
t1 = highResTimeFunc();
}
var expectedResolutionMilliseconds = 0.005;
var expectedResolutionMilliseconds = 0.100;
var integerMultipleOfResolution = (t1 - t0) / expectedResolutionMilliseconds;
shouldBeNearZeroOrOne = integerMultipleOfResolution % 1;
shouldBe("shouldBeNearZeroOrOne < 1e-10 || Math.abs(shouldBeNearZeroOrOne - 1) < 1e-10", "true");
shouldBe("shouldBeNearZeroOrOne < 1e-5 || Math.abs(shouldBeNearZeroOrOne - 1) < 1e-5", "true");
}
function timeByPerformanceNow() {
......
......@@ -17,12 +17,12 @@ IdleDeadline::IdleDeadline(double deadline_seconds, CallbackType callback_type)
double IdleDeadline::timeRemaining() const {
double time_remaining = deadline_seconds_ - CurrentTimeTicksInSeconds();
if (time_remaining < 0) {
time_remaining = 0;
return 0;
} else if (Platform::Current()
->CurrentThread()
->Scheduler()
->ShouldYieldForHighPriorityWork()) {
time_remaining = 0;
return 0;
}
return 1000.0 * PerformanceBase::ClampTimeResolution(time_remaining);
......
......@@ -89,7 +89,7 @@ TEST_F(IdleDeadlineTest, deadlineInFuture) {
IdleDeadline* deadline =
IdleDeadline::Create(1.25, IdleDeadline::CallbackType::kCalledWhenIdle);
// Note: the deadline is computed with reduced resolution.
EXPECT_FLOAT_EQ(249.995, deadline->timeRemaining());
EXPECT_FLOAT_EQ(250.0, deadline->timeRemaining());
}
TEST_F(IdleDeadlineTest, deadlineInPast) {
......
......@@ -34,7 +34,6 @@ class IdleRequestCallbackWrapper
static void IdleTaskFired(
scoped_refptr<IdleRequestCallbackWrapper> callback_wrapper,
double deadline_seconds) {
// TODO(rmcilroy): Implement clamping of deadline in some form.
if (ScriptedIdleTaskController* controller =
callback_wrapper->Controller()) {
// If we are going to yield immediately, reschedule the callback for
......
......@@ -45,6 +45,7 @@
#include "core/timing/PerformanceResourceTiming.h"
#include "core/timing/PerformanceUserTiming.h"
#include "platform/Histogram.h"
#include "platform/TimeClamper.h"
#include "platform/loader/fetch/ResourceResponse.h"
#include "platform/loader/fetch/ResourceTimingInfo.h"
#include "platform/runtime_enabled_features.h"
......@@ -581,8 +582,8 @@ void PerformanceBase::DeliverObservationsTimerFired(TimerBase*) {
// static
double PerformanceBase::ClampTimeResolution(double time_seconds) {
const double kResolutionSeconds = 0.000005;
return floor(time_seconds / kResolutionSeconds) * kResolutionSeconds;
DEFINE_THREAD_SAFE_STATIC_LOCAL(TimeClamper, clamper, ());
return clamper.ClampTimeResolution(time_seconds);
}
// static
......@@ -594,11 +595,11 @@ DOMHighResTimeStamp PerformanceBase::MonotonicTimeToDOMHighResTimeStamp(
if (!monotonic_time || !time_origin)
return 0.0;
double time_in_seconds = monotonic_time - time_origin;
if (time_in_seconds < 0 && !allow_negative_value)
double clamped_time_in_seconds =
ClampTimeResolution(monotonic_time) - ClampTimeResolution(time_origin);
if (clamped_time_in_seconds < 0 && !allow_negative_value)
return 0.0;
return ConvertSecondsToDOMHighResTimeStamp(
ClampTimeResolution(time_in_seconds));
return ConvertSecondsToDOMHighResTimeStamp(clamped_time_in_seconds);
}
DOMHighResTimeStamp PerformanceBase::MonotonicTimeToDOMHighResTimeStamp(
......
......@@ -74,7 +74,7 @@ class CORE_EXPORT PerformanceBase : public EventTargetWithInlineData {
virtual void UpdateLongTaskInstrumentation() {}
// Reduce the resolution to 5µs to prevent timing attacks. See:
// Reduce the resolution to prevent timing attacks. See:
// http://www.w3.org/TR/hr-time-2/#privacy-security
static double ClampTimeResolution(double time_seconds);
......
......@@ -376,6 +376,8 @@ jumbo_component("platform") {
"Theme.cpp",
"Theme.h",
"ThemeTypes.h",
"TimeClamper.cpp",
"TimeClamper.h",
"Timer.cpp",
"Timer.h",
"UUID.cpp",
......@@ -1765,6 +1767,7 @@ jumbo_source_set("blink_platform_unittests_sources") {
"PODRedBlackTreeTest.cpp",
"ScopedOrientationChangeIndicatorTest.cpp",
"SharedBufferTest.cpp",
"TimeClamperTest.cpp",
"TimerTest.cpp",
"UUIDTest.cpp",
"WebIconSizesParserTest.cpp",
......
......@@ -4,6 +4,7 @@ include_rules = [
"+base/allocator/partition_allocator/oom.h",
"+base/bind.h",
"+base/bind_helpers.h",
"+base/bit_cast.h",
"+base/feature_list.h",
"+base/files",
"+base/guid.h",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/TimeClamper.h"
#include "base/bit_cast.h"
#include "platform/wtf/Assertions.h"
#include "platform/wtf/CryptographicallyRandomNumber.h"
#include <cmath>
namespace blink {
TimeClamper::TimeClamper() {
CryptographicallyRandomValues(&secret_, sizeof(secret_));
}
double TimeClamper::ClampTimeResolution(double time_seconds) const {
DCHECK_GE(time_seconds, 0);
double clamped_time =
floor(time_seconds / kResolutionSeconds) * kResolutionSeconds;
double tick_threshold = ThresholdFor(clamped_time);
if (time_seconds >= tick_threshold)
return clamped_time + kResolutionSeconds;
return clamped_time;
}
inline double TimeClamper::ThresholdFor(double clamped_time) const {
uint64_t time_hash = MurmurHash3(bit_cast<int64_t>(clamped_time) ^ secret_);
return clamped_time + kResolutionSeconds * ToDouble(time_hash);
}
// static
inline double TimeClamper::ToDouble(uint64_t value) {
// Exponent for double values for [1.0 .. 2.0]
static const uint64_t kExponentBits = uint64_t{0x3FF0000000000000};
static const uint64_t kMantissaMask = uint64_t{0x000FFFFFFFFFFFFF};
uint64_t random = (value & kMantissaMask) | kExponentBits;
return bit_cast<double>(random) - 1;
}
// static
inline uint64_t TimeClamper::MurmurHash3(uint64_t value) {
value ^= value >> 33;
value *= uint64_t{0xFF51AFD7ED558CCD};
value ^= value >> 33;
value *= uint64_t{0xC4CEB9FE1A85EC53};
value ^= value >> 33;
return value;
}
} // namespace blink
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef TimeClamper_h
#define TimeClamper_h
#include "base/macros.h"
#include "platform/PlatformExport.h"
#include <stdint.h>
namespace blink {
class PLATFORM_EXPORT TimeClamper {
public:
static constexpr double kResolutionSeconds = 0.0001;
TimeClamper();
// Deterministically clamp the time value |time_seconds| to a 100us interval
// to prevent timing attacks. See
// http://www.w3.org/TR/hr-time-2/#privacy-security.
//
// For each clamped time interval, we compute a pseudorandom transition
// threshold. The returned time will either be the start of that interval or
// the next one depending on which side of the threshold |time_seconds| is.
double ClampTimeResolution(double time_seconds) const;
private:
inline double ThresholdFor(double clamped_time) const;
static inline double ToDouble(uint64_t value);
static inline uint64_t MurmurHash3(uint64_t value);
uint64_t secret_;
DISALLOW_COPY_AND_ASSIGN(TimeClamper);
};
} // namespace blink
#endif // TimeClamper_h
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "platform/TimeClamper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include <cmath>
namespace blink {
namespace {
const double kInterval = TimeClamper::kResolutionSeconds;
}
TEST(TimeClamperTest, TimeStampsAreNonNegative) {
TimeClamper clamper;
EXPECT_GE(clamper.ClampTimeResolution(0), 0.f);
EXPECT_GE(clamper.ClampTimeResolution(TimeClamper::kResolutionSeconds), 0.f);
}
TEST(TimeClamperTest, TimeStampsIncreaseByFixedAmount) {
const double kEpsilon = 1e-10;
TimeClamper clamper;
double prev = clamper.ClampTimeResolution(0);
for (double time_seconds = 0; time_seconds < kInterval * 100;
time_seconds += kInterval * 0.1) {
double clamped_time = clamper.ClampTimeResolution(time_seconds);
double delta = clamped_time - prev;
if (delta > kEpsilon) {
ASSERT_TRUE(std::fabs(delta - kInterval) < kEpsilon);
prev = clamped_time;
}
}
}
TEST(TimeClamperTest, ClampingIsConsistent) {
TimeClamper clamper;
for (double time_seconds = 0; time_seconds < kInterval * 100;
time_seconds += kInterval * 0.1) {
double t1 = clamper.ClampTimeResolution(time_seconds);
double t2 = clamper.ClampTimeResolution(time_seconds);
EXPECT_EQ(t1, t2);
}
}
TEST(TimeClamperTest, ClampingIsPerInstance) {
const double kEpsilon = 1e-10;
TimeClamper clamper1;
TimeClamper clamper2;
double time_seconds = 0;
while (true) {
if (std::fabs(clamper1.ClampTimeResolution(time_seconds) -
clamper2.ClampTimeResolution(time_seconds)) > kEpsilon) {
break;
}
time_seconds += kInterval;
}
}
TEST(TimeClamperTest, ClampingIsUniform) {
const int kBuckets = 8;
const int kSampleCount = 10000;
const double kEpsilon = 1e-10;
const double kTimeStep = kInterval / kBuckets;
double time_seconds = 299792.458;
int histogram[kBuckets] = {0};
TimeClamper clamper;
// This test ensures the jitter thresholds are approximately uniformly
// distributed inside the clamping intervals. It samples individual intervals
// to detect where the threshold is and counts the number of steps taken.
for (int i = 0; i < kSampleCount; i++) {
double start = clamper.ClampTimeResolution(time_seconds);
for (int step = 0; step < kBuckets; step++) {
time_seconds += kTimeStep;
if (std::abs(clamper.ClampTimeResolution(time_seconds) - start) >
kEpsilon) {
histogram[step]++;
// Skip to the next interval to make sure each measurement is
// independent.
time_seconds = floor(time_seconds / kInterval) * kInterval + kInterval;
break;
}
}
}
double expected_count = kSampleCount / kBuckets;
double chi_squared = 0;
for (int i = 0; i < kBuckets; ++i) {
double difference = histogram[i] - expected_count;
chi_squared += difference * difference / expected_count;
}
// P-value for a 0.001 significance level with 7 degrees of freedom.
EXPECT_LT(chi_squared, 24.322);
}
} // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment