이 명세는 클라이언트 JavaScript 실행 시간을 측정하기 위한 샘플링 프로파일러를 웹 애플리케이션이 제어할 수 있도록 하는 API를 설명한다.
현재 복잡한 웹 애플리케이션은 클라이언트에서 JS 실행 시간이 어디에 소비되는지 파악할 수 있는 수단이 제한적이다. 스택 샘플을 효율적으로 수집할 수 없으므로, 애플리케이션은 부정확하고 실행 속도를 크게 저하시킬 수 있는 프로파일링 훅을 코드에 계측해야 한다. 샘플링 프로파일러를 조작하는 API를 제공함으로써 애플리케이션은 최소한의 오버헤드로 집계 및 분석을 위한 풍부한 실행 데이터를 수집할 수 있다.
다음 예시는 사용자가 비용이 많이 드는 작업을 프로파일링하면서 10ms마다 JS 실행 샘플을 수집하는 방법을 보여 준다. 이상값을 디버깅하고 집계된 JS 실행 특성을 분석하기 위해 추적 데이터를 서버로 전송할 수 있다.
const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
const start = performance.now();
for (let i = 0; i < 1000000; i++) {
doWork();
}
const duration = performance.now() - start;
const trace = await profiler.stop();
const traceJson = JSON.stringify({
duration,
trace,
});
sendTrace(traceJson);
또 다른 일반적인 실제 사용 사례는 페이지 로드 전반에서 JS를 프로파일링하는 것이다. 이 예시는 onload 이벤트를 프로파일링하고 추적 데이터와 함께 성능 타이밍 데이터를 전송한다.
const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
window.addEventListener('load', async () => {
const trace = await profiler.stop();
const traceJson = JSON.stringify({
timing: performance.timing,
trace,
});
sendTrace(traceJson);
});
// 페이지의 나머지 JS 초기화 로직
샘플은 주어진 시점에서의 순간적인 실행 상태를 설명하는 기술자이다. 각 샘플은 스택과 연결된다.
스택은 가장 바깥쪽 프레임부터 가장 안쪽 프레임까지 순차적으로 정렬되어야 하는 프레임 목록이다.
프레임은 현재 실행 상태에 관한 정보를 포함하는 스택 컨텍스트의 요소이다.
프로파일링 세션은 샘플을 생성하는 추상적인 생성자이다. 각 세션에는 다음이 있다.
{started, paused, stopped} 중 하나인 상태.UA는 반드시 이 빈도로 샘플을 수집할 필요는 없다. 그러나 더 높은 품질의 추적 데이터를 생성하기 위해 이 빈도로 샘플을 수집하도록 샘플링에 우선순위를 부여하는 것이 좋다.
동일한 페이지에서 여러 프로파일링 세션을 지원하는 것이 좋다.
started 상태에서 UA는 샘플 간격이 경과할 때마다
샘플 수집 알고리즘을 [= in parallel =] 실행하여 샘플을 캡처하도록
최선의 노력을 기울이는 것이 좋다.
paused 및 stopped 상태에서 UA는 샘플을 캡처하지 않는 것이 좋다.
프로파일링 세션은 반드시 started 상태에서 시작해야 한다.
UA는 세션을 started에서 paused로, 그리고 paused에서
started로 전환할 수 있다.
브라우징 컨텍스트가 포그라운드에 있지 않은 경우 사용자 에이전트는 프로파일링 세션의 샘플링을 일시 중지하는 것이 좋다.
stopped 세션은 started 또는 paused 상태로 전환해서는 안 된다.
프로파일링 세션이 주어졌을 때 샘플을 수집하려면 다음 단계를 수행한다.
stopped로 전환한 후
반환한다.
stack에 바인딩된 실행 컨텍스트 스택이 주어졌을 때 스택 ID를 가져오려면 다음 단계를 수행한다.
undefined를 반환한다.undefined이면 parentId를 반환한다.context에 바인딩된 실행 컨텍스트가 주어졌을 때 프레임 ID를 가져오려면 다음 단계를 수행한다.
undefined를 반환한다.
ScriptOrModule과
같게 설정한다.
ScriptOrModule을 반환한다.
위 로직의 목적은 접근할 수 없는 스크립트가 호출한 내장 함수가 추적 데이터에
노출되지 않도록, 해당 함수의 귀속에 그 함수를 호출한 ScriptOrModule을
사용하는 것이다.
“|instance|를 호출한 함수를 포함하는 ScriptOrModule”은
더 엄밀하게 정의해야 한다. 이를 제공하기 위해 스택에서
ScriptOrModule을 정의하는 최상위 실행 컨텍스트를 활용할 수 있지만,
이는 이상적이지 않다. 이론적으로 내장 함수가 실행 컨텍스트 스택에 대기열로 추가되는
다른 메커니즘이 존재할 수 있으며, 이 경우 귀속은 유효하지 않게 된다.
undefined를 반환한다.true와 같으면 undefined를 반환한다.
이 검사는 CORS 교차 출처 응답으로 제공된 교차 출처 스크립트의 스택 프레임을 포함하지 않도록 보장한다. 이 사용 사례를 더 잘 반영하도록 오류 음소거의 이름을 변경하는 것을 고려할 수 있다.
list에 있는 item의 요소 ID를 가져오려면 다음 단계를 실행한다.
[Exposed=(Window, Worker)]
interface Profiler : EventTarget {
readonly attribute DOMHighResTimeStamp sampleInterval;
readonly attribute boolean stopped;
constructor(ProfilerInitOptions options);
Promise<ProfilerTrace> stop();
};
각 Profiler는 반드시 정확히 하나의 프로파일링 세션과 연결되어야 한다.
sampleInterval 속성은 연결된 프로파일링 세션의 샘플 간격을 DOMHighResTimeStamp로 표현하여 반영해야 한다.
stopped 속성은 프로파일링 세션의 상태가
stopped인 경우에만 true여야 한다.
RangeError를 발생시킨다.
"js-profiling-mode"에 대한
정책
값을 가져온다. 그 결과를 |profilingMode|로 설정한다.
"eager" 또는 "lazy"가 아니면:
"js-profiling"에 대한
정책
값을 가져온다. 그 결과를 |jsProfilingEnabled|로 설정한다.
"NotAllowedError"
DOMException을 발생시킨다.
"NotAllowedError" DOMException을 발생시킨다.
«[{{ProfilerTrace/resources}} → «», {{ProfilerTrace/frames}} → «», {{ProfilerTrace/stacks}} → «», {{ProfilerTrace/samples}} → «»]»로
설정한다.
프로파일러를 중지하고 추적 데이터를 반환한다. 이 메서드는 반드시 다음 단계를 실행해야 한다.
stopped이면, "InvalidStateError"
DOMException으로 [= a promise
rejected with =]를 반환한다.
stopped로 설정한다.
typedef DOMString ProfilerResource;
dictionary ProfilerTrace {
required sequence<ProfilerResource> resources;
required sequence<ProfilerFrame> frames;
required sequence<ProfilerStack> stacks;
required sequence<ProfilerSample> samples;
};
resources 속성은 샘플 수집 알고리즘이 설정한 ProfilerResource 목록을 반환해야 한다.
frames 속성은 샘플 수집 알고리즘이 설정한 ProfilerFrame 목록을 반환해야 한다.
stacks 속성은 샘플 수집 알고리즘이 설정한 ProfilerStack 목록을 반환해야 한다.
samples 속성은 샘플 수집 알고리즘이 설정한 ProfilerSample 목록을 반환해야 한다.
이 표현은 V8 추적 이벤트 형식 및 Gecko 프로파일 형식에서 영감을 얻었으며, 쉽고 효율적으로 직렬화할 수 있도록 설계되었다.
dictionary ProfilerSample {
required DOMHighResTimeStamp timestamp;
unsigned long long stackId;
};
timestamp는 초기화된 값을 반환해야 한다.
stackId는 초기화된 값을 반환해야 한다.
dictionary ProfilerStack {
unsigned long long parentId;
required unsigned long long frameId;
};
parentId는 초기화된 값을 반환해야 한다.
frameId는 초기화된 값을 반환해야 한다.
dictionary ProfilerFrame {
required DOMString name;
unsigned long long resourceId;
unsigned long long line;
unsigned long long column;
};
name은 초기화된 값을 반환해야 한다.
resourceId는 초기화된 값을 반환해야 한다.
line은 초기화된 값을 반환해야 한다.
column은 초기화된 값을 반환해야 한다.
dictionary ProfilerInitOptions {
required DOMHighResTimeStamp sampleInterval;
required unsigned long maxBufferSize;
};
ProfilerInitOptions는 다음 필드를 지원해야 한다.
이 명세는 프로파일링 기능과 초기화 동작을 제어하기 위해 문서 정책의 구성 지점을 정의한다.
js-profiling-mode
이 명세는 이름이 js-profiling-mode인 구성
지점을 정의한다. 해당 유형은
enum이며 허용되는 값은 "eager"와 "lazy"이다. 기본
값은 빈 문자열이다.
이 정책이 설정되면 스크립트가 JS 자체 프로파일링 API를 사용하도록 허용한다.
eager
js-profiling-mode가 "eager"로 설정되면 페이지 로드 중에
프로파일링이 예상된다는 것을 UA에 알린다.
UA는 이 힌트를 사용하여 프로파일링 인프라를 조기에 초기화하고, 문서 로드 중 가능한 한 이른 시점에 필요한 메타데이터를 저장하고 프로파일링 구성 요소를 준비할 수 있다. 그러나 프로파일링을 실제로 사용하지 않더라도 무시할 수 없는 성능 오버헤드가 발생할 수 있다. 이 오버헤드는 최초 콘텐츠풀 페인트(FCP) 및 최대 콘텐츠풀 페인트(LCP)와 같은 지표에 부정적인 영향을 줄 수 있다.
lazy
js-profiling-mode가 "lazy"로 설정되면 프로파일링이
조건부로 사용될 것임을 UA에 알린다.
UA는 이 힌트를 사용하여 첫 번째 Profiler가 인스턴스화될 때까지 프로파일링 관련 초기화 오버헤드를 지연함으로써, 프로파일링을 실제로 사용하지 않을 때 중요한 렌더링 기간의 성능 비용을 방지할 수 있다. 그러나 사용자 상호작용을 처리하는 동안 초기화가 발생하면 다음 페인트까지의 상호작용(INP)에 부정적인 영향을 줄 수 있다. 이 모드는 샘플링 결정에 따라 프로파일링을 조건부로 활성화하는 문서에 특히 적합하다.
js-profiling(사용 중단됨)
이 명세는 이름이 js-profiling인 구성
지점도 정의한다. 해당 유형은
boolean이며 기본
값은 false이다.
이 정책이 활성화되면 스크립트가 JS 자체 프로파일링 API를 사용하도록 허용하고, UA에
프로파일링 인프라를 조기에 초기화하도록 알린다. 이는 의미론적으로
js-profiling-mode=eager와 동일하다.
js-profiling 불리언 구성 지점은 js-profiling-mode를 위해
사용 중단되었다. 둘 다 지정된 경우 js-profiling-mode가 우선하며
js-profiling은 반드시 무시해야 한다. 구현은 이전 버전과의 호환성을 위해
js-profiling을 지원하는 것이 좋지만 향후 지원을 제거할 수 있다.
사용자 에이전트 자동화 및 애플리케이션 테스트를 위해 이 문서는 다음 [[WebDriver]] 확장 명령을 정의한다.
| HTTP 메서드 | URI 템플릿 |
|---|---|
| POST | `/session/{session id}/forcesample` |
강제 샘플 수집 확장 명령은 더 결정적인 테스트를 가능하게 하기 위해 모든 [=프로파일링 세션=]이 [=샘플을 수집=]하도록 강제한다.
원격 끝 단계는 다음과 같다.
started이면 |session|으로 [=샘플을 수집=]한다.
null과 함께 성공을 반환한다.다음 절에서는 API의 개인정보 보호 및 보안 관련 선택 사항을 자세히 설명하고, 다양한 유형의 공격에 대한 보호 전략을 보여 준다.
API는 샘플 수집 알고리즘을 통해 포함되는 모든 함수가 오류 음소거 속성을 이용하여
CORS 동일 출처로 제공된 스크립트에
정의되도록 요구함으로써 교차 출처 스크립트의 콘텐츠가 노출되는 것을 방지한다.
브라우저 내장 기능(예: performance.now())도 [= CORS-same-origin =] 스크립트에서
호출된 경우에만 포함해야 한다.
따라서 API는 수동 계측으로 이미 확인할 수 있는 것 이상으로 교차 출처 스크립트의 콘텐츠나 실행 특성에 관한 새로운 정보를 노출하지 않는다. UA가 매우 낮은 샘플 간격 값 (예: 1밀리초 미만)을 지원하기로 선택한 경우에도 이 특성이 유지되는지 확인하는 것이 권장된다.
교차 출처 실행 컨텍스트는 샘플 수집 알고리즘의 렐름 검사를 통해 API에서
관찰할 수 없어야 한다. 따라서 프로파일러와 에이전트를 공유하는 교차 출처
iframe 및 기타 실행 컨텍스트의 실행은 이 API를 통해 관찰할 수 없다.
새로운 고해상도 타이밍 정보 소스를 도입할 수 있는 모든 API에서 타이밍 공격은 여전히 우려 사항이다. 추적 데이터에서 수집되는 타임스탬프는 새로운 부채널 공격 벡터가 노출되지 않도록 [[?HR-Time]]의 현재 고해상도 시간과 동일한 소스에서 얻어야 한다.
[[?HR-Time]]의 시계 해상도에 관한 논의를 참조한다.