feat: add performance user timing APIs (#6421)

This commit is contained in:
Kitson Kelly 2020-07-11 12:38:15 +10:00 committed by GitHub
parent d01eb6d9c5
commit 40d081d3d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 536 additions and 26 deletions

View file

@ -18,7 +18,6 @@ import * as fetchTypes from "./web/fetch.ts";
import * as headers from "./web/headers.ts";
import * as navigator from "./web/navigator.ts";
import * as permissions from "./web/permissions.ts";
import * as performanceUtil from "./web/performance.ts";
import type * as promiseTypes from "./web/promise.ts";
import * as queuingStrategy from "./web/streams/queuing_strategy.ts";
import * as readableStream from "./web/streams/readable_stream.ts";
@ -29,6 +28,7 @@ import * as transformStream from "./web/streams/transform_stream.ts";
import * as url from "./web/url.ts";
import * as urlSearchParams from "./web/url_search_params.ts";
import * as workers from "./web/workers.ts";
import * as performance from "./web/performance.ts";
import * as writableStream from "./web/streams/writable_stream.ts";
// These imports are not exposed and therefore are fine to just import the
@ -234,12 +234,16 @@ export const windowOrWorkerGlobalScopeProperties = {
Headers: nonEnumerable(headers.HeadersImpl),
navigator: nonEnumerable(new navigator.NavigatorImpl()),
Navigator: nonEnumerable(navigator.NavigatorImpl),
performance: writable(new performanceUtil.Performance()),
Permissions: nonEnumerable(permissions.PermissionsImpl),
PermissionStatus: nonEnumerable(permissions.PermissionStatusImpl),
ReadableStream: nonEnumerable(readableStream.ReadableStreamImpl),
Request: nonEnumerable(request.Request),
Response: nonEnumerable(fetchTypes.Response),
performance: writable(new performance.PerformanceImpl()),
Performance: nonEnumerable(performance.PerformanceImpl),
PerformanceEntry: nonEnumerable(performance.PerformanceEntryImpl),
PerformanceMark: nonEnumerable(performance.PerformanceMarkImpl),
PerformanceMeasure: nonEnumerable(performance.PerformanceMeasureImpl),
TextDecoder: nonEnumerable(textEncoding.TextDecoder),
TextEncoder: nonEnumerable(textEncoding.TextEncoder),
TransformStream: nonEnumerable(transformStream.TransformStreamImpl),

View file

@ -3,6 +3,9 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/** Deno provides extra properties on `import.meta`. These are included here
* to ensure that these are still available when using the Deno namepsace in
* conjunction with other type libs, like `dom`. */
declare interface ImportMeta {
/** A string representation of the fully qualified module URL. */
url: string;
@ -19,6 +22,47 @@ declare interface ImportMeta {
main: boolean;
}
/** Deno supports user timing Level 3 (see: https://w3c.github.io/user-timing)
* which is not widely supported yet in other runtimes. These types are here
* so that these features are still available when using the Deno namespace
* in conjunction with other type libs, like `dom`. */
declare interface Performance {
/** Stores a timestamp with the associated name (a "mark"). */
mark(markName: string, options?: PerformanceMarkOptions): PerformanceMark;
/** Stores the `DOMHighResTimeStamp` duration between two marks along with the
* associated name (a "measure"). */
measure(
measureName: string,
options?: PerformanceMeasureOptions
): PerformanceMeasure;
}
declare interface PerformanceMarkOptions {
/** Metadata to be included in the mark. */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
detail?: any;
/** Timestamp to be used as the mark time. */
startTime?: number;
}
declare interface PerformanceMeasureOptions {
/** Metadata to be included in the measure. */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
detail?: any;
/** Timestamp to be used as the start time or string to be used as start
* mark.*/
start?: string | number;
/** Duration between the start and end times. */
duration?: number;
/** Timestamp to be used as the end time or string to be used as end mark. */
end?: string | number;
}
declare interface Permissions {
/** Resolves to the current status of a permission.
*

View file

@ -1337,7 +1337,36 @@ declare class Worker extends EventTarget {
terminate(): void;
}
declare namespace performance {
declare type PerformanceEntryList = PerformanceEntry[];
declare interface Performance {
/** Removes the stored timestamp with the associated name. */
clearMarks(markName?: string): void;
/** Removes stored timestamp with the associated name. */
clearMeasures(measureName?: string): void;
getEntries(): PerformanceEntryList;
getEntriesByName(name: string, type?: string): PerformanceEntryList;
getEntriesByType(type: string): PerformanceEntryList;
/** Stores a timestamp with the associated name (a "mark"). */
mark(markName: string, options?: PerformanceMarkOptions): PerformanceMark;
/** Stores the `DOMHighResTimeStamp` duration between two marks along with the
* associated name (a "measure"). */
measure(
measureName: string,
options?: PerformanceMeasureOptions
): PerformanceMeasure;
/** Stores the `DOMHighResTimeStamp` duration between two marks along with the
* associated name (a "measure"). */
measure(
measureName: string,
startMark?: string,
endMark?: string
): PerformanceMeasure;
/** Returns a current time from Deno's start in milliseconds.
*
* Use the permission flag `--allow-hrtime` return a precise value.
@ -1347,7 +1376,68 @@ declare namespace performance {
* console.log(`${t} ms since start!`);
* ```
*/
export function now(): number;
now(): number;
}
declare const Performance: {
prototype: Performance;
new (): Performance;
};
declare const performance: Performance;
declare interface PerformanceMarkOptions {
/** Metadata to be included in the mark. */
detail?: any;
/** Timestamp to be used as the mark time. */
startTime?: number;
}
declare interface PerformanceMeasureOptions {
/** Metadata to be included in the measure. */
detail?: any;
/** Timestamp to be used as the start time or string to be used as start
* mark.*/
start?: string | number;
/** Duration between the start and end times. */
duration?: number;
/** Timestamp to be used as the end time or string to be used as end mark. */
end?: string | number;
}
/** Encapsulates a single performance metric that is part of the performance
* timeline. A performance entry can be directly created by making a performance
* mark or measure (for example by calling the `.mark()` method) at an explicit
* point in an application. */
declare class PerformanceEntry {
readonly duration: number;
readonly entryType: string;
readonly name: string;
readonly startTime: number;
toJSON(): any;
}
/** `PerformanceMark` is an abstract interface for `PerformanceEntry` objects
* with an entryType of `"mark"`. Entries of this type are created by calling
* `performance.mark()` to add a named `DOMHighResTimeStamp` (the mark) to the
* performance timeline. */
declare class PerformanceMark extends PerformanceEntry {
readonly detail: any;
readonly entryType: "mark";
constructor(name: string, options?: PerformanceMarkOptions);
}
/** `PerformanceMeasure` is an abstract interface for `PerformanceEntry` objects
* with an entryType of `"measure"`. Entries of this type are created by calling
* `performance.measure()` to add a named `DOMHighResTimeStamp` (the measure)
* between two marks to the performance timeline. */
declare class PerformanceMeasure extends PerformanceEntry {
readonly detail: any;
readonly entryType: "measure";
}
interface EventInit {

View file

@ -1,10 +1,336 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { now as opNow } from "../ops/timers.ts";
import { customInspect, inspect } from "./console.ts";
import { cloneValue, setFunctionName } from "./util.ts";
export class Performance {
now(): number {
const res = opNow();
return res.seconds * 1e3 + res.subsecNanos / 1e6;
let performanceEntries: PerformanceEntryList = [];
function findMostRecent(
name: string,
type: "mark" | "measure"
): PerformanceEntry | undefined {
return performanceEntries
.slice()
.reverse()
.find((entry) => entry.name === name && entry.entryType === type);
}
function convertMarkToTimestamp(mark: string | number): number {
if (typeof mark === "string") {
const entry = findMostRecent(mark, "mark");
if (!entry) {
throw new SyntaxError(`Cannot find mark: "${mark}".`);
}
return entry.startTime;
}
if (mark < 0) {
throw new TypeError("Mark cannot be negative.");
}
return mark;
}
function filterByNameType(
name?: string,
type?: "mark" | "measure"
): PerformanceEntryList {
return performanceEntries.filter(
(entry) =>
(name ? entry.name === name : true) &&
(type ? entry.entryType === type : true)
);
}
function now(): number {
const res = opNow();
return res.seconds * 1e3 + res.subsecNanos / 1e6;
}
export class PerformanceEntryImpl implements PerformanceEntry {
#name: string;
#entryType: string;
#startTime: number;
#duration: number;
get name(): string {
return this.#name;
}
get entryType(): string {
return this.#entryType;
}
get startTime(): number {
return this.#startTime;
}
get duration(): number {
return this.#duration;
}
constructor(
name: string,
entryType: string,
startTime: number,
duration: number
) {
this.#name = name;
this.#entryType = entryType;
this.#startTime = startTime;
this.#duration = duration;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
toJSON(): any {
return {
name: this.#name,
entryType: this.#entryType,
startTime: this.#startTime,
duration: this.#duration,
};
}
[customInspect](): string {
return `${this.constructor.name} { name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
}
}
export class PerformanceMarkImpl extends PerformanceEntryImpl
implements PerformanceMark {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
#detail: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get detail(): any {
return this.#detail;
}
get entryType(): "mark" {
return "mark";
}
constructor(
name: string,
{ detail = null, startTime = now() }: PerformanceMarkOptions = {}
) {
super(name, "mark", startTime, 0);
if (startTime < 0) {
throw new TypeError("startTime cannot be negative");
}
this.#detail = cloneValue(detail);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
toJSON(): any {
return {
name: this.name,
entryType: this.entryType,
startTime: this.startTime,
duration: this.duration,
detail: this.detail,
};
}
[customInspect](): string {
return this.detail
? `${this.constructor.name} {\n detail: ${inspect(this.detail, {
depth: 3,
})},\n name: "${this.name}",\n entryType: "${
this.entryType
}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}`
: `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
}
}
export class PerformanceMeasureImpl extends PerformanceEntryImpl
implements PerformanceMeasure {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
#detail: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get detail(): any {
return this.#detail;
}
get entryType(): "measure" {
return "measure";
}
constructor(
name: string,
startTime: number,
duration: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
detail: any = null
) {
super(name, "measure", startTime, duration);
this.#detail = cloneValue(detail);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
toJSON(): any {
return {
name: this.name,
entryType: this.entryType,
startTime: this.startTime,
duration: this.duration,
detail: this.detail,
};
}
[customInspect](): string {
return this.detail
? `${this.constructor.name} {\n detail: ${inspect(this.detail, {
depth: 3,
})},\n name: "${this.name}",\n entryType: "${
this.entryType
}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}`
: `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
}
}
export class PerformanceImpl implements Performance {
clearMarks(markName?: string): void {
if (markName == null) {
performanceEntries = performanceEntries.filter(
(entry) => entry.entryType !== "mark"
);
} else {
performanceEntries = performanceEntries.filter(
(entry) => !(entry.name === markName && entry.entryType === "mark")
);
}
}
clearMeasures(measureName?: string): void {
if (measureName == null) {
performanceEntries = performanceEntries.filter(
(entry) => entry.entryType !== "measure"
);
} else {
performanceEntries = performanceEntries.filter(
(entry) =>
!(entry.name === measureName && entry.entryType === "measure")
);
}
}
getEntries(): PerformanceEntryList {
return filterByNameType();
}
getEntriesByName(
name: string,
type?: "mark" | "measure"
): PerformanceEntryList {
return filterByNameType(name, type);
}
getEntriesByType(type: "mark" | "measure"): PerformanceEntryList {
return filterByNameType(undefined, type);
}
mark(
markName: string,
options: PerformanceMarkOptions = {}
): PerformanceMark {
// 3.1.1.1 If the global object is a Window object and markName uses the
// same name as a read only attribute in the PerformanceTiming interface,
// throw a SyntaxError. - not implemented
const entry = new PerformanceMarkImpl(markName, options);
// 3.1.1.7 Queue entry - not implemented
performanceEntries.push(entry);
return entry;
}
measure(
measureName: string,
options?: PerformanceMeasureOptions
): PerformanceMeasure;
measure(
measureName: string,
startMark?: string,
endMark?: string
): PerformanceMeasure;
measure(
measureName: string,
startOrMeasureOptions: string | PerformanceMeasureOptions = {},
endMark?: string
): PerformanceMeasure {
if (startOrMeasureOptions && typeof startOrMeasureOptions === "object") {
if (endMark) {
throw new TypeError("Options cannot be passed with endMark.");
}
if (
!("start" in startOrMeasureOptions) &&
!("end" in startOrMeasureOptions)
) {
throw new TypeError("A start or end mark must be supplied in options.");
}
if (
"start" in startOrMeasureOptions &&
"duration" in startOrMeasureOptions &&
"end" in startOrMeasureOptions
) {
throw new TypeError(
"Cannot specify start, end, and duration together in options."
);
}
}
let endTime: number;
if (endMark) {
endTime = convertMarkToTimestamp(endMark);
} else if (
typeof startOrMeasureOptions === "object" &&
"end" in startOrMeasureOptions
) {
endTime = convertMarkToTimestamp(startOrMeasureOptions.end!);
} else if (
typeof startOrMeasureOptions === "object" &&
"start" in startOrMeasureOptions &&
"duration" in startOrMeasureOptions
) {
const start = convertMarkToTimestamp(startOrMeasureOptions.start!);
const duration = convertMarkToTimestamp(startOrMeasureOptions.duration!);
endTime = start + duration;
} else {
endTime = now();
}
let startTime: number;
if (
typeof startOrMeasureOptions === "object" &&
"start" in startOrMeasureOptions
) {
startTime = convertMarkToTimestamp(startOrMeasureOptions.start!);
} else if (
typeof startOrMeasureOptions === "object" &&
"end" in startOrMeasureOptions &&
"duration" in startOrMeasureOptions
) {
const end = convertMarkToTimestamp(startOrMeasureOptions.end!);
const duration = convertMarkToTimestamp(startOrMeasureOptions.duration!);
startTime = end - duration;
} else if (typeof startOrMeasureOptions === "string") {
startTime = convertMarkToTimestamp(startOrMeasureOptions);
} else {
startTime = 0;
}
const entry = new PerformanceMeasureImpl(
measureName,
startTime,
endTime - startTime,
typeof startOrMeasureOptions === "object"
? startOrMeasureOptions.detail ?? null
: null
);
performanceEntries.push(entry);
return entry;
}
now(): number {
return now();
}
}
setFunctionName(PerformanceEntryImpl, "PerformanceEntry");
setFunctionName(PerformanceMarkImpl, "PerformanceMark");
setFunctionName(PerformanceMeasureImpl, "PerformanceMeasure");
setFunctionName(PerformanceImpl, "Performance");

View file

@ -19,7 +19,7 @@ import { WritableStreamDefaultWriterImpl } from "./writable_stream_default_write
import { WritableStreamImpl } from "./writable_stream.ts";
import { AbortSignalImpl } from "../abort_signal.ts";
import { DOMExceptionImpl as DOMException } from "../dom_exception.ts";
import { cloneValue } from "../util.ts";
import { cloneValue, setFunctionName } from "../util.ts";
import { assert, AssertionError } from "../../util.ts";
export type AbortAlgorithm = (reason?: any) => PromiseLike<void>;
@ -1320,12 +1320,6 @@ export function resetQueue<R>(container: Container<R>): void {
container[sym.queueTotalSize] = 0;
}
/** An internal function which provides a function name for some generated
* functions, so stack traces are a bit more readable. */
export function setFunctionName(fn: Function, value: string): void {
Object.defineProperty(fn, "name", { value, configurable: true });
}
/** An internal function which mimics the behavior of setting the promise to
* handled in JavaScript. In this situation, an assertion failure, which
* shouldn't happen will get thrown, instead of swallowed. */

View file

@ -1,7 +1,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { setFunctionName } from "./internals.ts";
import { customInspect } from "../console.ts";
import { setFunctionName } from "../util.ts";
export class CountQueuingStrategyImpl implements CountQueuingStrategy {
highWaterMark: number;

View file

@ -18,12 +18,12 @@ import {
readableStreamHasDefaultReader,
readableStreamGetNumReadRequests,
readableStreamCreateReadResult,
setFunctionName,
} from "./internals.ts";
import type { ReadableStreamImpl } from "./readable_stream.ts";
import * as sym from "./symbols.ts";
import { assert } from "../../util.ts";
import { customInspect } from "../console.ts";
import { setFunctionName } from "../util.ts";
export class ReadableByteStreamControllerImpl
implements ReadableByteStreamController {

View file

@ -9,7 +9,6 @@ import {
isWritableStream,
isWritableStreamLocked,
makeSizeAlgorithmFromSizeFunction,
setFunctionName,
setPromiseIsHandledToTrue,
readableStreamCancel,
ReadableStreamGenericReader,
@ -25,6 +24,7 @@ import type { ReadableStreamDefaultControllerImpl } from "./readable_stream_defa
import * as sym from "./symbols.ts";
import { customInspect } from "../console.ts";
import { AbortSignalImpl } from "../abort_signal.ts";
import { setFunctionName } from "../util.ts";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class ReadableStreamImpl<R = any> implements ReadableStream<R> {

View file

@ -18,11 +18,11 @@ import {
readableStreamDefaultControllerGetDesiredSize,
resetQueue,
SizeAlgorithm,
setFunctionName,
} from "./internals.ts";
import type { ReadableStreamImpl } from "./readable_stream.ts";
import * as sym from "./symbols.ts";
import { customInspect } from "../console.ts";
import { setFunctionName } from "../util.ts";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class ReadableStreamDefaultControllerImpl<R = any>

View file

@ -9,11 +9,11 @@ import {
readableStreamReaderGenericCancel,
readableStreamReaderGenericInitialize,
readableStreamReaderGenericRelease,
setFunctionName,
} from "./internals.ts";
import type { ReadableStreamImpl } from "./readable_stream.ts";
import * as sym from "./symbols.ts";
import { customInspect } from "../console.ts";
import { setFunctionName } from "../util.ts";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class ReadableStreamDefaultReaderImpl<R = any>

View file

@ -7,7 +7,6 @@ import {
invokeOrNoop,
isTransformStream,
makeSizeAlgorithmFromSizeFunction,
setFunctionName,
setUpTransformStreamDefaultControllerFromTransformer,
validateAndNormalizeHighWaterMark,
} from "./internals.ts";
@ -16,6 +15,7 @@ import * as sym from "./symbols.ts";
import type { TransformStreamDefaultControllerImpl } from "./transform_stream_default_controller.ts";
import type { WritableStreamImpl } from "./writable_stream.ts";
import { customInspect, inspect } from "../console.ts";
import { setFunctionName } from "../util.ts";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class TransformStreamImpl<I = any, O = any>

View file

@ -4,7 +4,6 @@ import {
FlushAlgorithm,
isTransformStreamDefaultController,
readableStreamDefaultControllerGetDesiredSize,
setFunctionName,
TransformAlgorithm,
transformStreamDefaultControllerEnqueue,
transformStreamDefaultControllerError,
@ -14,6 +13,7 @@ import type { ReadableStreamDefaultControllerImpl } from "./readable_stream_defa
import * as sym from "./symbols.ts";
import type { TransformStreamImpl } from "./transform_stream.ts";
import { customInspect } from "../console.ts";
import { setFunctionName } from "../util.ts";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class TransformStreamDefaultControllerImpl<I = any, O = any>

View file

@ -8,7 +8,6 @@ import {
isWritableStream,
isWritableStreamLocked,
makeSizeAlgorithmFromSizeFunction,
setFunctionName,
setUpWritableStreamDefaultControllerFromUnderlyingSink,
writableStreamAbort,
writableStreamClose,
@ -19,6 +18,7 @@ import * as sym from "./symbols.ts";
import type { WritableStreamDefaultControllerImpl } from "./writable_stream_default_controller.ts";
import type { WritableStreamDefaultWriterImpl } from "./writable_stream_default_writer.ts";
import { customInspect } from "../console.ts";
import { setFunctionName } from "../util.ts";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class WritableStreamImpl<W = any> implements WritableStream<W> {

View file

@ -6,7 +6,6 @@ import {
isWritableStreamDefaultController,
Pair,
resetQueue,
setFunctionName,
SizeAlgorithm,
WriteAlgorithm,
writableStreamDefaultControllerClearAlgorithms,
@ -15,6 +14,7 @@ import {
import * as sym from "./symbols.ts";
import type { WritableStreamImpl } from "./writable_stream.ts";
import { customInspect } from "../console.ts";
import { setFunctionName } from "../util.ts";
export class WritableStreamDefaultControllerImpl<W>
implements WritableStreamDefaultController {

View file

@ -6,7 +6,6 @@ import {
isWritableStream,
isWritableStreamDefaultWriter,
isWritableStreamLocked,
setFunctionName,
setPromiseIsHandledToTrue,
writableStreamCloseQueuedOrInFlight,
writableStreamDefaultWriterAbort,
@ -19,6 +18,7 @@ import * as sym from "./symbols.ts";
import type { WritableStreamImpl } from "./writable_stream.ts";
import { customInspect } from "../console.ts";
import { assert } from "../../util.ts";
import { setFunctionName } from "../util.ts";
export class WritableStreamDefaultWriterImpl<W>
implements WritableStreamDefaultWriter<W> {

View file

@ -208,3 +208,11 @@ export function getHeaderValueParams(value: string): Map<string, string> {
export function hasHeaderValueOf(s: string, value: string): boolean {
return new RegExp(`^${value}[\t\s]*;?`).test(s);
}
/** An internal function which provides a function name for some generated
* functions, so stack traces are a bit more readable.
*
* @internal */
export function setFunctionName(fn: Function, value: string): void {
Object.defineProperty(fn, "name", { value, configurable: true });
}

View file

@ -1,5 +1,10 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { unitTest, assert, createResolvable } from "./test_util.ts";
import {
unitTest,
assert,
assertEquals,
createResolvable,
} from "./test_util.ts";
unitTest({ perms: { hrtime: false } }, async function performanceNow(): Promise<
void
@ -13,3 +18,42 @@ unitTest({ perms: { hrtime: false } }, async function performanceNow(): Promise<
}, 10);
await resolvable;
});
unitTest(function performanceMark() {
const mark = performance.mark("test");
assert(mark instanceof PerformanceMark);
assertEquals(mark.detail, null);
assertEquals(mark.name, "test");
assertEquals(mark.entryType, "mark");
assert(mark.startTime > 0);
assertEquals(mark.duration, 0);
const entries = performance.getEntries();
assert(entries[entries.length - 1] === mark);
const markEntries = performance.getEntriesByName("test", "mark");
assert(markEntries[markEntries.length - 1] === mark);
});
unitTest(function performanceMeasure() {
const mark = performance.mark("test");
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const measure = performance.measure("test", "test");
assert(measure instanceof PerformanceMeasure);
assertEquals(measure.detail, null);
assertEquals(measure.name, "test");
assertEquals(measure.entryType, "measure");
assert(measure.startTime > 0);
assertEquals(mark.startTime, measure.startTime);
assert(measure.duration >= 100 && measure.duration < 200);
const entries = performance.getEntries();
assert(entries[entries.length - 1] === measure);
const measureEntries = performance.getEntriesByName("test", "measure");
assert(measureEntries[measureEntries.length - 1] === measure);
} catch (e) {
return reject(e);
}
resolve();
}, 100);
});
});