blob: efec9b14eeeef32f35a06c7be3aa2679b60149fe [file] [log] [blame]
/*
* Copyright (C) 2012 Intel Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/timing/performance_user_timing.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/core/timing/performance.h"
#include "third_party/blink/renderer/core/timing/performance_mark.h"
#include "third_party/blink/renderer/core/timing/performance_mark_options.h"
#include "third_party/blink/renderer/core/timing/performance_measure.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
namespace blink {
namespace {
using RestrictedKeyMap = HashMap<AtomicString, NavigationTimingFunction>;
const RestrictedKeyMap& GetRestrictedKeyMap() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<RestrictedKeyMap>, map, ());
if (!map.IsSet()) {
*map = {
{"navigationStart", &PerformanceTiming::navigationStart},
{"unloadEventStart", &PerformanceTiming::unloadEventStart},
{"unloadEventEnd", &PerformanceTiming::unloadEventEnd},
{"redirectStart", &PerformanceTiming::redirectStart},
{"redirectEnd", &PerformanceTiming::redirectEnd},
{"fetchStart", &PerformanceTiming::fetchStart},
{"domainLookupStart", &PerformanceTiming::domainLookupStart},
{"domainLookupEnd", &PerformanceTiming::domainLookupEnd},
{"connectStart", &PerformanceTiming::connectStart},
{"connectEnd", &PerformanceTiming::connectEnd},
{"secureConnectionStart", &PerformanceTiming::secureConnectionStart},
{"requestStart", &PerformanceTiming::requestStart},
{"responseStart", &PerformanceTiming::responseStart},
{"responseEnd", &PerformanceTiming::responseEnd},
{"domLoading", &PerformanceTiming::domLoading},
{"domInteractive", &PerformanceTiming::domInteractive},
{"domContentLoadedEventStart",
&PerformanceTiming::domContentLoadedEventStart},
{"domContentLoadedEventEnd",
&PerformanceTiming::domContentLoadedEventEnd},
{"domComplete", &PerformanceTiming::domComplete},
{"loadEventStart", &PerformanceTiming::loadEventStart},
{"loadEventEnd", &PerformanceTiming::loadEventEnd},
};
}
return *map;
}
} // namespace
UserTiming::UserTiming(Performance& performance) : performance_(&performance) {}
static void InsertPerformanceEntry(PerformanceEntryMap& performance_entry_map,
PerformanceEntry& entry) {
PerformanceEntryMap::iterator it = performance_entry_map.find(entry.name());
if (it != performance_entry_map.end()) {
it->value.push_back(&entry);
} else {
PerformanceEntryVector vector(1);
vector[0] = Member<PerformanceEntry>(entry);
performance_entry_map.Set(entry.name(), vector);
}
}
static void ClearPeformanceEntries(PerformanceEntryMap& performance_entry_map,
const AtomicString& name) {
if (name.IsNull()) {
performance_entry_map.clear();
return;
}
if (performance_entry_map.Contains(name))
performance_entry_map.erase(name);
}
PerformanceMark* UserTiming::Mark(ScriptState* script_state,
const AtomicString& mark_name,
PerformanceMarkOptions* mark_options,
ExceptionState& exception_state) {
if (!RuntimeEnabledFeatures::CustomUserTimingEnabled())
DCHECK(!mark_options);
DOMHighResTimeStamp start = 0.0;
if (mark_options && mark_options->hasStartTime()) {
start = mark_options->startTime();
} else {
start = performance_->now();
}
// Pass in a null ScriptValue if the mark's detail doesn't exist.
ScriptValue detail = ScriptValue::CreateNull(script_state);
if (mark_options)
detail = mark_options->detail();
return MarkInternal(script_state, mark_name, start, detail, exception_state);
}
PerformanceMark* UserTiming::MarkInternal(ScriptState* script_state,
const AtomicString& mark_name,
const DOMHighResTimeStamp& start_time,
const ScriptValue& detail,
ExceptionState& exception_state) {
DCHECK(performance_);
bool is_worker_global_scope =
performance_->GetExecutionContext() &&
performance_->GetExecutionContext()->IsWorkerGlobalScope();
if (!is_worker_global_scope && GetRestrictedKeyMap().Contains(mark_name)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kSyntaxError,
"'" + mark_name +
"' is part of the PerformanceTiming interface, and "
"cannot be used as a mark name.");
return nullptr;
}
if (performance_->timing()) {
TRACE_EVENT_COPY_MARK1("blink.user_timing", mark_name.Utf8().data(), "data",
performance_->timing()->GetNavigationTracingData());
} else {
TRACE_EVENT_COPY_MARK("blink.user_timing", mark_name.Utf8().data());
}
PerformanceMark* mark =
PerformanceMark::Create(script_state, mark_name, start_time, detail);
InsertPerformanceEntry(marks_map_, *mark);
DEFINE_THREAD_SAFE_STATIC_LOCAL(CustomCountHistogram,
user_timing_mark_histogram,
("PLT.UserTiming_Mark", 0, 600000, 100));
user_timing_mark_histogram.Count(static_cast<int>(start_time));
return mark;
}
void UserTiming::ClearMarks(const AtomicString& mark_name) {
ClearPeformanceEntries(marks_map_, mark_name);
}
double UserTiming::FindExistingMarkStartTime(const AtomicString& mark_name,
ExceptionState& exception_state) {
if (marks_map_.Contains(mark_name))
return marks_map_.at(mark_name).back()->startTime();
if (GetRestrictedKeyMap().Contains(mark_name) && performance_->timing()) {
double value = static_cast<double>(
(performance_->timing()->*(GetRestrictedKeyMap().at(mark_name)))());
if (!value) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidAccessError,
"'" + mark_name +
"' is empty: either the event hasn't "
"happened yet, or it would provide "
"cross-origin timing information.");
return 0.0;
}
return value - performance_->timing()->navigationStart();
}
exception_state.ThrowDOMException(
DOMExceptionCode::kSyntaxError,
"The mark '" + mark_name + "' does not exist.");
return 0.0;
}
double UserTiming::GetTimeOrFindMarkTime(const StringOrDouble& mark_or_time,
ExceptionState& exception_state) {
if (mark_or_time.IsString()) {
return FindExistingMarkStartTime(AtomicString(mark_or_time.GetAsString()),
exception_state);
}
DCHECK(mark_or_time.IsDouble());
return mark_or_time.GetAsDouble();
}
PerformanceMeasure* UserTiming::Measure(ScriptState* script_state,
const AtomicString& measure_name,
const StringOrDouble& start,
const StringOrDouble& end,
const ScriptValue& detail,
ExceptionState& exception_state) {
double start_time =
start.IsNull() ? 0.0 : GetTimeOrFindMarkTime(start, exception_state);
if (exception_state.HadException())
return nullptr;
double end_time = end.IsNull() ? performance_->now()
: GetTimeOrFindMarkTime(end, exception_state);
if (exception_state.HadException())
return nullptr;
// User timing events are stored as integer milliseconds from the start of
// navigation, whereas trace events accept double seconds based off of
// CurrentTime::monotonicallyIncreasingTime().
double start_time_monotonic =
performance_->GetTimeOrigin() + start_time / 1000.0;
double end_time_monotonic = performance_->GetTimeOrigin() + end_time / 1000.0;
unsigned hash = WTF::StringHash::GetHash(measure_name);
WTF::AddFloatToHash(hash, start_time);
WTF::AddFloatToHash(hash, end_time);
TRACE_EVENT_COPY_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"blink.user_timing", measure_name.Utf8().data(), hash,
trace_event::ToTraceTimestamp(start_time_monotonic));
TRACE_EVENT_COPY_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"blink.user_timing", measure_name.Utf8().data(), hash,
trace_event::ToTraceTimestamp(end_time_monotonic));
PerformanceMeasure* measure = PerformanceMeasure::Create(
script_state, measure_name, start_time, end_time, detail);
InsertPerformanceEntry(measures_map_, *measure);
if (end_time >= start_time) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, measure_duration_histogram,
("PLT.UserTiming_MeasureDuration", 0, 600000, 100));
measure_duration_histogram.Count(static_cast<int>(end_time - start_time));
}
return measure;
}
void UserTiming::ClearMeasures(const AtomicString& measure_name) {
ClearPeformanceEntries(measures_map_, measure_name);
}
static PerformanceEntryVector ConvertToEntrySequence(
const PerformanceEntryMap& performance_entry_map) {
PerformanceEntryVector entries;
for (const auto& entry : performance_entry_map)
entries.AppendVector(entry.value);
return entries;
}
static PerformanceEntryVector GetEntrySequenceByName(
const PerformanceEntryMap& performance_entry_map,
const AtomicString& name) {
PerformanceEntryVector entries;
PerformanceEntryMap::const_iterator it = performance_entry_map.find(name);
if (it != performance_entry_map.end())
entries.AppendVector(it->value);
return entries;
}
PerformanceEntryVector UserTiming::GetMarks() const {
return ConvertToEntrySequence(marks_map_);
}
PerformanceEntryVector UserTiming::GetMarks(const AtomicString& name) const {
return GetEntrySequenceByName(marks_map_, name);
}
PerformanceEntryVector UserTiming::GetMeasures() const {
return ConvertToEntrySequence(measures_map_);
}
PerformanceEntryVector UserTiming::GetMeasures(const AtomicString& name) const {
return GetEntrySequenceByName(measures_map_, name);
}
void UserTiming::Trace(blink::Visitor* visitor) {
visitor->Trace(performance_);
visitor->Trace(marks_map_);
visitor->Trace(measures_map_);
}
} // namespace blink