Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 <ostream>
8 : #include "platform.h"
9 : #include "mozilla/HashFunctions.h"
10 : #include "mozilla/Sprintf.h"
11 :
12 : #include "nsThreadUtils.h"
13 : #include "nsXULAppAPI.h"
14 :
15 : // JS
16 : #include "jsapi.h"
17 : #include "jsfriendapi.h"
18 : #include "js/TrackedOptimizationInfo.h"
19 :
20 : // Self
21 : #include "ProfileBufferEntry.h"
22 :
23 : using mozilla::JSONWriter;
24 : using mozilla::MakeUnique;
25 : using mozilla::Maybe;
26 : using mozilla::Nothing;
27 : using mozilla::Some;
28 : using mozilla::TimeStamp;
29 : using mozilla::UniquePtr;
30 :
31 : ////////////////////////////////////////////////////////////////////////
32 : // BEGIN ProfileBufferEntry
33 :
34 0 : ProfileBufferEntry::ProfileBufferEntry()
35 0 : : mKind(Kind::INVALID)
36 : {
37 0 : u.mString = nullptr;
38 0 : }
39 :
40 : // aString must be a static string.
41 0 : ProfileBufferEntry::ProfileBufferEntry(Kind aKind, const char *aString)
42 0 : : mKind(aKind)
43 : {
44 0 : u.mString = aString;
45 0 : }
46 :
47 0 : ProfileBufferEntry::ProfileBufferEntry(Kind aKind, char aChars[kNumChars])
48 0 : : mKind(aKind)
49 : {
50 0 : memcpy(u.mChars, aChars, kNumChars);
51 0 : }
52 :
53 0 : ProfileBufferEntry::ProfileBufferEntry(Kind aKind, void* aPtr)
54 0 : : mKind(aKind)
55 : {
56 0 : u.mPtr = aPtr;
57 0 : }
58 :
59 0 : ProfileBufferEntry::ProfileBufferEntry(Kind aKind, ProfilerMarker* aMarker)
60 0 : : mKind(aKind)
61 : {
62 0 : u.mMarker = aMarker;
63 0 : }
64 :
65 0 : ProfileBufferEntry::ProfileBufferEntry(Kind aKind, double aDouble)
66 0 : : mKind(aKind)
67 : {
68 0 : u.mDouble = aDouble;
69 0 : }
70 :
71 0 : ProfileBufferEntry::ProfileBufferEntry(Kind aKind, int aInt)
72 0 : : mKind(aKind)
73 : {
74 0 : u.mInt = aInt;
75 0 : }
76 :
77 : // END ProfileBufferEntry
78 : ////////////////////////////////////////////////////////////////////////
79 :
80 : class JSONSchemaWriter
81 : {
82 : JSONWriter& mWriter;
83 : uint32_t mIndex;
84 :
85 : public:
86 0 : explicit JSONSchemaWriter(JSONWriter& aWriter)
87 0 : : mWriter(aWriter)
88 0 : , mIndex(0)
89 : {
90 0 : aWriter.StartObjectProperty("schema");
91 0 : }
92 :
93 0 : void WriteField(const char* aName) {
94 0 : mWriter.IntProperty(aName, mIndex++);
95 0 : }
96 :
97 0 : ~JSONSchemaWriter() {
98 0 : mWriter.EndObject();
99 0 : }
100 : };
101 :
102 : class StreamOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp
103 : {
104 : JSONWriter& mWriter;
105 : UniqueJSONStrings& mUniqueStrings;
106 : bool mStartedTypeList;
107 :
108 : public:
109 0 : StreamOptimizationTypeInfoOp(JSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings)
110 0 : : mWriter(aWriter)
111 : , mUniqueStrings(aUniqueStrings)
112 0 : , mStartedTypeList(false)
113 0 : { }
114 :
115 0 : void readType(const char* keyedBy, const char* name,
116 : const char* location, const Maybe<unsigned>& lineno) override {
117 0 : if (!mStartedTypeList) {
118 0 : mStartedTypeList = true;
119 0 : mWriter.StartObjectElement();
120 0 : mWriter.StartArrayProperty("typeset");
121 : }
122 :
123 0 : mWriter.StartObjectElement();
124 : {
125 0 : mUniqueStrings.WriteProperty(mWriter, "keyedBy", keyedBy);
126 0 : if (name) {
127 0 : mUniqueStrings.WriteProperty(mWriter, "name", name);
128 : }
129 0 : if (location) {
130 0 : mUniqueStrings.WriteProperty(mWriter, "location", location);
131 : }
132 0 : if (lineno.isSome()) {
133 0 : mWriter.IntProperty("line", *lineno);
134 : }
135 : }
136 0 : mWriter.EndObject();
137 0 : }
138 :
139 0 : void operator()(JS::TrackedTypeSite site, const char* mirType) override {
140 0 : if (mStartedTypeList) {
141 0 : mWriter.EndArray();
142 0 : mStartedTypeList = false;
143 : } else {
144 0 : mWriter.StartObjectElement();
145 : }
146 :
147 : {
148 0 : mUniqueStrings.WriteProperty(mWriter, "site", JS::TrackedTypeSiteString(site));
149 0 : mUniqueStrings.WriteProperty(mWriter, "mirType", mirType);
150 : }
151 0 : mWriter.EndObject();
152 0 : }
153 : };
154 :
155 : // As mentioned in ProfileBufferEntry.h, the JSON format contains many
156 : // arrays whose elements are laid out according to various schemas to help
157 : // de-duplication. This RAII class helps write these arrays by keeping track of
158 : // the last non-null element written and adding the appropriate number of null
159 : // elements when writing new non-null elements. It also automatically opens and
160 : // closes an array element on the given JSON writer.
161 : //
162 : // Example usage:
163 : //
164 : // // Define the schema of elements in this type of array: [FOO, BAR, BAZ]
165 : // enum Schema : uint32_t {
166 : // FOO = 0,
167 : // BAR = 1,
168 : // BAZ = 2
169 : // };
170 : //
171 : // AutoArraySchemaWriter writer(someJsonWriter, someUniqueStrings);
172 : // if (shouldWriteFoo) {
173 : // writer.IntElement(FOO, getFoo());
174 : // }
175 : // ... etc ...
176 : class MOZ_RAII AutoArraySchemaWriter
177 : {
178 : friend class AutoObjectWriter;
179 :
180 : SpliceableJSONWriter& mJSONWriter;
181 : UniqueJSONStrings* mStrings;
182 : uint32_t mNextFreeIndex;
183 :
184 : public:
185 0 : AutoArraySchemaWriter(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aStrings)
186 0 : : mJSONWriter(aWriter)
187 : , mStrings(&aStrings)
188 0 : , mNextFreeIndex(0)
189 : {
190 0 : mJSONWriter.StartArrayElement();
191 0 : }
192 :
193 : // If you don't have access to a UniqueStrings, you had better not try and
194 : // write a string element down the line!
195 0 : explicit AutoArraySchemaWriter(SpliceableJSONWriter& aWriter)
196 0 : : mJSONWriter(aWriter)
197 : , mStrings(nullptr)
198 0 : , mNextFreeIndex(0)
199 : {
200 0 : mJSONWriter.StartArrayElement();
201 0 : }
202 :
203 0 : ~AutoArraySchemaWriter() {
204 0 : mJSONWriter.EndArray();
205 0 : }
206 :
207 0 : void FillUpTo(uint32_t aIndex) {
208 0 : MOZ_ASSERT(aIndex >= mNextFreeIndex);
209 0 : mJSONWriter.NullElements(aIndex - mNextFreeIndex);
210 0 : mNextFreeIndex = aIndex + 1;
211 0 : }
212 :
213 0 : void IntElement(uint32_t aIndex, uint32_t aValue) {
214 0 : FillUpTo(aIndex);
215 0 : mJSONWriter.IntElement(aValue);
216 0 : }
217 :
218 0 : void DoubleElement(uint32_t aIndex, double aValue) {
219 0 : FillUpTo(aIndex);
220 0 : mJSONWriter.DoubleElement(aValue);
221 0 : }
222 :
223 0 : void StringElement(uint32_t aIndex, const char* aValue) {
224 0 : MOZ_RELEASE_ASSERT(mStrings);
225 0 : FillUpTo(aIndex);
226 0 : mStrings->WriteElement(mJSONWriter, aValue);
227 0 : }
228 : };
229 :
230 : class StreamOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp
231 : {
232 : SpliceableJSONWriter& mWriter;
233 : UniqueJSONStrings& mUniqueStrings;
234 :
235 : public:
236 0 : StreamOptimizationAttemptsOp(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings)
237 0 : : mWriter(aWriter),
238 0 : mUniqueStrings(aUniqueStrings)
239 0 : { }
240 :
241 0 : void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
242 : enum Schema : uint32_t {
243 : STRATEGY = 0,
244 : OUTCOME = 1
245 : };
246 :
247 0 : AutoArraySchemaWriter writer(mWriter, mUniqueStrings);
248 0 : writer.StringElement(STRATEGY, JS::TrackedStrategyString(strategy));
249 0 : writer.StringElement(OUTCOME, JS::TrackedOutcomeString(outcome));
250 0 : }
251 : };
252 :
253 : class StreamJSFramesOp : public JS::ForEachProfiledFrameOp
254 : {
255 : void* mReturnAddress;
256 : UniqueStacks::Stack& mStack;
257 : unsigned mDepth;
258 :
259 : public:
260 0 : StreamJSFramesOp(void* aReturnAddr, UniqueStacks::Stack& aStack)
261 0 : : mReturnAddress(aReturnAddr)
262 : , mStack(aStack)
263 0 : , mDepth(0)
264 0 : { }
265 :
266 0 : unsigned depth() const {
267 0 : MOZ_ASSERT(mDepth > 0);
268 0 : return mDepth;
269 : }
270 :
271 0 : void operator()(const JS::ForEachProfiledFrameOp::FrameHandle& aFrameHandle) override {
272 0 : UniqueStacks::OnStackFrameKey frameKey(mReturnAddress, mDepth, aFrameHandle);
273 0 : mStack.AppendFrame(frameKey);
274 0 : mDepth++;
275 0 : }
276 : };
277 :
278 0 : uint32_t UniqueJSONStrings::GetOrAddIndex(const char* aStr)
279 : {
280 : uint32_t index;
281 0 : StringKey key(aStr);
282 :
283 0 : auto it = mStringToIndexMap.find(key);
284 :
285 0 : if (it != mStringToIndexMap.end()) {
286 0 : return it->second;
287 : }
288 0 : index = mStringToIndexMap.size();
289 0 : mStringToIndexMap[key] = index;
290 0 : mStringTableWriter.StringElement(aStr);
291 0 : return index;
292 : }
293 :
294 0 : bool UniqueStacks::FrameKey::operator==(const FrameKey& aOther) const
295 : {
296 0 : return mLocation == aOther.mLocation &&
297 0 : mLine == aOther.mLine &&
298 0 : mCategory == aOther.mCategory &&
299 0 : mJITAddress == aOther.mJITAddress &&
300 0 : mJITDepth == aOther.mJITDepth;
301 : }
302 :
303 0 : bool UniqueStacks::StackKey::operator==(const StackKey& aOther) const
304 : {
305 0 : MOZ_ASSERT_IF(mPrefix == aOther.mPrefix, mPrefixHash == aOther.mPrefixHash);
306 0 : return mPrefix == aOther.mPrefix && mFrame == aOther.mFrame;
307 : }
308 :
309 0 : UniqueStacks::Stack::Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot)
310 : : mUniqueStacks(aUniqueStacks)
311 0 : , mStack(aUniqueStacks.GetOrAddFrameIndex(aRoot))
312 : {
313 0 : }
314 :
315 0 : void UniqueStacks::Stack::AppendFrame(const OnStackFrameKey& aFrame)
316 : {
317 : // Compute the prefix hash and index before mutating mStack.
318 0 : uint32_t prefixHash = mStack.Hash();
319 0 : uint32_t prefix = mUniqueStacks.GetOrAddStackIndex(mStack);
320 0 : mStack.UpdateHash(prefixHash, prefix, mUniqueStacks.GetOrAddFrameIndex(aFrame));
321 0 : }
322 :
323 0 : uint32_t UniqueStacks::Stack::GetOrAddIndex() const
324 : {
325 0 : return mUniqueStacks.GetOrAddStackIndex(mStack);
326 : }
327 :
328 0 : uint32_t UniqueStacks::FrameKey::Hash() const
329 : {
330 0 : uint32_t hash = 0;
331 0 : if (!mLocation.IsEmpty()) {
332 0 : hash = mozilla::HashString(mLocation.get());
333 : }
334 0 : if (mLine.isSome()) {
335 0 : hash = mozilla::AddToHash(hash, *mLine);
336 : }
337 0 : if (mCategory.isSome()) {
338 0 : hash = mozilla::AddToHash(hash, *mCategory);
339 : }
340 0 : if (mJITAddress.isSome()) {
341 0 : hash = mozilla::AddToHash(hash, *mJITAddress);
342 0 : if (mJITDepth.isSome()) {
343 0 : hash = mozilla::AddToHash(hash, *mJITDepth);
344 : }
345 : }
346 0 : return hash;
347 : }
348 :
349 0 : uint32_t UniqueStacks::StackKey::Hash() const
350 : {
351 0 : if (mPrefix.isNothing()) {
352 0 : return mozilla::HashGeneric(mFrame);
353 : }
354 0 : return mozilla::AddToHash(*mPrefixHash, mFrame);
355 : }
356 :
357 0 : UniqueStacks::Stack UniqueStacks::BeginStack(const OnStackFrameKey& aRoot)
358 : {
359 0 : return Stack(*this, aRoot);
360 : }
361 :
362 0 : UniqueStacks::UniqueStacks(JSContext* aContext)
363 : : mContext(aContext)
364 0 : , mFrameCount(0)
365 : {
366 0 : mFrameTableWriter.StartBareList();
367 0 : mStackTableWriter.StartBareList();
368 0 : }
369 :
370 0 : uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack)
371 : {
372 : uint32_t index;
373 0 : if (mStackToIndexMap.Get(aStack, &index)) {
374 0 : MOZ_ASSERT(index < mStackToIndexMap.Count());
375 0 : return index;
376 : }
377 :
378 0 : index = mStackToIndexMap.Count();
379 0 : mStackToIndexMap.Put(aStack, index);
380 0 : StreamStack(aStack);
381 0 : return index;
382 : }
383 :
384 0 : uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame)
385 : {
386 : uint32_t index;
387 0 : if (mFrameToIndexMap.Get(aFrame, &index)) {
388 0 : MOZ_ASSERT(index < mFrameCount);
389 0 : return index;
390 : }
391 :
392 : // If aFrame isn't canonical, forward it to the canonical frame's index.
393 0 : if (aFrame.mJITFrameHandle) {
394 0 : void* canonicalAddr = aFrame.mJITFrameHandle->canonicalAddress();
395 0 : if (canonicalAddr != *aFrame.mJITAddress) {
396 0 : OnStackFrameKey canonicalKey(canonicalAddr, *aFrame.mJITDepth, *aFrame.mJITFrameHandle);
397 0 : uint32_t canonicalIndex = GetOrAddFrameIndex(canonicalKey);
398 0 : mFrameToIndexMap.Put(aFrame, canonicalIndex);
399 0 : return canonicalIndex;
400 : }
401 : }
402 :
403 : // A manual count is used instead of mFrameToIndexMap.Count() due to
404 : // forwarding of canonical JIT frames above.
405 0 : index = mFrameCount++;
406 0 : mFrameToIndexMap.Put(aFrame, index);
407 0 : StreamFrame(aFrame);
408 0 : return index;
409 : }
410 :
411 0 : uint32_t UniqueStacks::LookupJITFrameDepth(void* aAddr)
412 : {
413 : uint32_t depth;
414 :
415 0 : auto it = mJITFrameDepthMap.find(aAddr);
416 0 : if (it != mJITFrameDepthMap.end()) {
417 0 : depth = it->second;
418 0 : MOZ_ASSERT(depth > 0);
419 0 : return depth;
420 : }
421 0 : return 0;
422 : }
423 :
424 0 : void UniqueStacks::AddJITFrameDepth(void* aAddr, unsigned depth)
425 : {
426 0 : mJITFrameDepthMap[aAddr] = depth;
427 0 : }
428 :
429 0 : void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter)
430 : {
431 0 : mFrameTableWriter.EndBareList();
432 0 : aWriter.TakeAndSplice(mFrameTableWriter.WriteFunc());
433 0 : }
434 :
435 0 : void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter)
436 : {
437 0 : mStackTableWriter.EndBareList();
438 0 : aWriter.TakeAndSplice(mStackTableWriter.WriteFunc());
439 0 : }
440 :
441 0 : void UniqueStacks::StreamStack(const StackKey& aStack)
442 : {
443 : enum Schema : uint32_t {
444 : PREFIX = 0,
445 : FRAME = 1
446 : };
447 :
448 0 : AutoArraySchemaWriter writer(mStackTableWriter, mUniqueStrings);
449 0 : if (aStack.mPrefix.isSome()) {
450 0 : writer.IntElement(PREFIX, *aStack.mPrefix);
451 : }
452 0 : writer.IntElement(FRAME, aStack.mFrame);
453 0 : }
454 :
455 0 : void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame)
456 : {
457 : enum Schema : uint32_t {
458 : LOCATION = 0,
459 : IMPLEMENTATION = 1,
460 : OPTIMIZATIONS = 2,
461 : LINE = 3,
462 : CATEGORY = 4
463 : };
464 :
465 0 : AutoArraySchemaWriter writer(mFrameTableWriter, mUniqueStrings);
466 :
467 0 : if (!aFrame.mJITFrameHandle) {
468 0 : writer.StringElement(LOCATION, aFrame.mLocation.get());
469 0 : if (aFrame.mLine.isSome()) {
470 0 : writer.IntElement(LINE, *aFrame.mLine);
471 : }
472 0 : if (aFrame.mCategory.isSome()) {
473 0 : writer.IntElement(CATEGORY, *aFrame.mCategory);
474 : }
475 : } else {
476 0 : const JS::ForEachProfiledFrameOp::FrameHandle& jitFrame = *aFrame.mJITFrameHandle;
477 :
478 0 : writer.StringElement(LOCATION, jitFrame.label());
479 :
480 0 : JS::ProfilingFrameIterator::FrameKind frameKind = jitFrame.frameKind();
481 0 : MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion ||
482 : frameKind == JS::ProfilingFrameIterator::Frame_Baseline);
483 0 : writer.StringElement(IMPLEMENTATION,
484 : frameKind == JS::ProfilingFrameIterator::Frame_Ion
485 : ? "ion"
486 0 : : "baseline");
487 :
488 0 : if (jitFrame.hasTrackedOptimizations()) {
489 0 : writer.FillUpTo(OPTIMIZATIONS);
490 0 : mFrameTableWriter.StartObjectElement();
491 : {
492 0 : mFrameTableWriter.StartArrayProperty("types");
493 : {
494 0 : StreamOptimizationTypeInfoOp typeInfoOp(mFrameTableWriter, mUniqueStrings);
495 0 : jitFrame.forEachOptimizationTypeInfo(typeInfoOp);
496 : }
497 0 : mFrameTableWriter.EndArray();
498 :
499 0 : JS::Rooted<JSScript*> script(mContext);
500 : jsbytecode* pc;
501 0 : mFrameTableWriter.StartObjectProperty("attempts");
502 : {
503 : {
504 0 : JSONSchemaWriter schema(mFrameTableWriter);
505 0 : schema.WriteField("strategy");
506 0 : schema.WriteField("outcome");
507 : }
508 :
509 0 : mFrameTableWriter.StartArrayProperty("data");
510 : {
511 0 : StreamOptimizationAttemptsOp attemptOp(mFrameTableWriter, mUniqueStrings);
512 0 : jitFrame.forEachOptimizationAttempt(attemptOp, script.address(), &pc);
513 : }
514 0 : mFrameTableWriter.EndArray();
515 : }
516 0 : mFrameTableWriter.EndObject();
517 :
518 0 : if (JSAtom* name = js::GetPropertyNameFromPC(script, pc)) {
519 : char buf[512];
520 0 : JS_PutEscapedFlatString(buf, mozilla::ArrayLength(buf), js::AtomToFlatString(name), 0);
521 0 : mUniqueStrings.WriteProperty(mFrameTableWriter, "propertyName", buf);
522 : }
523 :
524 : unsigned line, column;
525 0 : line = JS_PCToLineNumber(script, pc, &column);
526 0 : mFrameTableWriter.IntProperty("line", line);
527 0 : mFrameTableWriter.IntProperty("column", column);
528 : }
529 0 : mFrameTableWriter.EndObject();
530 : }
531 : }
532 0 : }
533 :
534 0 : struct ProfileSample
535 : {
536 : uint32_t mStack;
537 : double mTime;
538 : Maybe<double> mResponsiveness;
539 : Maybe<double> mRSS;
540 : Maybe<double> mUSS;
541 : };
542 :
543 0 : static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample)
544 : {
545 : enum Schema : uint32_t {
546 : STACK = 0,
547 : TIME = 1,
548 : RESPONSIVENESS = 2,
549 : RSS = 3,
550 : USS = 4
551 : };
552 :
553 0 : AutoArraySchemaWriter writer(aWriter);
554 :
555 0 : writer.IntElement(STACK, aSample.mStack);
556 :
557 0 : writer.DoubleElement(TIME, aSample.mTime);
558 :
559 0 : if (aSample.mResponsiveness.isSome()) {
560 0 : writer.DoubleElement(RESPONSIVENESS, *aSample.mResponsiveness);
561 : }
562 :
563 0 : if (aSample.mRSS.isSome()) {
564 0 : writer.DoubleElement(RSS, *aSample.mRSS);
565 : }
566 :
567 0 : if (aSample.mUSS.isSome()) {
568 0 : writer.DoubleElement(USS, *aSample.mUSS);
569 : }
570 0 : }
571 :
572 : class EntryGetter
573 : {
574 : public:
575 0 : explicit EntryGetter(const ProfileBuffer& aBuffer)
576 0 : : mEntries(aBuffer.mEntries.get())
577 0 : , mReadPos(aBuffer.mReadPos)
578 0 : , mWritePos(aBuffer.mWritePos)
579 0 : , mEntrySize(aBuffer.mEntrySize)
580 0 : {}
581 :
582 0 : bool Has() const { return mReadPos != mWritePos; }
583 0 : const ProfileBufferEntry& Get() const { return mEntries[mReadPos]; }
584 0 : void Next() { mReadPos = (mReadPos + 1) % mEntrySize; }
585 :
586 : private:
587 : const ProfileBufferEntry* const mEntries;
588 : int mReadPos;
589 : const int mWritePos;
590 : const int mEntrySize;
591 : };
592 :
593 : // Each sample is made up of multiple ProfileBuffer entries. The following
594 : // grammar shows legal sequences.
595 : //
596 : // (
597 : // ThreadId
598 : // Time
599 : // ( NativeLeafAddr
600 : // | Label DynamicStringFragment* LineNumber? Category?
601 : // | JitReturnAddr
602 : // )+
603 : // Marker*
604 : // Responsiveness?
605 : // ResidentMemory?
606 : // UnsharedMemory?
607 : // )*
608 : //
609 : // The most complicated part is the stack entry sequence that begins with
610 : // Label. Here are some examples.
611 : //
612 : // - PseudoStack entries without a dynamic string:
613 : //
614 : // Label("js::RunScript")
615 : // Category(ProfileEntry::Category::JS)
616 : //
617 : // Label("XREMain::XRE_main")
618 : // LineNumber(4660)
619 : // Category(ProfileEntry::Category::OTHER)
620 : //
621 : // Label("ElementRestyler::ComputeStyleChangeFor")
622 : // LineNumber(3003)
623 : // Category(ProfileEntry::Category::CSS)
624 : //
625 : // - PseudoStack entries with a dynamic string:
626 : //
627 : // Label("nsObserverService::NotifyObservers")
628 : // DynamicStringFragment("domwindo")
629 : // DynamicStringFragment("wopened")
630 : // LineNumber(291)
631 : // Category(ProfileEntry::Category::OTHER)
632 : //
633 : // Label("")
634 : // DynamicStringFragment("closeWin")
635 : // DynamicStringFragment("dow (chr")
636 : // DynamicStringFragment("ome://gl")
637 : // DynamicStringFragment("obal/con")
638 : // DynamicStringFragment("tent/glo")
639 : // DynamicStringFragment("balOverl")
640 : // DynamicStringFragment("ay.js:5)")
641 : // DynamicStringFragment("") # this string holds the closing '\0'
642 : // LineNumber(25)
643 : // Category(ProfileEntry::Category::JS)
644 : //
645 : // Label("")
646 : // DynamicStringFragment("bound (s")
647 : // DynamicStringFragment("elf-host")
648 : // DynamicStringFragment("ed:914)")
649 : // LineNumber(945)
650 : // Category(ProfileEntry::Category::JS)
651 : //
652 : // - A pseudoStack entry with a dynamic string, but with privacy enabled:
653 : //
654 : // Label("nsObserverService::NotifyObservers")
655 : // DynamicStringFragment("(private")
656 : // DynamicStringFragment(")")
657 : // LineNumber(291)
658 : // Category(ProfileEntry::Category::OTHER)
659 : //
660 : // - A pseudoStack entry with an overly long dynamic string:
661 : //
662 : // Label("")
663 : // DynamicStringFragment("(too lon")
664 : // DynamicStringFragment("g)")
665 : // LineNumber(100)
666 : // Category(ProfileEntry::Category::NETWORK)
667 : //
668 : // - A wasm JIT frame entry:
669 : //
670 : // Label("")
671 : // DynamicStringFragment("wasm-fun")
672 : // DynamicStringFragment("ction[87")
673 : // DynamicStringFragment("36] (blo")
674 : // DynamicStringFragment("b:http:/")
675 : // DynamicStringFragment("/webasse")
676 : // DynamicStringFragment("mbly.org")
677 : // DynamicStringFragment("/3dc5759")
678 : // DynamicStringFragment("4-ce58-4")
679 : // DynamicStringFragment("626-975b")
680 : // DynamicStringFragment("-08ad116")
681 : // DynamicStringFragment("30bc1:38")
682 : // DynamicStringFragment("29856)")
683 : //
684 : // - A JS frame entry in a synchronous sample:
685 : //
686 : // Label("")
687 : // DynamicStringFragment("u (https")
688 : // DynamicStringFragment("://perf-")
689 : // DynamicStringFragment("html.io/")
690 : // DynamicStringFragment("ac0da204")
691 : // DynamicStringFragment("aaa44d75")
692 : // DynamicStringFragment("a800.bun")
693 : // DynamicStringFragment("dle.js:2")
694 : // DynamicStringFragment("5)")
695 : //
696 : void
697 0 : ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
698 : double aSinceTime, JSContext* aContext,
699 : UniqueStacks& aUniqueStacks) const
700 : {
701 0 : UniquePtr<char[]> strbuf = MakeUnique<char[]>(kMaxFrameKeyLength);
702 :
703 : // Because this is a format entirely internal to the Profiler, any parsing
704 : // error indicates a bug in the ProfileBuffer writing or the parser itself,
705 : // or possibly flaky hardware.
706 : #define ERROR_AND_SKIP_TO_NEXT_SAMPLE(msg) \
707 : do { \
708 : fprintf(stderr, "ProfileBuffer parse error: %s", msg); \
709 : MOZ_ASSERT(false, msg); \
710 : goto skip_to_next_sample; \
711 : } while (0)
712 :
713 0 : EntryGetter e(*this);
714 :
715 : // This block skips entries until we find the start of the next sample. This
716 : // is useful in two situations.
717 : //
718 : // - The circular buffer overwrites old entries, so when we start parsing we
719 : // might be in the middle of a sample, and we must skip forward to the
720 : // start of the next sample.
721 : //
722 : // - We skip samples that don't have an appropriate ThreadId or Time.
723 : //
724 : skip_to_next_sample:
725 0 : while (e.Has()) {
726 0 : if (e.Get().IsThreadId()) {
727 0 : break;
728 : } else {
729 0 : e.Next();
730 : }
731 : }
732 :
733 0 : while (e.Has()) {
734 0 : if (e.Get().IsThreadId()) {
735 0 : int threadId = e.Get().u.mInt;
736 0 : e.Next();
737 :
738 : // Ignore samples that are for the wrong thread.
739 0 : if (threadId != aThreadId) {
740 0 : goto skip_to_next_sample;
741 : }
742 : } else {
743 : // Due to the skip_to_next_sample block above, if we have an entry here
744 : // it must be a ThreadId entry.
745 0 : MOZ_CRASH();
746 : }
747 :
748 0 : ProfileSample sample;
749 :
750 0 : if (e.Has() && e.Get().IsTime()) {
751 0 : sample.mTime = e.Get().u.mDouble;
752 0 : e.Next();
753 :
754 : // Ignore samples that are too old.
755 0 : if (sample.mTime < aSinceTime) {
756 0 : goto skip_to_next_sample;
757 : }
758 : } else {
759 0 : ERROR_AND_SKIP_TO_NEXT_SAMPLE("expected a Time entry");
760 : }
761 :
762 : UniqueStacks::Stack stack =
763 0 : aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)"));
764 :
765 0 : int numFrames = 0;
766 0 : while (e.Has()) {
767 0 : if (e.Get().IsNativeLeafAddr()) {
768 0 : numFrames++;
769 :
770 : // Bug 753041: We need a double cast here to tell GCC that we don't
771 : // want to sign extend 32-bit addresses starting with 0xFXXXXXX.
772 0 : unsigned long long pc = (unsigned long long)(uintptr_t)e.Get().u.mPtr;
773 : char buf[20];
774 0 : SprintfLiteral(buf, "%#llx", pc);
775 0 : stack.AppendFrame(UniqueStacks::OnStackFrameKey(buf));
776 0 : e.Next();
777 :
778 0 : } else if (e.Get().IsLabel()) {
779 0 : numFrames++;
780 :
781 : // Copy the label into strbuf.
782 0 : const char* label = e.Get().u.mString;
783 0 : strncpy(strbuf.get(), label, kMaxFrameKeyLength);
784 0 : size_t i = strlen(label);
785 0 : e.Next();
786 :
787 0 : bool seenFirstDynamicStringFragment = false;
788 0 : while (e.Has()) {
789 0 : if (e.Get().IsDynamicStringFragment()) {
790 : // If this is the first dynamic string fragment and we have a
791 : // non-empty label, insert a ' ' after the label and before the
792 : // dynamic string.
793 0 : if (!seenFirstDynamicStringFragment) {
794 0 : if (i > 0 && i < kMaxFrameKeyLength) {
795 0 : strbuf[i] = ' ';
796 0 : i++;
797 : }
798 0 : seenFirstDynamicStringFragment = true;
799 : }
800 :
801 0 : for (size_t j = 0; j < ProfileBufferEntry::kNumChars; j++) {
802 0 : const char* chars = e.Get().u.mChars;
803 0 : if (i < kMaxFrameKeyLength) {
804 0 : strbuf[i] = chars[j];
805 0 : i++;
806 : }
807 : }
808 0 : e.Next();
809 : } else {
810 0 : break;
811 : }
812 : }
813 0 : strbuf[kMaxFrameKeyLength - 1] = '\0';
814 :
815 0 : UniqueStacks::OnStackFrameKey frameKey(strbuf.get());
816 :
817 0 : if (e.Has() && e.Get().IsLineNumber()) {
818 0 : frameKey.mLine = Some(unsigned(e.Get().u.mInt));
819 0 : e.Next();
820 : }
821 :
822 0 : if (e.Has() && e.Get().IsCategory()) {
823 0 : frameKey.mCategory = Some(unsigned(e.Get().u.mInt));
824 0 : e.Next();
825 : }
826 :
827 0 : stack.AppendFrame(frameKey);
828 :
829 0 : } else if (e.Get().IsJitReturnAddr()) {
830 0 : numFrames++;
831 :
832 : // A JIT frame may expand to multiple frames due to inlining.
833 0 : void* pc = e.Get().u.mPtr;
834 0 : unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc);
835 0 : if (depth == 0) {
836 0 : StreamJSFramesOp framesOp(pc, stack);
837 0 : MOZ_RELEASE_ASSERT(aContext);
838 0 : JS::ForEachProfiledFrame(aContext, pc, framesOp);
839 0 : aUniqueStacks.AddJITFrameDepth(pc, framesOp.depth());
840 : } else {
841 0 : for (unsigned i = 0; i < depth; i++) {
842 0 : UniqueStacks::OnStackFrameKey inlineFrameKey(pc, i);
843 0 : stack.AppendFrame(inlineFrameKey);
844 : }
845 : }
846 :
847 0 : e.Next();
848 :
849 : } else {
850 0 : break;
851 : }
852 : }
853 :
854 0 : if (numFrames == 0) {
855 0 : ERROR_AND_SKIP_TO_NEXT_SAMPLE("expected one or more frame entries");
856 : }
857 :
858 0 : sample.mStack = stack.GetOrAddIndex();
859 :
860 : // Skip over the markers. We process them in StreamMarkersToJSON().
861 0 : while (e.Has()) {
862 0 : if (e.Get().IsMarker()) {
863 0 : e.Next();
864 : } else {
865 0 : break;
866 : }
867 : }
868 :
869 0 : if (e.Has() && e.Get().IsResponsiveness()) {
870 0 : sample.mResponsiveness = Some(e.Get().u.mDouble);
871 0 : e.Next();
872 : }
873 :
874 0 : if (e.Has() && e.Get().IsResidentMemory()) {
875 0 : sample.mRSS = Some(e.Get().u.mDouble);
876 0 : e.Next();
877 : }
878 :
879 0 : if (e.Has() && e.Get().IsUnsharedMemory()) {
880 0 : sample.mUSS = Some(e.Get().u.mDouble);
881 0 : e.Next();
882 : }
883 :
884 0 : WriteSample(aWriter, sample);
885 : }
886 :
887 : #undef ERROR_AND_SKIP_TO_NEXT_SAMPLE
888 0 : }
889 :
890 : void
891 0 : ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter,
892 : int aThreadId,
893 : const TimeStamp& aProcessStartTime,
894 : double aSinceTime,
895 : UniqueStacks& aUniqueStacks) const
896 : {
897 0 : EntryGetter e(*this);
898 :
899 0 : int currentThreadID = -1;
900 :
901 : // Stream all markers whose threadId matches aThreadId. All other entries are
902 : // skipped, because we process them in StreamSamplesToJSON().
903 0 : while (e.Has()) {
904 0 : if (e.Get().IsThreadId()) {
905 0 : currentThreadID = e.Get().u.mInt;
906 0 : } else if (currentThreadID == aThreadId && e.Get().IsMarker()) {
907 0 : const ProfilerMarker* marker = e.Get().u.mMarker;
908 0 : if (marker->GetTime() >= aSinceTime) {
909 0 : marker->StreamJSON(aWriter, aProcessStartTime, aUniqueStacks);
910 : }
911 : }
912 0 : e.Next();
913 : }
914 0 : }
915 :
916 : int
917 0 : ProfileBuffer::FindLastSampleOfThread(int aThreadId, const LastSample& aLS)
918 : const
919 : {
920 : // |aLS| has a valid generation number if either it matches the buffer's
921 : // generation, or is one behind the buffer's generation, since the buffer's
922 : // generation is incremented on wraparound. There's no ambiguity relative to
923 : // ProfileBuffer::reset, since that increments mGeneration by two.
924 0 : if (aLS.mGeneration == mGeneration ||
925 0 : (mGeneration > 0 && aLS.mGeneration == mGeneration - 1)) {
926 0 : int ix = aLS.mPos;
927 :
928 0 : if (ix == -1) {
929 : // There's no record of |aLS|'s thread ever having recorded a sample in
930 : // the buffer.
931 0 : return -1;
932 : }
933 :
934 : // It might be that the sample has since been overwritten, so check that it
935 : // is still valid.
936 0 : MOZ_RELEASE_ASSERT(0 <= ix && ix < mEntrySize);
937 0 : ProfileBufferEntry& entry = mEntries[ix];
938 0 : bool isStillValid = entry.IsThreadId() && entry.u.mInt == aThreadId;
939 0 : return isStillValid ? ix : -1;
940 : }
941 :
942 : // |aLS| denotes a sample which is older than either two wraparounds or one
943 : // call to ProfileBuffer::reset. In either case it is no longer valid.
944 0 : MOZ_ASSERT(aLS.mGeneration <= mGeneration - 2);
945 0 : return -1;
946 : }
947 :
948 : bool
949 0 : ProfileBuffer::DuplicateLastSample(int aThreadId,
950 : const TimeStamp& aProcessStartTime,
951 : LastSample& aLS)
952 : {
953 0 : int lastSampleStartPos = FindLastSampleOfThread(aThreadId, aLS);
954 0 : if (lastSampleStartPos == -1) {
955 0 : return false;
956 : }
957 :
958 0 : MOZ_ASSERT(mEntries[lastSampleStartPos].IsThreadId() &&
959 : mEntries[lastSampleStartPos].u.mInt == aThreadId);
960 :
961 0 : AddThreadIdEntry(aThreadId, &aLS);
962 :
963 : // Go through the whole entry and duplicate it, until we find the next one.
964 0 : for (int readPos = (lastSampleStartPos + 1) % mEntrySize;
965 0 : readPos != mWritePos;
966 0 : readPos = (readPos + 1) % mEntrySize) {
967 0 : switch (mEntries[readPos].GetKind()) {
968 : case ProfileBufferEntry::Kind::ThreadId:
969 : // We're done.
970 0 : return true;
971 : case ProfileBufferEntry::Kind::Time:
972 : // Copy with new time
973 0 : AddEntry(ProfileBufferEntry::Time(
974 0 : (TimeStamp::Now() - aProcessStartTime).ToMilliseconds()));
975 0 : break;
976 : case ProfileBufferEntry::Kind::Marker:
977 : // Don't copy markers
978 0 : break;
979 : default:
980 : // Copy anything else we don't know about.
981 0 : AddEntry(mEntries[readPos]);
982 0 : break;
983 : }
984 : }
985 0 : return true;
986 : }
987 :
988 : // END ProfileBuffer
989 : ////////////////////////////////////////////////////////////////////////
990 :
|