Skip to content
File

Blob: src/workerd/api/performance.c++

11.9 KB
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5#include "performance.h"
6 
7#include <workerd/io/io-util.h>
8#include <workerd/io/limit-enforcer.h>
9 
10#include <kj/encoding.h>
11 
12namespace workerd::api {
13 
14double Performance::now(jsg::Lock& js) {
15 // We define performance.now() for compatibility purposes, but due to Spectre concerns it
16 // returns exactly what Date.now() returns.
17 isolateLimitEnforcer.markPerfEvent("performance_now"_kjc);
18 return dateNow();
19}
20 
21jsg::Ref<PerformanceMark> PerformanceMark::constructor(
22 jsg::Lock& js, kj::String name, jsg::Optional<Options> maybeOptions) {
23 auto options = kj::mv(maybeOptions).orDefault({});
24 return js.alloc<PerformanceMark>(
25 kj::mv(name), kj::mv(options.detail), options.startTime.orDefault(dateNow()));
26}
27 
28jsg::JsObject PerformanceMark::toJSON(jsg::Lock& js) {
29 auto obj = js.objNoProto();
30 obj.set(js, "name"_kj, js.str(name));
31 obj.set(js, "entryType"_kj, js.str(entryType));
32 obj.set(js, "startTime"_kj, js.num(startTime));
33 obj.set(js, "duration"_kj, js.num(duration));
34 KJ_IF_SOME(d, detail) {
35 obj.set(js, "detail"_kj, d.getHandle(js));
36 }
37 return kj::mv(obj);
38}
39 
40jsg::JsObject PerformanceMeasure::toJSON(jsg::Lock& js) {
41 auto obj = js.objNoProto();
42 obj.set(js, "name"_kj, js.str(name));
43 obj.set(js, "entryType"_kj, js.str(entryType));
44 obj.set(js, "startTime"_kj, js.num(startTime));
45 obj.set(js, "duration"_kj, js.num(duration));
46 KJ_IF_SOME(d, detail) {
47 obj.set(js, "detail"_kj, d.getHandle(js));
48 }
49 return kj::mv(obj);
50}
51 
52jsg::JsObject PerformanceEntry::toJSON(jsg::Lock& js) {
53 auto obj = js.objNoProto();
54 obj.set(js, "name"_kj, js.str(name));
55 obj.set(js, "entryType"_kj, js.str(entryType));
56 obj.set(js, "startTime"_kj, js.num(startTime));
57 obj.set(js, "duration"_kj, js.num(duration));
58 return kj::mv(obj);
59}
60 
61jsg::JsObject PerformanceResourceTiming::toJSON(jsg::Lock& js) {
62 JSG_FAIL_REQUIRE(Error, "PerformanceResourceTiming.toJSON is not implemented"_kj);
63}
64 
65jsg::JsObject PerformanceNodeTiming::toJSON(jsg::Lock& js) {
66 auto obj = js.objNoProto();
67 obj.set(js, "name"_kj, js.str(name));
68 obj.set(js, "entryType"_kj, js.str(entryType));
69 obj.set(js, "startTime"_kj, js.num(startTime));
70 obj.set(js, "duration"_kj, js.num(duration));
71 obj.set(js, "nodeStart"_kj, js.num(0));
72 obj.set(js, "v8Start"_kj, js.num(0));
73 obj.set(js, "bootstrapComplete"_kj, js.num(0));
74 obj.set(js, "environment"_kj, js.num(0));
75 obj.set(js, "loopStart"_kj, js.num(0));
76 obj.set(js, "loopExit"_kj, js.num(0));
77 obj.set(js, "idleTime"_kj, js.num(0));
78 // Include uvMetricsInfo in the JSON representation
79 auto uvObj = js.objNoProto();
80 uvObj.set(js, "loopCount"_kj, js.num(0));
81 uvObj.set(js, "events"_kj, js.num(0));
82 uvObj.set(js, "eventsWaiting"_kj, js.num(0));
83 obj.set(js, "uvMetricsInfo"_kj, uvObj);
84 return kj::mv(obj);
85}
86 
87void Performance::clearMarks(jsg::Optional<kj::String> name) {
88 kj::Vector<jsg::Ref<PerformanceEntry>> filtered;
89 
90 KJ_IF_SOME(n, name) {
91 for (auto& entry: entries) {
92 if (entry->getName() != n) {
93 filtered.add(kj::mv(entry));
94 }
95 }
96 } else {
97 for (auto& entry: entries) {
98 if (entry->getEntryType() != "mark") {
99 filtered.add(kj::mv(entry));
100 }
101 }
102 }
103 
104 entries = filtered.releaseAsArray();
105}
106 
107void Performance::clearMeasures(jsg::Optional<kj::String> name) {
108 kj::Vector<jsg::Ref<PerformanceEntry>> filtered;
109 
110 KJ_IF_SOME(n, name) {
111 for (auto& entry: entries) {
112 if (entry->getName() != n) {
113 filtered.add(kj::mv(entry));
114 }
115 }
116 } else {
117 for (auto& entry: entries) {
118 if (entry->getEntryType() != "measure") {
119 filtered.add(kj::mv(entry));
120 }
121 }
122 }
123 
124 entries = filtered.releaseAsArray();
125}
126 
127void Performance::clearResourceTimings() {
128 kj::Vector<jsg::Ref<PerformanceEntry>> filtered;
129 
130 // Remove entries where entryType is "resource" or "navigation"
131 for (auto& entry: entries) {
132 auto entryType = entry->getEntryType();
133 if (entryType != "resource"_kj && entryType != "navigation"_kj) {
134 filtered.add(kj::mv(entry));
135 }
136 }
137 
138 entries = filtered.releaseAsArray();
139}
140 
141kj::ArrayPtr<jsg::Ref<PerformanceEntry>> Performance::getEntries() {
142 return entries;
143}
144 
145kj::Array<jsg::Ref<PerformanceEntry>> Performance::getEntriesByName(
146 kj::String name, jsg::Optional<kj::String> type) {
147 kj::Vector<jsg::Ref<PerformanceEntry>> filtered;
148 
149 for (auto& entry: entries) {
150 if (entry->getName() == name) {
151 KJ_IF_SOME(t, type) {
152 if (entry->getEntryType() == t) {
153 filtered.add(entry.addRef());
154 }
155 } else {
156 filtered.add(entry.addRef());
157 }
158 }
159 }
160 
161 return filtered.releaseAsArray();
162}
163 
164kj::Array<jsg::Ref<PerformanceEntry>> Performance::getEntriesByType(kj::String type) {
165 kj::Vector<jsg::Ref<PerformanceEntry>> filtered;
166 
167 for (auto& entry: entries) {
168 if (entry->getEntryType() == type) {
169 filtered.add(entry.addRef());
170 }
171 }
172 
173 return filtered.releaseAsArray();
174}
175 
176kj::ArrayPtr<jsg::Ref<PerformanceEntry>> PerformanceObserverEntryList::getEntries() {
177 return {};
178}
179 
180kj::ArrayPtr<jsg::Ref<PerformanceEntry>> PerformanceObserverEntryList::getEntriesByType(
181 kj::String type) {
182 return {};
183}
184 
185kj::ArrayPtr<jsg::Ref<PerformanceEntry>> PerformanceObserverEntryList::getEntriesByName(
186 kj::String name, jsg::Optional<kj::String> type) {
187 return {};
188}
189 
190jsg::Ref<PerformanceMark> Performance::mark(
191 jsg::Lock& js, kj::String name, jsg::Optional<PerformanceMark::Options> options) {
192 // TODO(someday): Include `name` in the perf event name?
193 isolateLimitEnforcer.markPerfEvent("performance_mark"_kjc);
194 double startTime = dateNow();
195 KJ_IF_SOME(opts, options) {
196 KJ_IF_SOME(time, opts.startTime) {
197 startTime = time;
198 }
199 }
200 
201 auto mark = js.alloc<PerformanceMark>(kj::mv(name), kj::none, startTime);
202 
203 KJ_IF_SOME(opts, options) {
204 KJ_IF_SOME(d, opts.detail) {
205 mark->detail = kj::mv(d);
206 }
207 }
208 
209 entries.add(mark.addRef());
210 return mark;
211}
212 
213jsg::Ref<PerformanceMeasure> Performance::measure(jsg::Lock& js,
214 kj::String measureName,
215 jsg::Optional<kj::OneOf<PerformanceMeasure::Options, kj::String>> measureOptionsOrStartMark,
216 jsg::Optional<kj::String> maybeEndMark) {
217 isolateLimitEnforcer.markPerfEvent("performance_measure"_kjc);
218 // Default: measure from timeOrigin (0) to now, per the Web Performance API spec.
219 double startTime = 0;
220 double endTime = dateNow();
221 
222 KJ_IF_SOME(startOrOptions, measureOptionsOrStartMark) {
223 KJ_SWITCH_ONEOF(startOrOptions) {
224 KJ_CASE_ONEOF(startMark, kj::String) {
225 auto startMarks = getEntriesByName(kj::str(startMark), kj::str("mark"));
226 if (startMarks.size() > 0) {
227 startTime = startMarks[0]->getStartTime();
228 }
229 
230 KJ_IF_SOME(endMark, maybeEndMark) {
231 auto endMarks = getEntriesByName(kj::str(endMark), kj::str("mark"));
232 if (endMarks.size() > 0) {
233 endTime = endMarks[0]->getStartTime();
234 }
235 }
236 }
237 KJ_CASE_ONEOF(options, PerformanceMeasure::Options) {
238 KJ_IF_SOME(start, options.start) {
239 startTime = start;
240 }
241 
242 KJ_IF_SOME(end, options.end) {
243 endTime = end;
244 } else KJ_IF_SOME(duration, options.duration) {
245 endTime = startTime + duration;
246 }
247 }
248 }
249 }
250 
251 double duration = endTime >= startTime ? endTime - startTime : 0;
252 auto measure = js.alloc<PerformanceMeasure>(kj::mv(measureName), startTime, duration);
253 
254 // Per the spec, detail defaults to null. Only set it if explicitly provided in options.
255 KJ_IF_SOME(startOrOptions, measureOptionsOrStartMark) {
256 KJ_SWITCH_ONEOF(startOrOptions) {
257 KJ_CASE_ONEOF(startMark, kj::String) {
258 // detail remains null when using string marks
259 }
260 KJ_CASE_ONEOF(options, PerformanceMeasure::Options) {
261 KJ_IF_SOME(d, options.detail) {
262 measure->detail = jsg::JsRef<jsg::JsObject>(js, d.getHandle(js));
263 }
264 // If options.detail is not provided, detail remains null
265 }
266 }
267 }
268 
269 entries.add(measure.addRef());
270 return measure;
271}
272 
273void Performance::setResourceTimingBufferSize(uint32_t size) {
274 // No-op stub for compatibility. Resource timing is not applicable in the Workers context,
275 // but we avoid throwing so that isomorphic code calling this defensively does not break.
276}
277 
278jsg::JsObject Performance::toJSON(jsg::Lock& js) {
279 auto obj = js.objNoProto();
280 obj.set(js, "timeOrigin"_kj, js.num(getTimeOrigin()));
281 return kj::mv(obj);
282}
283 
284jsg::Ref<PerformanceObserver> PerformanceObserver::constructor(jsg::Lock& js, Callback callback) {
285 return js.alloc<PerformanceObserver>(js, callback);
286}
287 
288void PerformanceObserver::disconnect() {
289 // Leaving it as a no-op for now.
290}
291 
292void PerformanceObserver::observe(jsg::Optional<ObserveOptions> options) {
293 // Leaving it as a no-op for now.
294}
295 
296kj::Array<jsg::Ref<PerformanceEntry>> PerformanceObserver::takeRecords() {
297 return {};
298}
299kj::ArrayPtr<const kj::StringPtr> PerformanceObserver::getSupportedEntryTypes() {
300 return supportedEntryTypes.asPtr();
301}
302 
303Performance::EventLoopUtilization Performance::eventLoopUtilization() {
304 // Return stub values - actual event loop utilization metrics are not available in workerd.
305 // This provides compatibility with code that expects Node.js-style performance APIs.
306 return EventLoopUtilization{
307 .idle = 0,
308 .active = 0,
309 .utilization = 0,
310 };
311}
312 
313jsg::Ref<PerformanceNodeTiming> Performance::getNodeTiming(jsg::Lock& js) {
314 return js.alloc<PerformanceNodeTiming>();
315}
316 
317void Performance::markResourceTiming() {
318 // No-op stub - resource timing is not applicable in the Workers context.
319 // This provides compatibility with code that expects this API to exist.
320}
321 
322jsg::Function<void()> Performance::timerify(jsg::Lock& js, jsg::Function<void()> fn) {
323 // We currently don't support timerify, so we just return the function as is.
324 return kj::mv(fn);
325}
326 
327jsg::Optional<uint32_t> EventCounts::get(kj::String eventType) {
328 KJ_IF_SOME(count, eventCounts.find(eventType)) {
329 return count;
330 }
331 return kj::none;
332}
333 
334bool EventCounts::has(kj::String eventType) {
335 return eventCounts.find(eventType) != kj::none;
336}
337 
338uint32_t EventCounts::getSize() {
339 return eventCounts.size();
340}
341 
342jsg::Ref<EventCounts::EntryIterator> EventCounts::entries(jsg::Lock& js) {
343 return jsg::alloc<EntryIterator>(IteratorState(JSG_THIS));
344}
345 
346kj::Maybe<EventCounts::EntryIteratorType> EventCounts::entryIteratorNext(
347 jsg::Lock& js, IteratorState& state) {
348 if (state.index >= state.entries.size()) {
349 return kj::none;
350 }
351 auto& entry = state.entries[state.index++];
352 // Return [key, value] as an array
353 return kj::arr(kj::str(kj::get<0>(entry)), kj::str(kj::get<1>(entry)));
354}
355 
356jsg::Ref<EventCounts::KeyIterator> EventCounts::keys(jsg::Lock& js) {
357 return jsg::alloc<KeyIterator>(IteratorState(JSG_THIS));
358}
359 
360kj::Maybe<EventCounts::KeyIteratorType> EventCounts::keyIteratorNext(
361 jsg::Lock& js, IteratorState& state) {
362 if (state.index >= state.entries.size()) {
363 return kj::none;
364 }
365 auto& entry = state.entries[state.index++];
366 return kj::str(kj::get<0>(entry));
367}
368 
369jsg::Ref<EventCounts::ValueIterator> EventCounts::values(jsg::Lock& js) {
370 return jsg::alloc<ValueIterator>(IteratorState(JSG_THIS));
371}
372 
373kj::Maybe<EventCounts::ValueIteratorType> EventCounts::valueIteratorNext(
374 jsg::Lock& js, IteratorState& state) {
375 if (state.index >= state.entries.size()) {
376 return kj::none;
377 }
378 auto& entry = state.entries[state.index++];
379 return kj::get<1>(entry);
380}
381 
382void EventCounts::forEach(jsg::Lock& js,
383 jsg::Function<void(uint32_t, kj::String, jsg::Ref<EventCounts>)> callback,
384 jsg::Optional<jsg::JsValue> thisArg) {
385 // Since we don't track events, this is effectively a no-op
386 for (auto& entry: eventCounts) {
387 callback(js, entry.value, kj::str(entry.key), JSG_THIS);
388 }
389}
390 
391jsg::Ref<EventCounts> Performance::getEventCounts(jsg::Lock& js) {
392 // Return a new EventCounts instance (currently empty as we don't track events)
393 return js.alloc<EventCounts>();
394}
395 
396} // namespace workerd::api