Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Define class ScriptPromiseResultTracker for metric tracking
As part of the effort to improve metric and latency tracking of Chrome RTC APIs, we have introduced the class ScriptPromiseResultTracker which wraps around ScriptPromiseResolver. In the follow-up CLs, we would replace the usage of ScriptPromiseResolver with ScriptPromiseResultTracker for our APIs of interest. Bug: 1373398 Change-Id: Ifa91ff641da1c703ec0f701a743c867af0203591 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4031635 Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Commit-Queue: Palak Agarwal <agpalak@chromium.org> Cr-Commit-Position: refs/heads/main@{#1075160}
- Loading branch information
Showing
3 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
third_party/blink/renderer/bindings/core/v8/script_promise_result_tracker.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright 2022 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_SCRIPT_PROMISE_RESULT_TRACKER_H_ | ||
#define THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_SCRIPT_PROMISE_RESULT_TRACKER_H_ | ||
|
||
#include "base/metrics/histogram_functions.h" | ||
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" | ||
|
||
namespace blink { | ||
|
||
// ScriptPromiseResultTracker is a wrapper around ScriptPromiseResolver which | ||
// simplifies recording UMA metric and latency for APIs. | ||
|
||
// Callers should ensure that the ResultEnumType has kOk and kTimedOut as | ||
// values. | ||
template <typename ResultEnumType> | ||
class CORE_EXPORT ScriptPromiseResultTracker | ||
: public GarbageCollected<ScriptPromiseResultTracker<ResultEnumType>> { | ||
public: | ||
// If the targeted histograms are "WebRTC.EnumerateDevices.Result" and | ||
// "WebRTC.EnumerateDevices.Latency", the input to |metric_name_prefix| should | ||
// be "WebRTC.EnumerateDevices". | ||
// | ||
// |timeout_interval| is the timeout limit after which a | ||
// ResultEnumType::kTimedOut response is recorded in the Result histogram. | ||
// | ||
// This creates/accesses the Latency histogram which has |n_buckets_| buckets | ||
// and the range of the buckets are from (min_latency_bucket, | ||
// max_latency_bucket). | ||
ScriptPromiseResultTracker( | ||
ScriptState* script_state, | ||
std::string metric_name_prefix, | ||
base::TimeDelta timeout_interval, | ||
base::TimeDelta min_latency_bucket = base::Milliseconds(1), | ||
base::TimeDelta max_latency_bucket = base::Seconds(10), | ||
size_t n_buckets = 50) | ||
: metric_name_prefix_(std::move(metric_name_prefix)), | ||
start_time_(base::TimeTicks::Now()), | ||
timeout_interval_(timeout_interval), | ||
min_latency_bucket_(min_latency_bucket), | ||
max_latency_bucket_(max_latency_bucket), | ||
n_buckets_(n_buckets) { | ||
CHECK(!metric_name_prefix_.empty()); | ||
resolver_ = MakeGarbageCollected<ScriptPromiseResolver>(script_state); | ||
if (timeout_interval.is_positive()) { | ||
ExecutionContext::From(script_state) | ||
->GetTaskRunner(TaskType::kInternalDefault) | ||
->PostDelayedTask( | ||
FROM_HERE, | ||
WTF::BindOnce(&ScriptPromiseResultTracker::RecordResult, | ||
WrapPersistent(this), ResultEnumType::kTimedOut), | ||
timeout_interval); | ||
} | ||
} | ||
ScriptPromiseResultTracker(const ScriptPromiseResultTracker&) = delete; | ||
ScriptPromiseResultTracker& operator=(const ScriptPromiseResultTracker&) = | ||
delete; | ||
~ScriptPromiseResultTracker() = default; | ||
|
||
template <typename T> | ||
void Resolve(T value, ResultEnumType result = ResultEnumType::kOk) { | ||
RecordResult(result); | ||
RecordLatency(); | ||
resolver_->Resolve(value); | ||
} | ||
|
||
template <typename T> | ||
void Reject(T value, ResultEnumType result) { | ||
RecordResult(result); | ||
RecordLatency(); | ||
resolver_->Reject(value); | ||
} | ||
|
||
void RecordResult(ResultEnumType result) { | ||
if (is_result_recorded_) | ||
return; | ||
|
||
is_result_recorded_ = true; | ||
base::UmaHistogramEnumeration(metric_name_prefix_ + ".Result", result); | ||
} | ||
|
||
void RecordLatency() { | ||
if (is_latency_recorded_) | ||
return; | ||
|
||
is_latency_recorded_ = true; | ||
const base::TimeDelta elapsed = base::TimeTicks::Now() - start_time_; | ||
base::UmaHistogramCustomTimes(metric_name_prefix_ + ".Latency", elapsed, | ||
min_latency_bucket_, max_latency_bucket_, | ||
n_buckets_); | ||
} | ||
|
||
ScriptState* GetScriptState() const { return resolver_->GetScriptState(); } | ||
|
||
ScriptPromise Promise() { return resolver_->Promise(); } | ||
|
||
void Trace(Visitor* visitor) const { visitor->Trace(resolver_); } | ||
|
||
private: | ||
Member<ScriptPromiseResolver> resolver_; | ||
std::string metric_name_prefix_; | ||
base::TimeTicks start_time_; | ||
base::TimeDelta timeout_interval_; | ||
base::TimeDelta min_latency_bucket_; | ||
base::TimeDelta max_latency_bucket_; | ||
size_t n_buckets_; | ||
bool is_latency_recorded_ = false; | ||
bool is_result_recorded_ = false; | ||
}; | ||
|
||
} // namespace blink | ||
|
||
#endif // THIRD_PARTY_BLINK_RENDERER_BINDINGS_CORE_V8_SCRIPT_PROMISE_RESULT_TRACKER_H_ |
181 changes: 181 additions & 0 deletions
181
third_party/blink/renderer/bindings/core/v8/script_promise_result_tracker_test.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
// Copyright 2022 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "third_party/blink/renderer/bindings/core/v8/script_promise_result_tracker.h" | ||
|
||
#include "base/test/metrics/histogram_tester.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
#include "third_party/blink/renderer/bindings/core/v8/script_function.h" | ||
#include "third_party/blink/renderer/bindings/core/v8/script_value.h" | ||
#include "third_party/blink/renderer/core/frame/local_dom_window.h" | ||
#include "third_party/blink/renderer/core/frame/local_frame.h" | ||
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h" | ||
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h" | ||
#include "v8/include/v8.h" | ||
|
||
namespace blink { | ||
|
||
class TestHelperFunction : public ScriptFunction::Callable { | ||
public: | ||
explicit TestHelperFunction(String* value) : value_(value) {} | ||
|
||
ScriptValue Call(ScriptState* script_state, ScriptValue value) override { | ||
DCHECK(!value.IsEmpty()); | ||
*value_ = ToCoreString( | ||
value.V8Value()->ToString(script_state->GetContext()).ToLocalChecked()); | ||
return value; | ||
} | ||
|
||
private: | ||
String* value_; | ||
}; | ||
|
||
enum class TestEnum { | ||
kOk = 0, | ||
kFailedWithReason = 1, | ||
kTimedOut = 2, | ||
kMaxValue = kTimedOut | ||
}; | ||
|
||
class ScriptPromiseResultTrackerTest : public testing::Test { | ||
public: | ||
ScriptPromiseResultTrackerTest() | ||
: metric_name_prefix_("Histogram.TestEnum"), | ||
page_holder_(std::make_unique<DummyPageHolder>()) {} | ||
|
||
~ScriptPromiseResultTrackerTest() override { PerformMicrotaskCheckpoint(); } | ||
|
||
ScriptState* GetScriptState() const { | ||
return ToScriptStateForMainWorld(&page_holder_->GetFrame()); | ||
} | ||
|
||
void PerformMicrotaskCheckpoint() { | ||
ScriptState::Scope scope(GetScriptState()); | ||
GetScriptState()->GetContext()->GetMicrotaskQueue()->PerformCheckpoint( | ||
GetScriptState()->GetIsolate()); | ||
} | ||
|
||
ScriptPromiseResultTracker<TestEnum>* CreateResultTracker( | ||
String& on_fulfilled, | ||
String& on_rejected, | ||
base::TimeDelta timeout_delay = base::Minutes(1)) { | ||
ScriptState::Scope scope(GetScriptState()); | ||
auto* result_tracker = | ||
MakeGarbageCollected<ScriptPromiseResultTracker<TestEnum>>( | ||
GetScriptState(), metric_name_prefix_, timeout_delay); | ||
|
||
ScriptPromise promise = result_tracker->Promise(); | ||
promise.Then(MakeGarbageCollected<ScriptFunction>( | ||
GetScriptState(), | ||
MakeGarbageCollected<TestHelperFunction>(&on_fulfilled)), | ||
MakeGarbageCollected<ScriptFunction>( | ||
GetScriptState(), | ||
MakeGarbageCollected<TestHelperFunction>(&on_rejected))); | ||
|
||
PerformMicrotaskCheckpoint(); | ||
|
||
CheckResultHistogram(/*expected_count=*/0); | ||
CheckLatencyHistogram(/*expected_count=*/0); | ||
return result_tracker; | ||
} | ||
|
||
void CheckResultHistogram(int expected_count) { | ||
histogram_tester_.ExpectTotalCount(metric_name_prefix_ + ".Result", | ||
expected_count); | ||
} | ||
|
||
void CheckLatencyHistogram(int expected_count) { | ||
histogram_tester_.ExpectTotalCount(metric_name_prefix_ + ".Latency", | ||
expected_count); | ||
} | ||
|
||
protected: | ||
base::HistogramTester histogram_tester_; | ||
std::string metric_name_prefix_; | ||
std::unique_ptr<DummyPageHolder> page_holder_; | ||
}; | ||
|
||
TEST_F(ScriptPromiseResultTrackerTest, resolve) { | ||
String on_fulfilled, on_rejected; | ||
auto* result_tracker = CreateResultTracker(on_fulfilled, on_rejected); | ||
result_tracker->Resolve(/*value=*/"hello", /*result=*/TestEnum::kOk); | ||
PerformMicrotaskCheckpoint(); | ||
|
||
EXPECT_EQ("hello", on_fulfilled); | ||
EXPECT_EQ(String(), on_rejected); | ||
CheckResultHistogram(/*expected_count=*/1); | ||
CheckLatencyHistogram(/*expected_count=*/1); | ||
} | ||
|
||
TEST_F(ScriptPromiseResultTrackerTest, reject) { | ||
String on_fulfilled, on_rejected; | ||
auto* result_tracker = CreateResultTracker(on_fulfilled, on_rejected); | ||
result_tracker->Reject(/*value=*/"hello", | ||
/*result=*/TestEnum::kFailedWithReason); | ||
PerformMicrotaskCheckpoint(); | ||
|
||
EXPECT_EQ(String(), on_fulfilled); | ||
EXPECT_EQ("hello", on_rejected); | ||
CheckResultHistogram(/*expected_count=*/1); | ||
CheckLatencyHistogram(/*expected_count=*/1); | ||
} | ||
|
||
TEST_F(ScriptPromiseResultTrackerTest, resolve_reject_again) { | ||
String on_fulfilled, on_rejected; | ||
auto* result_tracker = CreateResultTracker(on_fulfilled, on_rejected); | ||
result_tracker->Reject(/*value=*/"hello", | ||
/*result=*/TestEnum::kFailedWithReason); | ||
PerformMicrotaskCheckpoint(); | ||
|
||
EXPECT_EQ(String(), on_fulfilled); | ||
EXPECT_EQ("hello", on_rejected); | ||
CheckResultHistogram(/*expected_count=*/1); | ||
CheckLatencyHistogram(/*expected_count=*/1); | ||
|
||
// Resolve/Reject on already resolved/rejected promise doesn't log new values | ||
// in the histogram. | ||
result_tracker->Resolve(/*value=*/"bye", /*result=*/TestEnum::kOk); | ||
result_tracker->Reject(/*value=*/"bye", | ||
/*result=*/TestEnum::kFailedWithReason); | ||
PerformMicrotaskCheckpoint(); | ||
|
||
EXPECT_EQ(String(), on_fulfilled); | ||
EXPECT_EQ("hello", on_rejected); | ||
CheckResultHistogram(/*expected_count=*/1); | ||
CheckLatencyHistogram(/*expected_count=*/1); | ||
} | ||
|
||
TEST_F(ScriptPromiseResultTrackerTest, timeout) { | ||
String on_fulfilled, on_rejected; | ||
base::TimeDelta timeout_delay = base::Milliseconds(200); | ||
auto* result_tracker = | ||
CreateResultTracker(on_fulfilled, on_rejected, timeout_delay); | ||
|
||
// Run the tasks scheduled to run within the delay specified. | ||
test::RunDelayedTasks(timeout_delay); | ||
PerformMicrotaskCheckpoint(); | ||
|
||
// kTimedOut is logged in the Result histogram but nothing is logged in the | ||
// latency histogram as the promise was never rejected or resolved. | ||
CheckResultHistogram(/*expected_count=*/1); | ||
CheckLatencyHistogram(/*expected_count=*/0); | ||
|
||
// Though the timeout has passed, the promise is not yet rejected or resolved. | ||
EXPECT_EQ(String(), on_fulfilled); | ||
EXPECT_EQ(String(), on_rejected); | ||
|
||
result_tracker->Reject(/*value=*/"hello", | ||
/*result=*/TestEnum::kFailedWithReason); | ||
PerformMicrotaskCheckpoint(); | ||
|
||
EXPECT_EQ("hello", on_rejected); | ||
EXPECT_EQ(String(), on_fulfilled); | ||
|
||
// Rejected result is not logged again as it was rejected after the timeout | ||
// had passed. It is still logged in the latency though. | ||
CheckResultHistogram(/*expected_count=*/1); | ||
CheckLatencyHistogram(/*expected_count=*/1); | ||
} | ||
|
||
} // namespace blink |