/src/mozilla-central/js/src/vm/GeckoProfiler.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | | * vim: set ts=8 sts=4 et sw=4 tw=99: |
3 | | * This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "vm/GeckoProfiler-inl.h" |
8 | | |
9 | | #include "mozilla/DebugOnly.h" |
10 | | |
11 | | #include "jsnum.h" |
12 | | |
13 | | #include "gc/GC.h" |
14 | | #include "gc/PublicIterators.h" |
15 | | #include "jit/BaselineFrame.h" |
16 | | #include "jit/BaselineJIT.h" |
17 | | #include "jit/JitcodeMap.h" |
18 | | #include "jit/JitFrames.h" |
19 | | #include "jit/JitRealm.h" |
20 | | #include "jit/JSJitFrameIter.h" |
21 | | #include "util/StringBuffer.h" |
22 | | #include "vm/JSScript.h" |
23 | | |
24 | | #include "gc/Marking-inl.h" |
25 | | |
26 | | using namespace js; |
27 | | |
28 | | using mozilla::DebugOnly; |
29 | | |
30 | | GeckoProfilerThread::GeckoProfilerThread() |
31 | | : profilingStack_(nullptr) |
32 | 27 | { |
33 | 27 | } |
34 | | |
35 | | GeckoProfilerRuntime::GeckoProfilerRuntime(JSRuntime* rt) |
36 | | : rt(rt), |
37 | | strings(mutexid::GeckoProfilerStrings), |
38 | | slowAssertions(false), |
39 | | enabled_(false), |
40 | | eventMarker_(nullptr) |
41 | 3 | { |
42 | 3 | MOZ_ASSERT(rt != nullptr); |
43 | 3 | } |
44 | | |
45 | | void |
46 | | GeckoProfilerThread::setProfilingStack(ProfilingStack* profilingStack) |
47 | 3 | { |
48 | 3 | profilingStack_ = profilingStack; |
49 | 3 | } |
50 | | |
51 | | void |
52 | | GeckoProfilerRuntime::setEventMarker(void (*fn)(const char*)) |
53 | 0 | { |
54 | 0 | eventMarker_ = fn; |
55 | 0 | } |
56 | | |
57 | | // Get a pointer to the top-most profiling frame, given the exit frame pointer. |
58 | | static void* |
59 | | GetTopProfilingJitFrame(Activation* act) |
60 | 0 | { |
61 | 0 | if (!act || !act->isJit()) { |
62 | 0 | return nullptr; |
63 | 0 | } |
64 | 0 | |
65 | 0 | jit::JitActivation* jitActivation = act->asJit(); |
66 | 0 |
|
67 | 0 | // If there is no exit frame set, just return. |
68 | 0 | if (!jitActivation->hasExitFP()) { |
69 | 0 | return nullptr; |
70 | 0 | } |
71 | 0 | |
72 | 0 | // Skip wasm frames that might be in the way. |
73 | 0 | OnlyJSJitFrameIter iter(jitActivation); |
74 | 0 | if (iter.done()) { |
75 | 0 | return nullptr; |
76 | 0 | } |
77 | 0 | |
78 | 0 | jit::JSJitProfilingFrameIterator jitIter((jit::CommonFrameLayout*) iter.frame().fp()); |
79 | 0 | MOZ_ASSERT(!jitIter.done()); |
80 | 0 | return jitIter.fp(); |
81 | 0 | } |
82 | | |
83 | | void |
84 | | GeckoProfilerRuntime::enable(bool enabled) |
85 | 0 | { |
86 | 0 | JSContext* cx = rt->mainContextFromAnyThread(); |
87 | 0 | MOZ_ASSERT(cx->geckoProfiler().installed()); |
88 | 0 |
|
89 | 0 | if (enabled_ == enabled) { |
90 | 0 | return; |
91 | 0 | } |
92 | 0 | |
93 | 0 | /* |
94 | 0 | * Ensure all future generated code will be instrumented, or that all |
95 | 0 | * currently instrumented code is discarded |
96 | 0 | */ |
97 | 0 | ReleaseAllJITCode(rt->defaultFreeOp()); |
98 | 0 |
|
99 | 0 | // This function is called when the Gecko profiler makes a new Sampler |
100 | 0 | // (and thus, a new circular buffer). Set all current entries in the |
101 | 0 | // JitcodeGlobalTable as expired and reset the buffer range start. |
102 | 0 | if (rt->hasJitRuntime() && rt->jitRuntime()->hasJitcodeGlobalTable()) { |
103 | 0 | rt->jitRuntime()->getJitcodeGlobalTable()->setAllEntriesAsExpired(); |
104 | 0 | } |
105 | 0 | rt->setProfilerSampleBufferRangeStart(0); |
106 | 0 |
|
107 | 0 | // Ensure that lastProfilingFrame is null for the main thread. |
108 | 0 | if (cx->jitActivation) { |
109 | 0 | cx->jitActivation->setLastProfilingFrame(nullptr); |
110 | 0 | cx->jitActivation->setLastProfilingCallSite(nullptr); |
111 | 0 | } |
112 | 0 |
|
113 | 0 | enabled_ = enabled; |
114 | 0 |
|
115 | 0 | /* Toggle Gecko Profiler-related jumps on baseline jitcode. |
116 | 0 | * The call to |ReleaseAllJITCode| above will release most baseline jitcode, but not |
117 | 0 | * jitcode for scripts with active frames on the stack. These scripts need to have |
118 | 0 | * their profiler state toggled so they behave properly. |
119 | 0 | */ |
120 | 0 | jit::ToggleBaselineProfiling(rt, enabled); |
121 | 0 |
|
122 | 0 | /* Update lastProfilingFrame to point to the top-most JS jit-frame currently on |
123 | 0 | * stack. |
124 | 0 | */ |
125 | 0 | if (cx->jitActivation) { |
126 | 0 | // Walk through all activations, and set their lastProfilingFrame appropriately. |
127 | 0 | if (enabled) { |
128 | 0 | Activation* act = cx->activation(); |
129 | 0 | void* lastProfilingFrame = GetTopProfilingJitFrame(act); |
130 | 0 |
|
131 | 0 | jit::JitActivation* jitActivation = cx->jitActivation; |
132 | 0 | while (jitActivation) { |
133 | 0 | jitActivation->setLastProfilingFrame(lastProfilingFrame); |
134 | 0 | jitActivation->setLastProfilingCallSite(nullptr); |
135 | 0 |
|
136 | 0 | jitActivation = jitActivation->prevJitActivation(); |
137 | 0 | lastProfilingFrame = GetTopProfilingJitFrame(jitActivation); |
138 | 0 | } |
139 | 0 | } else { |
140 | 0 | jit::JitActivation* jitActivation = cx->jitActivation; |
141 | 0 | while (jitActivation) { |
142 | 0 | jitActivation->setLastProfilingFrame(nullptr); |
143 | 0 | jitActivation->setLastProfilingCallSite(nullptr); |
144 | 0 | jitActivation = jitActivation->prevJitActivation(); |
145 | 0 | } |
146 | 0 | } |
147 | 0 | } |
148 | 0 |
|
149 | 0 | // WebAssembly code does not need to be released, but profiling string |
150 | 0 | // labels have to be generated so that they are available during async |
151 | 0 | // profiling stack iteration. |
152 | 0 | for (RealmsIter r(rt); !r.done(); r.next()) { |
153 | 0 | r->wasm.ensureProfilingLabels(enabled); |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | /* Lookup the string for the function/script, creating one if necessary */ |
158 | | const char* |
159 | | GeckoProfilerRuntime::profileString(JSScript* script, JSFunction* maybeFun) |
160 | 0 | { |
161 | 0 | auto locked = strings.lock(); |
162 | 0 |
|
163 | 0 | ProfileStringMap::AddPtr s = locked->lookupForAdd(script); |
164 | 0 |
|
165 | 0 | if (!s) { |
166 | 0 | auto str = allocProfileString(script, maybeFun); |
167 | 0 | if (!str || !locked->add(s, script, std::move(str))) { |
168 | 0 | return nullptr; |
169 | 0 | } |
170 | 0 | } |
171 | 0 | |
172 | 0 | return s->value().get(); |
173 | 0 | } |
174 | | |
175 | | void |
176 | | GeckoProfilerRuntime::onScriptFinalized(JSScript* script) |
177 | 0 | { |
178 | 0 | /* |
179 | 0 | * This function is called whenever a script is destroyed, regardless of |
180 | 0 | * whether profiling has been turned on, so don't invoke a function on an |
181 | 0 | * invalid hash set. Also, even if profiling was enabled but then turned |
182 | 0 | * off, we still want to remove the string, so no check of enabled() is |
183 | 0 | * done. |
184 | 0 | */ |
185 | 0 | auto locked = strings.lock(); |
186 | 0 | if (ProfileStringMap::Ptr entry = locked->lookup(script)) { |
187 | 0 | locked->remove(entry); |
188 | 0 | } |
189 | 0 | } |
190 | | |
191 | | void |
192 | | GeckoProfilerRuntime::markEvent(const char* event) |
193 | 0 | { |
194 | 0 | MOZ_ASSERT(enabled()); |
195 | 0 | if (eventMarker_) { |
196 | 0 | JS::AutoSuppressGCAnalysis nogc; |
197 | 0 | eventMarker_(event); |
198 | 0 | } |
199 | 0 | } |
200 | | |
201 | | bool |
202 | | GeckoProfilerThread::enter(JSContext* cx, JSScript* script, JSFunction* maybeFun) |
203 | 0 | { |
204 | 0 | const char* dynamicString = cx->runtime()->geckoProfiler().profileString(script, maybeFun); |
205 | 0 | if (dynamicString == nullptr) { |
206 | 0 | ReportOutOfMemory(cx); |
207 | 0 | return false; |
208 | 0 | } |
209 | 0 | |
210 | | #ifdef DEBUG |
211 | | // In debug builds, assert the JS profiling stack frames already on the |
212 | | // stack have a non-null pc. Only look at the top frames to avoid quadratic |
213 | | // behavior. |
214 | | uint32_t sp = profilingStack_->stackPointer; |
215 | | if (sp > 0 && sp - 1 < profilingStack_->stackCapacity()) { |
216 | | size_t start = (sp > 4) ? sp - 4 : 0; |
217 | | for (size_t i = start; i < sp - 1; i++) { |
218 | | MOZ_ASSERT_IF(profilingStack_->frames[i].isJsFrame(), profilingStack_->frames[i].pc()); |
219 | | } |
220 | | } |
221 | | #endif |
222 | | |
223 | 0 | profilingStack_->pushJsFrame("", dynamicString, script, script->code()); |
224 | 0 | return true; |
225 | 0 | } |
226 | | |
227 | | void |
228 | | GeckoProfilerThread::exit(JSScript* script, JSFunction* maybeFun) |
229 | 0 | { |
230 | 0 | profilingStack_->pop(); |
231 | 0 |
|
232 | | #ifdef DEBUG |
233 | | /* Sanity check to make sure push/pop balanced */ |
234 | | uint32_t sp = profilingStack_->stackPointer; |
235 | | if (sp < profilingStack_->stackCapacity()) { |
236 | | JSRuntime* rt = script->runtimeFromMainThread(); |
237 | | const char* dynamicString = rt->geckoProfiler().profileString(script, maybeFun); |
238 | | /* Can't fail lookup because we should already be in the set */ |
239 | | MOZ_ASSERT(dynamicString); |
240 | | |
241 | | // Bug 822041 |
242 | | if (!profilingStack_->frames[sp].isJsFrame()) { |
243 | | fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n"); |
244 | | fprintf(stderr, " frames=%p size=%u/%u\n", |
245 | | (void*) profilingStack_->frames, |
246 | | uint32_t(profilingStack_->stackPointer), |
247 | | profilingStack_->stackCapacity()); |
248 | | for (int32_t i = sp; i >= 0; i--) { |
249 | | ProfilingStackFrame& frame = profilingStack_->frames[i]; |
250 | | if (frame.isJsFrame()) { |
251 | | fprintf(stderr, " [%d] JS %s\n", i, frame.dynamicString()); |
252 | | } else { |
253 | | fprintf(stderr, " [%d] C line %d %s\n", i, frame.line(), frame.dynamicString()); |
254 | | } |
255 | | } |
256 | | } |
257 | | |
258 | | ProfilingStackFrame& frame = profilingStack_->frames[sp]; |
259 | | MOZ_ASSERT(frame.isJsFrame()); |
260 | | MOZ_ASSERT(frame.script() == script); |
261 | | MOZ_ASSERT(strcmp((const char*) frame.dynamicString(), dynamicString) == 0); |
262 | | } |
263 | | #endif |
264 | | } |
265 | | |
266 | | /* |
267 | | * Serializes the script/function pair into a "descriptive string" which is |
268 | | * allowed to fail. This function cannot trigger a GC because it could finalize |
269 | | * some scripts, resize the hash table of profile strings, and invalidate the |
270 | | * AddPtr held while invoking allocProfileString. |
271 | | */ |
272 | | UniqueChars |
273 | | GeckoProfilerRuntime::allocProfileString(JSScript* script, JSFunction* maybeFun) |
274 | 0 | { |
275 | 0 | // Note: this profiler string is regexp-matched by |
276 | 0 | // devtools/client/profiler/cleopatra/js/parserWorker.js. |
277 | 0 |
|
278 | 0 | // Get the function name, if any. |
279 | 0 | JSAtom* atom = maybeFun ? maybeFun->displayAtom() : nullptr; |
280 | 0 |
|
281 | 0 | // Get the script filename, if any, and its length. |
282 | 0 | const char* filename = script->filename(); |
283 | 0 | if (filename == nullptr) { |
284 | 0 | filename = "<unknown>"; |
285 | 0 | } |
286 | 0 | size_t lenFilename = strlen(filename); |
287 | 0 |
|
288 | 0 | // Get the line number and its length as a string. |
289 | 0 | uint32_t lineno = script->lineno(); |
290 | 0 | size_t lenLineno = 1; |
291 | 0 | for (uint32_t i = lineno; i /= 10; lenLineno++); |
292 | 0 |
|
293 | 0 | // Get the column number and its length as a string. |
294 | 0 | uint32_t column = script->column(); |
295 | 0 | size_t lenColumn = 1; |
296 | 0 | for (uint32_t i = column; i /= 10; lenColumn++); |
297 | 0 |
|
298 | 0 | // Determine the required buffer size. |
299 | 0 | size_t len = lenFilename + 1 + lenLineno + 1 + lenColumn; // +1 for each separator colon, ":". |
300 | 0 | if (atom) { |
301 | 0 | len += JS::GetDeflatedUTF8StringLength(atom) + 3; // +3 for the " (" and ")" it adds. |
302 | 0 | } |
303 | 0 |
|
304 | 0 | // Allocate the buffer. |
305 | 0 | UniqueChars cstr(js_pod_malloc<char>(len + 1)); |
306 | 0 | if (!cstr) { |
307 | 0 | return nullptr; |
308 | 0 | } |
309 | 0 | |
310 | 0 | // Construct the descriptive string. |
311 | 0 | DebugOnly<size_t> ret; |
312 | 0 | if (atom) { |
313 | 0 | UniqueChars atomStr = StringToNewUTF8CharsZ(nullptr, *atom); |
314 | 0 | if (!atomStr) { |
315 | 0 | return nullptr; |
316 | 0 | } |
317 | 0 | |
318 | 0 | ret = snprintf(cstr.get(), len + 1, "%s (%s:%" PRIu32 ":%" PRIu32 ")", |
319 | 0 | atomStr.get(), filename, lineno, column); |
320 | 0 | } else { |
321 | 0 | ret = snprintf(cstr.get(), len + 1, "%s:%" PRIu32 ":%" PRIu32, |
322 | 0 | filename, lineno, column); |
323 | 0 | } |
324 | 0 |
|
325 | 0 | MOZ_ASSERT(ret == len, "Computed length should match actual length!"); |
326 | 0 |
|
327 | 0 | return cstr; |
328 | 0 | } |
329 | | |
330 | | void |
331 | | GeckoProfilerThread::trace(JSTracer* trc) |
332 | 19 | { |
333 | 19 | if (profilingStack_) { |
334 | 19 | size_t size = profilingStack_->stackSize(); |
335 | 104 | for (size_t i = 0; i < size; i++) { |
336 | 85 | profilingStack_->frames[i].trace(trc); |
337 | 85 | } |
338 | 19 | } |
339 | 19 | } |
340 | | |
341 | | void |
342 | | GeckoProfilerRuntime::fixupStringsMapAfterMovingGC() |
343 | 0 | { |
344 | 0 | auto locked = strings.lock(); |
345 | 0 | for (ProfileStringMap::Enum e(locked.get()); !e.empty(); e.popFront()) { |
346 | 0 | JSScript* script = e.front().key(); |
347 | 0 | if (IsForwarded(script)) { |
348 | 0 | script = Forwarded(script); |
349 | 0 | e.rekeyFront(script); |
350 | 0 | } |
351 | 0 | } |
352 | 0 | } |
353 | | |
354 | | #ifdef JSGC_HASH_TABLE_CHECKS |
355 | | void |
356 | | GeckoProfilerRuntime::checkStringsMapAfterMovingGC() |
357 | | { |
358 | | auto locked = strings.lock(); |
359 | | for (auto r = locked->all(); !r.empty(); r.popFront()) { |
360 | | JSScript* script = r.front().key(); |
361 | | CheckGCThingAfterMovingGC(script); |
362 | | auto ptr = locked->lookup(script); |
363 | | MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); |
364 | | } |
365 | | } |
366 | | #endif |
367 | | |
368 | | void |
369 | | ProfilingStackFrame::trace(JSTracer* trc) |
370 | 85 | { |
371 | 85 | if (isJsFrame()) { |
372 | 14 | JSScript* s = rawScript(); |
373 | 14 | TraceNullableRoot(trc, &s, "ProfilingStackFrame script"); |
374 | 14 | spOrScript = s; |
375 | 14 | } |
376 | 85 | } |
377 | | |
378 | | GeckoProfilerBaselineOSRMarker::GeckoProfilerBaselineOSRMarker(JSContext* cx, bool hasProfilerFrame |
379 | | MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
380 | | : profiler(&cx->geckoProfiler()) |
381 | 1 | { |
382 | 1 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
383 | 1 | if (!hasProfilerFrame || !cx->runtime()->geckoProfiler().enabled()) { |
384 | 1 | profiler = nullptr; |
385 | 1 | return; |
386 | 1 | } |
387 | 0 | |
388 | 0 | uint32_t sp = profiler->profilingStack_->stackPointer; |
389 | 0 | if (sp >= profiler->profilingStack_->stackCapacity()) { |
390 | 0 | profiler = nullptr; |
391 | 0 | return; |
392 | 0 | } |
393 | 0 | |
394 | 0 | spBefore_ = sp; |
395 | 0 | if (sp == 0) { |
396 | 0 | return; |
397 | 0 | } |
398 | 0 | |
399 | 0 | ProfilingStackFrame& frame = profiler->profilingStack_->frames[sp - 1]; |
400 | 0 | MOZ_ASSERT(frame.kind() == ProfilingStackFrame::Kind::JS_NORMAL); |
401 | 0 | frame.setKind(ProfilingStackFrame::Kind::JS_OSR); |
402 | 0 | } |
403 | | |
404 | | GeckoProfilerBaselineOSRMarker::~GeckoProfilerBaselineOSRMarker() |
405 | 1 | { |
406 | 1 | if (profiler == nullptr) { |
407 | 1 | return; |
408 | 1 | } |
409 | 0 | |
410 | 0 | uint32_t sp = profiler->stackPointer(); |
411 | 0 | MOZ_ASSERT(spBefore_ == sp); |
412 | 0 | if (sp == 0) { |
413 | 0 | return; |
414 | 0 | } |
415 | 0 | |
416 | 0 | ProfilingStackFrame& frame = profiler->stack()[sp - 1]; |
417 | 0 | MOZ_ASSERT(frame.kind() == ProfilingStackFrame::Kind::JS_OSR); |
418 | 0 | frame.setKind(ProfilingStackFrame::Kind::JS_NORMAL); |
419 | 0 | } |
420 | | |
421 | | JS_PUBLIC_API(JSScript*) |
422 | | ProfilingStackFrame::script() const |
423 | 0 | { |
424 | 0 | MOZ_ASSERT(isJsFrame()); |
425 | 0 | auto script = reinterpret_cast<JSScript*>(spOrScript.operator void*()); |
426 | 0 | if (!script) { |
427 | 0 | return nullptr; |
428 | 0 | } |
429 | 0 | |
430 | 0 | // If profiling is supressed then we can't trust the script pointers to be |
431 | 0 | // valid as they could be in the process of being moved by a compacting GC |
432 | 0 | // (although it's still OK to get the runtime from them). |
433 | 0 | JSContext* cx = script->runtimeFromAnyThread()->mainContextFromAnyThread(); |
434 | 0 | if (!cx->isProfilerSamplingEnabled()) { |
435 | 0 | return nullptr; |
436 | 0 | } |
437 | 0 | |
438 | 0 | MOZ_ASSERT(!IsForwarded(script)); |
439 | 0 | return script; |
440 | 0 | } |
441 | | |
442 | | JS_FRIEND_API(jsbytecode*) |
443 | | ProfilingStackFrame::pc() const |
444 | 0 | { |
445 | 0 | MOZ_ASSERT(isJsFrame()); |
446 | 0 | if (lineOrPcOffset == NullPCOffset) { |
447 | 0 | return nullptr; |
448 | 0 | } |
449 | 0 | |
450 | 0 | JSScript* script = this->script(); |
451 | 0 | return script ? script->offsetToPC(lineOrPcOffset) : nullptr; |
452 | 0 | } |
453 | | |
454 | | /* static */ int32_t |
455 | 1.62M | ProfilingStackFrame::pcToOffset(JSScript* aScript, jsbytecode* aPc) { |
456 | 1.62M | return aPc ? aScript->pcToOffset(aPc) : NullPCOffset; |
457 | 1.62M | } |
458 | | |
459 | | void |
460 | | ProfilingStackFrame::setPC(jsbytecode* pc) |
461 | 0 | { |
462 | 0 | MOZ_ASSERT(isJsFrame()); |
463 | 0 | JSScript* script = this->script(); |
464 | 0 | MOZ_ASSERT(script); // This should not be called while profiling is suppressed. |
465 | 0 | lineOrPcOffset = pcToOffset(script, pc); |
466 | 0 | } |
467 | | |
468 | | JS_FRIEND_API(void) |
469 | | js::SetContextProfilingStack(JSContext* cx, ProfilingStack* profilingStack) |
470 | 3 | { |
471 | 3 | cx->geckoProfiler().setProfilingStack(profilingStack); |
472 | 3 | } |
473 | | |
474 | | JS_FRIEND_API(void) |
475 | | js::EnableContextProfilingStack(JSContext* cx, bool enabled) |
476 | 0 | { |
477 | 0 | cx->runtime()->geckoProfiler().enable(enabled); |
478 | 0 | } |
479 | | |
480 | | JS_FRIEND_API(void) |
481 | | js::RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*)) |
482 | 0 | { |
483 | 0 | MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled()); |
484 | 0 | cx->runtime()->geckoProfiler().setEventMarker(fn); |
485 | 0 | } |
486 | | |
487 | | AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext* cx |
488 | | MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
489 | | : cx_(cx), |
490 | | previouslyEnabled_(cx->isProfilerSamplingEnabled()) |
491 | 81 | { |
492 | 81 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
493 | 81 | if (previouslyEnabled_) { |
494 | 81 | cx_->disableProfilerSampling(); |
495 | 81 | } |
496 | 81 | } |
497 | | |
498 | | AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling() |
499 | 81 | { |
500 | 81 | if (previouslyEnabled_) { |
501 | 81 | cx_->enableProfilerSampling(); |
502 | 81 | } |
503 | 81 | } |