Bug 1804638 - Add telemetry probes to measure ORB JavaScript performance. r=necko-reviewers,smaug,sefeng,kershaw

Differential Revision: https://phabricator.services.mozilla.com/D164568
This commit is contained in:
Andreas Farre 2023-01-17 14:03:41 +00:00
Родитель 03c58d765c
Коммит 5a0687b02f
10 изменённых файлов: 178 добавлений и 35 удалений

Просмотреть файл

@ -23,7 +23,7 @@ mozilla::ipc::IPCResult JSValidatorChild::RecvOnDataAvailable(Shmem&& aData) {
mozilla::fallible)) {
// To prevent an attacker from flood the validation process,
// we don't validate here.
Resolve(false);
Resolve(ValidatorResult::Failure);
}
DeallocShmem(aData);
@ -37,7 +37,7 @@ mozilla::ipc::IPCResult JSValidatorChild::RecvOnStopRequest(
}
if (NS_FAILED(aReason)) {
Resolve(false);
Resolve(ValidatorResult::Failure);
} else {
Resolve(ShouldAllowJS());
}
@ -47,35 +47,41 @@ mozilla::ipc::IPCResult JSValidatorChild::RecvOnStopRequest(
void JSValidatorChild::ActorDestroy(ActorDestroyReason aReason) {
if (mResolver) {
Resolve(false);
Resolve(ValidatorResult::Failure);
}
};
void JSValidatorChild::Resolve(bool aAllow) {
void JSValidatorChild::Resolve(ValidatorResult aResult) {
MOZ_ASSERT(mResolver);
Maybe<Shmem> data = Nothing();
if (aAllow) {
if (!mSourceBytes.IsEmpty()) {
Shmem sharedData;
nsresult rv =
JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes, sharedData);
if (NS_SUCCEEDED(rv)) {
data = Some(std::move(sharedData));
}
if (aResult == ValidatorResult::JavaScript && !mSourceBytes.IsEmpty()) {
Shmem sharedData;
nsresult rv =
JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes, sharedData);
if (NS_SUCCEEDED(rv)) {
data = Some(std::move(sharedData));
}
}
mResolver.ref()(
Tuple<const bool&, mozilla::Maybe<Shmem>&&>(aAllow, std::move(data)));
mResolver = Nothing();
mResolver.ref()(Tuple<mozilla::Maybe<Shmem>&&, const ValidatorResult&>(
std::move(data), aResult));
mResolver.reset();
}
bool JSValidatorChild::ShouldAllowJS() const {
JSValidatorChild::ValidatorResult JSValidatorChild::ShouldAllowJS() const {
// mSourceBytes could be empty when
// 1. No OnDataAvailable calls
// 2. Failed to allocate shmem
//
// TODO(sefeng): THIS IS A VERY TEMPORARY SOLUTION
return !mSourceBytes.IsEmpty()
? !StringBeginsWith(NS_ConvertUTF8toUTF16(mSourceBytes), u"{"_ns)
: true;
// The empty document parses as JavaScript, so for clarity we have a condition
// separately for that.
if (mSourceBytes.IsEmpty()) {
return ValidatorResult::JavaScript;
}
if (StringBeginsWith(NS_ConvertUTF8toUTF16(mSourceBytes), u"{"_ns)) {
return ValidatorResult::JSON;
}
return ValidatorResult::JavaScript;
}

Просмотреть файл

@ -10,6 +10,7 @@
#include "mozilla/ProcInfo.h"
#include "mozilla/dom/PJSValidatorChild.h"
#include "mozilla/dom/JSValidatorUtils.h"
#include "mozilla/net/OpaqueResponseUtils.h"
namespace mozilla::dom {
class JSValidatorChild final : public PJSValidatorChild {
@ -28,8 +29,9 @@ class JSValidatorChild final : public PJSValidatorChild {
private:
virtual ~JSValidatorChild() = default;
void Resolve(bool aAllow);
bool ShouldAllowJS() const;
using ValidatorResult = net::OpaqueResponseBlocker::ValidatorResult;
void Resolve(ValidatorResult aResult);
ValidatorResult ShouldAllowJS() const;
nsCString mSourceBytes;
Maybe<IsOpaqueResponseAllowedResolver> mResolver;

Просмотреть файл

@ -24,7 +24,7 @@ already_AddRefed<JSValidatorParent> JSValidatorParent::Create() {
}
void JSValidatorParent::IsOpaqueResponseAllowed(
const std::function<void(bool, Maybe<Shmem>)>& aCallback) {
const std::function<void(Maybe<Shmem>, ValidatorResult)>& aCallback) {
JSOracleParent::WithJSOracle([=, self = RefPtr{this}](const auto* aParent) {
if (aParent) {
MOZ_DIAGNOSTIC_ASSERT(self->CanSend());
@ -34,14 +34,16 @@ void JSValidatorParent::IsOpaqueResponseAllowed(
const IsOpaqueResponseAllowedPromise::ResolveOrRejectValue&
aResult) {
if (aResult.IsResolve()) {
const Tuple<bool, Maybe<Shmem>>& result = aResult.ResolveValue();
aCallback(Get<0>(result), Get<1>(aResult.ResolveValue()));
Maybe<Shmem> data;
ValidatorResult result;
Tie(data, result) = aResult.ResolveValue();
aCallback(std::move(data), result);
} else {
aCallback(false, Nothing());
aCallback(Nothing(), ValidatorResult::Failure);
}
});
} else {
aCallback(false, Nothing());
aCallback(Nothing(), ValidatorResult::Failure);
}
});
}

Просмотреть файл

@ -25,7 +25,8 @@ class JSValidatorParent final : public PJSValidatorParent {
static already_AddRefed<JSValidatorParent> Create();
void IsOpaqueResponseAllowed(
const std::function<void(bool, Maybe<mozilla::ipc::Shmem>)>& aCallback);
const std::function<void(Maybe<mozilla::ipc::Shmem>, ValidatorResult)>&
aCallback);
void OnDataAvailable(const nsACString& aData);

Просмотреть файл

@ -5,6 +5,8 @@
include protocol PJSOracle;
using mozilla::net::OpaqueResponseBlocker::ValidatorResult from "mozilla/net/OpaqueResponseUtils.h";
namespace mozilla {
namespace dom {
@ -12,7 +14,7 @@ async protocol PJSValidator {
manager PJSOracle;
child:
async IsOpaqueResponseAllowed() returns(bool aAllowed, Shmem? aMem);
async IsOpaqueResponseAllowed() returns (Shmem? aMem, ValidatorResult aResult);
async OnDataAvailable(Shmem aData);

Просмотреть файл

@ -3099,6 +3099,11 @@ HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
return OpaqueResponse::Allow;
}
Telemetry::ScalarAdd(
Telemetry::ScalarID::
OPAQUE_RESPONSE_BLOCKING_CROSS_ORIGIN_OPAQUE_RESPONSE_COUNT,
1);
// https://whatpr.org/fetch/1442.html#orb-algorithm
// Step 1
nsAutoCString contentType;

Просмотреть файл

@ -8,6 +8,7 @@
#include "mozilla/dom/Document.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/dom/JSValidatorParent.h"
#include "ErrorList.h"
#include "nsContentUtils.h"
#include "nsHttpResponseHead.h"
@ -269,6 +270,10 @@ OpaqueResponseBlocker::OnStopRequest(nsIRequest* aRequest,
}
if (mState == State::Sniffing) {
// It is the call to JSValidatorParent::OnStopRequest that will trigger the
// JS parser.
mStartOfJavaScriptValidation = TimeStamp::Now();
MOZ_ASSERT(mJSValidator);
mPendingOnStopRequestStatus = Some(aStatusCode);
mJSValidator->OnStopRequest(aStatusCode);
@ -360,6 +365,36 @@ nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff(
return ValidateJavaScript(httpBaseChannel, uri, loadInfo);
}
static void RecordTelemetry(const TimeStamp& aStartOfValidation,
const TimeStamp& aStartOfJavaScriptValidation,
OpaqueResponseBlocker::ValidatorResult aResult) {
using ValidatorResult = OpaqueResponseBlocker::ValidatorResult;
MOZ_DIAGNOSTIC_ASSERT(aStartOfValidation);
auto key = [aResult]() {
switch (aResult) {
case ValidatorResult::JavaScript:
return "javascript"_ns;
case ValidatorResult::JSON:
return "json"_ns;
case ValidatorResult::Other:
return "other"_ns;
case ValidatorResult::Failure:
return "failure"_ns;
}
MOZ_ASSERT_UNREACHABLE("Switch statement should be saturated");
return "failure"_ns;
}();
TimeStamp now = TimeStamp::Now();
Telemetry::AccumulateTimeDelta(Telemetry::ORB_RECEIVE_DATA_FOR_VALIDATION_MS,
key, aStartOfValidation,
aStartOfJavaScriptValidation);
Telemetry::AccumulateTimeDelta(Telemetry::ORB_JAVASCRIPT_VALIDATION_MS, key,
aStartOfJavaScriptValidation, now);
}
// The specification for ORB is currently being written:
// https://whatpr.org/fetch/1442.html#orb-algorithm
// The `opaque-response-safelist check` is implemented in:
@ -387,28 +422,36 @@ nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel,
return rv;
}
Telemetry::ScalarAdd(
Telemetry::ScalarID::OPAQUE_RESPONSE_BLOCKING_JAVASCRIPT_VALIDATION_COUNT,
1);
LOGORB("Send %s to the validator", aURI->GetSpecOrDefault().get());
// https://whatpr.org/fetch/1442.html#orb-algorithm, step 15
mJSValidator = dom::JSValidatorParent::Create();
mJSValidator->IsOpaqueResponseAllowed(
[self = RefPtr{this}, channel = nsCOMPtr{aChannel}, uri = nsCOMPtr{aURI},
loadInfo = nsCOMPtr{aLoadInfo}](bool aAllowed,
Maybe<ipc::Shmem> aSharedData) {
loadInfo = nsCOMPtr{aLoadInfo}, startOfValidation = TimeStamp::Now()](
Maybe<ipc::Shmem> aSharedData, ValidatorResult aResult) {
MOZ_LOG(gORBLog, LogLevel::Debug,
("JSValidator resolved for %s with %s",
uri->GetSpecOrDefault().get(),
aSharedData.isSome() ? "true" : "false"));
if (aAllowed) {
bool allowed = aResult == ValidatorResult::JavaScript;
if (allowed) {
self->AllowResponse();
} else {
self->BlockResponse(channel, NS_ERROR_FAILURE);
LogORBError(loadInfo, uri);
}
self->ResolveAndProcessData(channel, aAllowed, aSharedData);
self->ResolveAndProcessData(channel, allowed, aSharedData);
if (aSharedData.isSome()) {
self->mJSValidator->DeallocShmem(aSharedData.ref());
}
RecordTelemetry(startOfValidation, self->mStartOfJavaScriptValidation,
aResult);
Unused << dom::PJSValidatorParent::Send__delete__(self->mJSValidator);
self->mJSValidator = nullptr;
});

Просмотреть файл

@ -8,7 +8,8 @@
#ifndef mozilla_net_OpaqueResponseUtils_h
#define mozilla_net_OpaqueResponseUtils_h
#include "mozilla/dom/JSValidatorParent.h"
#include "ipc/EnumSerializer.h"
#include "mozilla/TimeStamp.h"
#include "nsIContentPolicy.h"
#include "nsIStreamListener.h"
#include "nsUnknownDecoder.h"
@ -25,6 +26,14 @@
class nsIContentSniffer;
static mozilla::LazyLogModule gORBLog("ORB");
namespace mozilla::dom {
class JSValidatorParent;
}
namespace mozilla::ipc {
class Shmem;
}
namespace mozilla::net {
class HttpBaseChannel;
@ -70,6 +79,14 @@ class OpaqueResponseBlocker final : public nsIStreamListener {
nsresult EnsureOpaqueResponseIsAllowedAfterSniff(nsIRequest* aRequest);
// The four possible results for validation. `JavaScript` and `JSON` are
// self-explanatory. `JavaScript` is the only successful result, in the sense
// that it will allow the opaque response, whereas `JSON` will block. `Other`
// is the case where validation fails, because the response is neither
// `JavaScript` nor `JSON`, but the framework itself works as intended.
// `Failure` implies that something has gone wrong, such as allocation, etc.
enum class ValidatorResult : uint32_t { JavaScript, JSON, Other, Failure };
private:
virtual ~OpaqueResponseBlocker() = default;
@ -89,6 +106,8 @@ class OpaqueResponseBlocker final : public nsIStreamListener {
State mState = State::Sniffing;
nsresult mStatus = NS_OK;
TimeStamp mStartOfJavaScriptValidation;
RefPtr<dom::JSValidatorParent> mJSValidator;
Maybe<nsresult> mPendingOnStopRequestStatus{Nothing()};
@ -142,4 +161,13 @@ class nsCompressedAudioVideoImageDetector : public nsUnknownDecoder {
};
} // namespace mozilla::net
namespace IPC {
template <>
struct ParamTraits<mozilla::net::OpaqueResponseBlocker::ValidatorResult>
: public ContiguousEnumSerializerInclusive<
mozilla::net::OpaqueResponseBlocker::ValidatorResult,
mozilla::net::OpaqueResponseBlocker::ValidatorResult::JavaScript,
mozilla::net::OpaqueResponseBlocker::ValidatorResult::Failure> {};
} // namespace IPC
#endif // mozilla_net_OpaqueResponseUtils_h

Просмотреть файл

@ -17151,5 +17151,31 @@
"n_buckets": 100,
"description": "The length of time (in seconds) that a Picture-in-Picture window was open.",
"releaseChannelCollection": "opt-out"
},
"ORB_JAVASCRIPT_VALIDATION_MS": {
"record_in_processes": ["main"],
"products": ["firefox"],
"alert_emails": ["farre@mozilla.com"],
"expires_in_version": "never",
"kind": "exponential",
"keyed": true,
"keys": ["javascript", "json", "other", "failure"],
"high": 10000,
"n_buckets": 50,
"bug_numbers": [1804638],
"description": "Time (in ms) that it takes for a ORB JavaScript validator to complete a validation, including IPC to the validator actor."
},
"ORB_RECEIVE_DATA_FOR_VALIDATION_MS": {
"record_in_processes": ["main"],
"products": ["firefox"],
"alert_emails": ["farre@mozilla.com"],
"expires_in_version": "never",
"kind": "exponential",
"keyed": true,
"keys": ["javascript", "json", "other", "failure"],
"high": 10000,
"n_buckets": 50,
"bug_numbers": [1804638],
"description": "Time (in ms) that it takes to receive data for ORB JavaScript validation, including IPC to the validator actor."
}
}

Просмотреть файл

@ -8985,6 +8985,34 @@ opaque.response.blocking:
- 'main'
release_channel_collection: opt-out
cross_origin_opaque_response_count:
bug_numbers:
- 1804638
description: >
The number of loads of cross origin opaque resources.
expires: never
kind: uint
notification_emails:
- farre@mozilla.com
products:
- 'firefox'
record_in_processes:
- 'main'
javascript_validation_count:
bug_numbers:
- 1804638
description: >
The number of times we run the JS validator.
expires: never
kind: uint
notification_emails:
- farre@mozilla.com
products:
- 'firefox'
record_in_processes:
- 'main'
places:
sponsored_visit_no_triggering_url:
bug_numbers: