blob: 39a16bf290a794cab104a51fcb8a82a789824231 [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 "base/trace_event/typed_macros.h"
#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_performance_mark_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_double_string.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/performance_entry_names.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_measure.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.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 {
bool IsTracingEnabled() {
bool enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED("blink.user_timing", &enabled);
return enabled;
}
} // namespace
UserTiming::UserTiming(Performance& performance) : performance_(&performance) {}
String UserTiming::GetSerializedDetail(const ScriptValue& detail) {
String serialized_detail = "";
if (ExecutionContext* execution_context =
performance_->GetExecutionContext()) {
v8::Isolate* isolate = execution_context->GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
if (!(detail.IsEmpty() || detail.V8Value()->IsNullOrUndefined())) {
v8::Local<v8::String> v8_string;
if (v8::JSON::Stringify(context, detail.V8Value()).ToLocal(&v8_string)) {
serialized_detail = ToCoreString(isolate, v8_string);
}
}
}
return serialized_detail;
}
void UserTiming::AddMarkToPerformanceTimeline(
PerformanceMark& mark,
PerformanceMarkOptions* mark_options) {
InsertPerformanceEntry(marks_map_, marks_buffer_, mark);
if (!IsTracingEnabled()) {
return;
}
ScriptValue detail = mark_options && mark_options->hasDetail()
? mark_options->detail()
: ScriptValue();
String serialized_detail = GetSerializedDetail(detail);
const auto trace_event_details = [&](perfetto::EventContext ctx) {
ctx.event()->set_name(mark.name().Utf8().c_str());
ctx.AddDebugAnnotation("data", [&](perfetto::TracedValue trace_context) {
auto dict = std::move(trace_context).WriteDictionary();
dict.Add("startTime", mark.startTime());
// Only set when performance_ is a WindowPerformance.
// performance_->timing() returns null when performance_ is a
// WorkerPerformance.
if (serialized_detail.length()) {
dict.Add("detail", serialized_detail);
}
if (performance_->timing()) {
performance_->timing()->WriteInto(dict);
}
});
};
TRACE_EVENT_INSTANT("blink.user_timing", nullptr, mark.UnsafeTimeForTraces(),
trace_event_details);
}
void UserTiming::ClearMarks(const AtomicString& mark_name) {
ClearPerformanceEntries(marks_map_, marks_buffer_, mark_name);
}
const PerformanceMark* UserTiming::FindExistingMark(
const AtomicString& mark_name) {
PerformanceEntryMap::const_iterator existing_marks =
marks_map_.find(mark_name);
if (existing_marks != marks_map_.end()) {
PerformanceEntry* entry = existing_marks->value->back().Get();
DCHECK(entry->entryType() == performance_entry_names::kMark);
return static_cast<PerformanceMark*>(entry);
}
return nullptr;
}
double UserTiming::FindExistingMarkStartTime(const AtomicString& mark_name,
ExceptionState& exception_state) {
const PerformanceMark* mark = FindExistingMark(mark_name);
if (mark) {
return mark->startTime();
}
// Although there was no mark with the given name in UserTiming, we need to
// support measuring with respect to |PerformanceTiming| attributes.
if (!PerformanceTiming::IsAttributeName(mark_name)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kSyntaxError,
"The mark '" + mark_name + "' does not exist.");
return 0.0;
}
PerformanceTiming* timing = performance_->timing();
if (!timing) {
// According to
// https://w3c.github.io/user-timing/#convert-a-name-to-a-timestamp.
exception_state.ThrowTypeError(
"When converting a mark name ('" + mark_name +
"') to a timestamp given a name that is a read only attribute in the "
"PerformanceTiming interface, the global object has to be a Window "
"object.");
return 0.0;
}
// Because we know |PerformanceTiming::IsAttributeName(mark_name)| is true
// (from above), we know calling |GetNamedAttribute| won't fail.
double value = static_cast<double>(timing->GetNamedAttribute(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;
}
// Count the usage of PerformanceTiming attribute names in performance
// measure. See crbug.com/1318445.
blink::UseCounter::Count(performance_->GetExecutionContext(),
WebFeature::kPerformanceMeasureFindExistingName);
return value - timing->navigationStart();
}
double UserTiming::GetTimeOrFindMarkTime(
const AtomicString& measure_name,
const V8UnionDoubleOrString* mark_or_time,
ExceptionState& exception_state) {
DCHECK(mark_or_time);
switch (mark_or_time->GetContentType()) {
case V8UnionDoubleOrString::ContentType::kDouble: {
const double time = mark_or_time->GetAsDouble();
if (time < 0.0) {
exception_state.ThrowTypeError("'" + measure_name +
"' cannot have a negative time stamp.");
}
return time;
}
case V8UnionDoubleOrString::ContentType::kString:
return FindExistingMarkStartTime(
AtomicString(mark_or_time->GetAsString()), exception_state);
}
NOTREACHED();
return 0;
}
base::TimeTicks UserTiming::GetPerformanceMarkUnsafeTimeForTraces(
double start_time,
const V8UnionDoubleOrString* maybe_mark_name) {
if (maybe_mark_name && maybe_mark_name->IsString()) {
const PerformanceMark* mark =
FindExistingMark(AtomicString(maybe_mark_name->GetAsString()));
if (mark) {
return mark->UnsafeTimeForTraces();
}
}
return performance_->GetTimeOriginInternal() + base::Milliseconds(start_time);
}
PerformanceMeasure* UserTiming::Measure(ScriptState* script_state,
const AtomicString& measure_name,
const V8UnionDoubleOrString* start,
const std::optional<double>& duration,
const V8UnionDoubleOrString* end,
const ScriptValue& detail,
ExceptionState& exception_state,
DOMWindow* source) {
double start_time =
start ? GetTimeOrFindMarkTime(measure_name, start, exception_state) : 0;
if (exception_state.HadException())
return nullptr;
double end_time =
end ? GetTimeOrFindMarkTime(measure_name, end, exception_state)
: performance_->now();
if (exception_state.HadException())
return nullptr;
if (duration.has_value()) {
// When |duration| is specified, we require that exactly one of |start| and
// |end| were specified. Then, since |start| + |duration| = |end|, we'll
// compute the missing boundary.
if (!start) {
start_time = end_time - duration.value();
} else {
DCHECK(!end) << "When duration is specified, one of 'start' or "
"'end' must be unspecified";
end_time = start_time + duration.value();
}
}
if (IsTracingEnabled()) {
base::TimeTicks unsafe_start_time =
GetPerformanceMarkUnsafeTimeForTraces(start_time, start);
base::TimeTicks unsafe_end_time =
GetPerformanceMarkUnsafeTimeForTraces(end_time, end);
unsigned hash = WTF::GetHash(measure_name);
WTF::AddFloatToHash(hash, start_time);
WTF::AddFloatToHash(hash, end_time);
String serialized_detail = GetSerializedDetail(detail);
if (serialized_detail.length()) {
TRACE_EVENT_BEGIN("blink.user_timing", nullptr, perfetto::Track(hash),
unsafe_start_time, "startTime", start_time, "detail",
serialized_detail, [&](perfetto::EventContext ctx) {
ctx.event()->set_name(measure_name.Utf8().c_str());
});
} else {
TRACE_EVENT_BEGIN("blink.user_timing", nullptr, perfetto::Track(hash),
unsafe_start_time, "startTime", start_time,
[&](perfetto::EventContext ctx) {
ctx.event()->set_name(measure_name.Utf8().c_str());
});
}
TRACE_EVENT_END("blink.user_timing", perfetto::Track(hash),
unsafe_end_time);
}
PerformanceMeasure* measure =
PerformanceMeasure::Create(script_state, measure_name, start_time,
end_time, detail, exception_state, source);
if (!measure)
return nullptr;
InsertPerformanceEntry(measures_map_, measures_buffer_, *measure);
return measure;
}
void UserTiming::ClearMeasures(const AtomicString& measure_name) {
ClearPerformanceEntries(measures_map_, measures_buffer_, measure_name);
}
PerformanceEntryVector UserTiming::GetMarks() const {
return marks_buffer_;
}
PerformanceEntryVector UserTiming::GetMarks(const AtomicString& name) const {
PerformanceEntryMap::const_iterator it = marks_map_.find(name);
if (it != marks_map_.end()) {
return *it->value;
}
return {};
}
PerformanceEntryVector UserTiming::GetMeasures() const {
return measures_buffer_;
}
PerformanceEntryVector UserTiming::GetMeasures(const AtomicString& name) const {
PerformanceEntryMap::const_iterator it = measures_map_.find(name);
if (it != measures_map_.end()) {
return *it->value;
}
return {};
}
void UserTiming::InsertPerformanceEntry(
PerformanceEntryMap& performance_entry_map,
PerformanceEntryVector& performance_entry_buffer,
PerformanceEntry& entry) {
performance_->InsertEntryIntoSortedBuffer(performance_entry_buffer, entry,
Performance::kDoNotRecordSwaps);
auto it = performance_entry_map.find(entry.name());
if (it == performance_entry_map.end()) {
PerformanceEntryVector* entries =
MakeGarbageCollected<PerformanceEntryVector>();
entries->push_back(&entry);
performance_entry_map.Set(entry.name(), entries);
return;
}
DCHECK(it->value);
performance_->InsertEntryIntoSortedBuffer(*it->value.Get(), entry,
Performance::kDoNotRecordSwaps);
}
void UserTiming::ClearPerformanceEntries(
PerformanceEntryMap& performance_entry_map,
PerformanceEntryVector& performance_entry_buffer,
const AtomicString& name) {
if (name.IsNull()) {
performance_entry_map.clear();
performance_entry_buffer.clear();
return;
}
if (performance_entry_map.Contains(name)) {
UseCounter::Count(performance_->GetExecutionContext(),
WebFeature::kClearPerformanceEntries);
// Remove key/value pair from the map.
performance_entry_map.erase(name);
// In favor of quicker getEntries() calls, we tradeoff performance here to
// linearly 'clear' entries in the vector.
performance_entry_buffer.erase(
std::remove_if(performance_entry_buffer.begin(),
performance_entry_buffer.end(),
[name](auto& entry) { return entry->name() == name; }),
performance_entry_buffer.end());
}
}
void UserTiming::Trace(Visitor* visitor) const {
visitor->Trace(performance_);
visitor->Trace(marks_map_);
visitor->Trace(measures_map_);
visitor->Trace(marks_buffer_);
visitor->Trace(measures_buffer_);
}
} // namespace blink