mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-27 10:21:47 +00:00
7adb8133f5
- missing bit of 989499 and some other files (23b0597ba6) - Bug 1233666 - Remove hacks for getting frame pointer for x86/x64 gcc. r=glandium (bfe8f59916) - Bug 1176266: In TimeStamp_posix.cpp, check for XP_LINUX instead of LINUX, and add missing #include, to allow strrchr usage. r=BenWa (56c725cffa) - Bug 1167230 - Don't pack ProfileEntry on ARM. r=shu (89f880e0cb) - Bug 1209779 - Ensure that all null elements are written when streaming profiler JSON; r=shu (2bae5addc6) - missing bit of Bug 1141712 - Make LUL (55f1276545) - Bug 1061800 - Add breakpad ids to profiler in Linux. r=BenWa (994fd1a941) - Bug 829621 - Compute the breakpad-id for OS X. r=BenWa. (e129580174) - missing of Bug 938157 - Lightweight CFI/EXIDX (b355dc3140) - Bug 1193838 - Allow ProfileGatherer to gather profiles from exiting processes. r=BenWa (5ab1a6a3c9) - align some missing stuff (5ebecd2364) - align some missing stuff (b8ff7aa361) - Bug 1164315 - Update key fingerprint for bitbucket.org; r=me (c1a3fbd930) - Bug 1178955 - Refactor config path selection; r=smacleod (89552bb0ac) - Bug 1195445 - Update host key fingerprint for bugzilla.mozilla.org (3783541088) - Bug 1218903 - Update bmo fingerprint. r=fubar, a=Tomcat (5b836fc585) - Bug 1178955 - Print config path on failure; r=smacleod (f5499f3771) - Bug 1185113 - Support setting more secure file permissions; r=smacleod (7dbf6b22fd) - Bug 1184229 - Detect multiple version-control-tools repos in Mercurial config; r=smacleod (16c24072a9) - Bug 978514 - mach mercurial-setup: Use mqext from the version-control-tools repo (1fa5765e8a) - Bug 1178955 - Don't pass config paths to updater; r=smacleod (80fcb05121) - Bug 1197527 - Don't unnecessarily attempt to create extensions directory in MercurialUpdater; r=gps (9b049c3ff8) - Bug 1164812 - mach mercurial-setup: Always mark the v-c-t repo as needing update (bd631208bd) - Bug 1197527 - Always clone version-control-tools in MercurialSetupWizard; r=gps (6990e8f589) - Bug 1197527 - Consolidate obtaining hg path into mozversioncontrol.get_hg_path; r=gps (e0b029a8e9) - Bug 1200458 - Skip permission check for .hgrc on Windows in hgsetup wizard. r=gps DONTBUILD (cc5b0d6daf) - Bug 1168466 - Bump minimum Mercurial version; r=smacleod (7fde47cfbe) - Bug 1185113 - Clarify language around Bugzilla credentials; r=smacleod (9166fdfbf9) - Bug 1185112 - Don't prompt for Bugzilla username/password if cookies defined; r=smacleod (cd87c96823) - Bug 1188931 - Fix hgsetup wizard. r=gps (68a6b46be4) - Bug 1200461 - Prompt for Bugzilla API Key instead of password; r=smacleod (780fb5d85d) - Bug 1228580 - ./mach mercurial-setup should use ~ to set up extension paths, not my literal home directory. r=gps (7a0c839880) - Bug 1231192 - Mark Mercurial 3.5.2 as oldest non-legacy version; r=smacleod (8f69483333) - Bug 1231192 - Bump some minimum Mercurial version; r=smacleod (916c56a852) - Bug 1162093 - Add "push-to-try" from version-control-tools to the mercurial setup wizard prompt.;r=gps (d29c7cf63a) - Bug 1168466 - Prompt to install bundleclone extension; r=smacleod (780ce90a08) - Bug 1185557 - Print relevant config options; r=smacleod (2f3f7e0161) - Bug 1231192 - Support clonebundles feature; r=smacleod (dcba1ccd34) - Bug 1231192 - Offer to install hg wip; r=smacleod (c42ebce5c8) - Bug 1231192 - Only install host fingerprints if not running secure Python+hg; r=smacleod (3154a2497b) - Bug 1178955 - Error when semicolon comments are seen; r=smacleod (e1f7081bb6) - Bug 1231989 - Prompt to install hgwatchman extension; r=ahal (0eddf0c1c8) - Bug 1178955 - Print line number for parse errors; r=smacleod (5369468cf1) - Bug 1185557 - Only prompt to install progress on Mercurial <3.5; r=smacleod (401f362265) - Bug 1232747 - Check for ssl.SSLContext existence; r=dminor (d505b07c5c) - Bug 1144629 - UnicodeDecodeError in ./mach mercurial-setup. r=gps, r=glandium (611d3ec83e) - Bug 1216970 - Make the copying more obvious in ProfilerImpl::GetStacktrace. r=froydnj (085625e113) - Bug 1190466 - tools/rb/find-leakers.pl re-written in Python r=mccr8 (4bfdcad13e) - Bug 1116478 - Open web content handlers in the proper tab in e10s. r=billm (ff8e11f45e) - Bug 1213437 - Less data copying when handling structured clones in MessageManager, r=baku (c4e2a13253) - const-var (69d17f312d) - Bug 1203090 - Ensure we always use '/' as the starting path separator for the DOM path of the Directorys initially returned by HTMLInputElement.getFilesAndDirectories. r=baku (1325bbc40c) - Bug 1209975 - Stop using dom::Promise::MaybeRejectBrokenly() in GetDirectoryListingTask. r=baku (2106790950) - Bug 1209924 - Implement a general filtering mechanism for Directory::GetFilesAndDirectories, and add filtering of sensitive files/directories. r=baku (27b4a26262) - Force a repaint after DXGI device resets. (bug 1188019, r=bas) (09c999e6e5) - Bug 1163911 - Make responsive images block the document load event while the load task is queued. r=jst (0ee0e3db79) - Bug 1166138 - Make img srcset react to resize/viewport changes, r=jdm (91674519e6) - Bug 1194893 - Pref for default file upload directory. r=smaug (ec6d33d983) - bug 1116409: switch update server to sha2 cert; update in-tree pinning. r=rstrong,snorp,mfinkle,dkeeler (7c8f631f27) - bug 1116409: fix cert pinning on backup cert for aus5.mozilla.org. r=typofix (3c690cbc6d) - Bug 1167048 - Change default font for Thai script from serif to sans-serif. r=smontagu (15dc86c389) - Bug 1205570 - fix up font prefs for x-math lang group. r=heycam (03f1820752) - Bug 1071769: Use DrawTargetTiled on B2G. r=Bas (b80ce768f1) - fix misspatch of 1149343 (541dd7aac8) - Bug 1199766 - Disable ICE TCP SO gathering via user pref. r=bwc (80cdc9c662) - Bug 1187472 - only log UDP and TCP candidate gathering failures. r=bwc (bc3dcb02d0) - Bug 1190615 - Skip non-UDP STUN servers for UDP sockets. r=bwc (a2d1d914b5) - Bug 1187775 - skip host and reflexive ICE candidates if relay-only. r=bwc (7e2cba1685) - Bug 1185198 - use port 9 for TCP active candidates. r=bwc (0a89cb199d) - Bug 1177921 - Fix typo in STUN server name. r=drno (1ad43ced6b) - Bug 1178349 - Enable ice_unittests on desktop linux on CI. r=bwc (ce5ece8264) - Bug 1189041 - Add option to only gather addresses for default route. r=bwc (3651f2ff06) - Bug 1189040: add a whitelist for network interfaces to use with ICE/webrtc r=ekr (6f693af72c) - Bug 1189198 - don't start STUN transactions with a protocol mis-match. r=mtseng (a3b410e2a8) - Bug 1208096 - Handle various failure cases for TURN gathering better. r=drno (1d8e173448) - Bug 1211389 - Make absolutely sure the relay->srflx pointer doesn't dangle. r=drno (d59b0bf08d) - Bug 1215616: use base address for server rflx ICE candidates r=bwc (89d07331ac) - Bug 1207451 - removed framing from multi_tcp API. r=bwc (317f40f490) - Bug 1186590 - Part 1 - Enable interface prioritizer on all platforms. r=drno (036a69fdb3) - Bug 1194019 - New defaults for gather tests. r=bwc (8343ceab56) - Bug 1144933: Only check that remote candidate is loopback in TestLoopbackOnlySortOf. r=drno (1f53d824e4) - Bug 1186590 - Part 2 - Move hard-coded interface priority list into nrinterfaceprioritizer, and simplify some functions. r=drno (9f20fad21b) - Bug 1152137 - Part 1: Test case. r=ekr (6b50f06d90) - Bug 1152137 - Part 2: Remove attributes that could not be initted properly instead of just freeing them. r=ekr (ccdf81294a) - Bug 1200763 - Remove hard-coded STUN IP address from ice_unittest, and do a DNS lookup instead. r=drno (ae54a83363) - Bug 1208176 - Part 1: Add a couple of interface names. r=drno (b7ead0b476) - Bug 1208176 - Part 2: Add a one-sided trickle test case to ice_unittest. r=drno (ad6afedb1c) - Bug 1037618 - Relax candidate verification for TCP. r=bwc (0cad14c89e) - Bug 1208176 - Part 3: Be forgiving when we see prflx instead of host candidates in ice_unittest. r=drno (50bdec2ba3) - Bug 1035428: Re-register writeable callback after partially servicing the send queue. r=drno (2fdb7880fa) - Bug 1135753 - Mark some overridden virtual functions in WebRTC as MOZ_OVERRIDE; r=mt (97f451c97d) - Bug 950660: Part 4: Bridge TCPSocketChild to nr_socket r=bwc,jdm (654587b321) - Bug 971357: Log STUN responses at INFO instead of DEBUG. r=ekr (81b500df17) - Bug 1006809 - update triggered check behavior to RFC 5245. r+bwc r=mjf (31b718b5e5) - Bug 1208278 - improved STUN request timeout handling. r=bwc (cf470fb12f) - Bug 1142964 - Fix ICE tiebreaker on Windows. r=bwc (0d2fd78252) - Bug 1219557 - don't pair candidates from different reserved networks. r=mt r=bwc (24d3e5106c) - Bug 1220441 - Improve gather trickle ice unit tests. r=bwc r=mjf (96f76c6c8c) - Bug 1205421 - fix DNS resolution of STUN server in ice_unittest. r=bwc (5d5b153358) - Bug 1206465 - removed ice_ctx from TestStunTcpServer. r=bwc (9a0df03894) - ug 1008792 - Check for valid pointer before using. r=bwc (7660fd0a71) - Bug 1233101 - Use MOZ_LIKELY in js_new etc to help branch prediction; r=terrence (31fb244734) - Bug 1225565 - Fix module import cycle detection r=shu (370dc26ee8) - Bug 1225558 - Improve module error messages r=shu (83b6038bb3) - Bug 1225561 - Don't allow a module to export non-existent local bindings r=shu (41f065891a) - Bug 1233124 - Remove mis-named duplicate typedefs for rooted import and export entries r=terrence (17a60bdb39) - Bug 1208464 - Implement proposed ES7 functions Object.values and Object.entries. r=evilpie (615193d0fb) - Bug 1226549 - added assert check for matches pointer in for prevent null dereference. r=hv1989 (d321ad0385) - Bug 1232113 - "Make the format specifiers in JS_snprintf() invocations more portable". r=jcoppeard (7c58b79a53) - Bug 1232446 - Re-enable method calls in SelfHosted code using new anti-content checks. (r=till) (ba7dc22ff8) - Bug 1232159 - Stop using pseudo-Uint32Array in SelfHosted code. (r=till) (c325f8ff58) - Bug 1226235 - Print file and line info for failing assert in self-hosted code. r=efaust (4a8d54d38b) - fix misspatch (fca2efc1f1) - Bug 1186003 - Switch automated builds to Gtk+3. r=mshal (658ad843b7) - Bug 1181342 - tooltool manifests and build-clang config for clang 3.6 r=rail (6264b4df68) - Bug 1181342 - Follow up to use the unpack feature of tooltool instead of setup.sh r=glandium (96bb3b2062) - Bug 1181255 - Mozconfigs for tsan builds. r=glandium (19250f4cc1) - Bug 1181255 - Get tsan builds on gtk3. r=glandium (e5ffd1c02f) - Bug 1187664 - Create a fontconfig cache so that Firefox doesn't have to do it itself when run on build automation. r=mshal (8ce567bd4c) - Bug 1188780 - Include debug symbols in gtk3 tooltool package. r=mshal (a5b573aa58) - Bug 1188780: remove setup.sh invocations, as they fail outside the mock environment; r=glandium a=RyanVM (058e306cac) - Bug 1178513 - Fix non-unified bustage. r=wchen (543d1e5497) - Bug 1162789 - Add a comment explaining why mForm is not set to null during unlink (eaa2a82048) - Bug 1189655 - Define MOZ_HAVE_CXX11_CONSTEXPR on VS2015 or later. r=Waldo (2d134e3b41) - Bug 1231758 - Fix bogus assertion in BCE for Annex B function assignment. (r=jorendorff) (701b2530b9) - Bug 1233100 - Ensure that derived constructor bad return value errors are thrown before leaving the containing block. (r=shu) (52f5bcf0a5) - Bug 1232022, 1232449 - Address forgotten review nits and fix bogus error message. (rs=Waldo) (dfd9d5e388) - Bug 1233121 - Refactor ObjectBox tracing r=terrence (876a140535) - Bug 1231647 - Check for duplicate exported let and const in modules r=shu (99f53ad443)
886 lines
25 KiB
C++
886 lines
25 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include <ostream>
|
|
#include "platform.h"
|
|
#include "mozilla/HashFunctions.h"
|
|
|
|
#ifndef SPS_STANDALONE
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXULAppAPI.h"
|
|
|
|
// JS
|
|
#include "jsapi.h"
|
|
#include "jsfriendapi.h"
|
|
#include "js/TrackedOptimizationInfo.h"
|
|
#endif
|
|
|
|
// Self
|
|
#include "ProfileEntry.h"
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER < 1900
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
using mozilla::MakeUnique;
|
|
using mozilla::UniquePtr;
|
|
using mozilla::Maybe;
|
|
using mozilla::Some;
|
|
using mozilla::Nothing;
|
|
using mozilla::JSONWriter;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// BEGIN ProfileEntry
|
|
|
|
ProfileEntry::ProfileEntry()
|
|
: mTagData(nullptr)
|
|
, mTagName(0)
|
|
{ }
|
|
|
|
// aTagData must not need release (i.e. be a string from the text segment)
|
|
ProfileEntry::ProfileEntry(char aTagName, const char *aTagData)
|
|
: mTagData(aTagData)
|
|
, mTagName(aTagName)
|
|
{ }
|
|
|
|
ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker)
|
|
: mTagMarker(aTagMarker)
|
|
, mTagName(aTagName)
|
|
{ }
|
|
|
|
ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr)
|
|
: mTagPtr(aTagPtr)
|
|
, mTagName(aTagName)
|
|
{ }
|
|
|
|
ProfileEntry::ProfileEntry(char aTagName, double aTagDouble)
|
|
: mTagDouble(aTagDouble)
|
|
, mTagName(aTagName)
|
|
{ }
|
|
|
|
ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset)
|
|
: mTagOffset(aTagOffset)
|
|
, mTagName(aTagName)
|
|
{ }
|
|
|
|
ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress)
|
|
: mTagAddress(aTagAddress)
|
|
, mTagName(aTagName)
|
|
{ }
|
|
|
|
ProfileEntry::ProfileEntry(char aTagName, int aTagInt)
|
|
: mTagInt(aTagInt)
|
|
, mTagName(aTagName)
|
|
{ }
|
|
|
|
ProfileEntry::ProfileEntry(char aTagName, char aTagChar)
|
|
: mTagChar(aTagChar)
|
|
, mTagName(aTagName)
|
|
{ }
|
|
|
|
bool ProfileEntry::is_ent_hint(char hintChar) {
|
|
return mTagName == 'h' && mTagChar == hintChar;
|
|
}
|
|
|
|
bool ProfileEntry::is_ent_hint() {
|
|
return mTagName == 'h';
|
|
}
|
|
|
|
bool ProfileEntry::is_ent(char tagChar) {
|
|
return mTagName == tagChar;
|
|
}
|
|
|
|
void* ProfileEntry::get_tagPtr() {
|
|
// No consistency checking. Oh well.
|
|
return mTagPtr;
|
|
}
|
|
|
|
// END ProfileEntry
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
class JSONSchemaWriter
|
|
{
|
|
JSONWriter& mWriter;
|
|
uint32_t mIndex;
|
|
|
|
public:
|
|
explicit JSONSchemaWriter(JSONWriter& aWriter)
|
|
: mWriter(aWriter)
|
|
, mIndex(0)
|
|
{
|
|
aWriter.StartObjectProperty("schema");
|
|
}
|
|
|
|
void WriteField(const char* aName) {
|
|
mWriter.IntProperty(aName, mIndex++);
|
|
}
|
|
|
|
~JSONSchemaWriter() {
|
|
mWriter.EndObject();
|
|
}
|
|
};
|
|
|
|
#ifndef SPS_STANDALONE
|
|
class StreamOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp
|
|
{
|
|
JSONWriter& mWriter;
|
|
UniqueJSONStrings& mUniqueStrings;
|
|
bool mStartedTypeList;
|
|
|
|
public:
|
|
StreamOptimizationTypeInfoOp(JSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings)
|
|
: mWriter(aWriter)
|
|
, mUniqueStrings(aUniqueStrings)
|
|
, mStartedTypeList(false)
|
|
{ }
|
|
|
|
void readType(const char* keyedBy, const char* name,
|
|
const char* location, Maybe<unsigned> lineno) override {
|
|
if (!mStartedTypeList) {
|
|
mStartedTypeList = true;
|
|
mWriter.StartObjectElement();
|
|
mWriter.StartArrayProperty("typeset");
|
|
}
|
|
|
|
mWriter.StartObjectElement();
|
|
{
|
|
mUniqueStrings.WriteProperty(mWriter, "keyedBy", keyedBy);
|
|
if (name) {
|
|
mUniqueStrings.WriteProperty(mWriter, "name", name);
|
|
}
|
|
if (location) {
|
|
mUniqueStrings.WriteProperty(mWriter, "location", location);
|
|
}
|
|
if (lineno.isSome()) {
|
|
mWriter.IntProperty("line", *lineno);
|
|
}
|
|
}
|
|
mWriter.EndObject();
|
|
}
|
|
|
|
void operator()(JS::TrackedTypeSite site, const char* mirType) override {
|
|
if (mStartedTypeList) {
|
|
mWriter.EndArray();
|
|
mStartedTypeList = false;
|
|
} else {
|
|
mWriter.StartObjectElement();
|
|
}
|
|
|
|
{
|
|
mUniqueStrings.WriteProperty(mWriter, "site", JS::TrackedTypeSiteString(site));
|
|
mUniqueStrings.WriteProperty(mWriter, "mirType", mirType);
|
|
}
|
|
mWriter.EndObject();
|
|
}
|
|
};
|
|
|
|
// As mentioned in ProfileEntry.h, the JSON format contains many arrays whose
|
|
// elements are laid out according to various schemas to help
|
|
// de-duplication. This RAII class helps write these arrays by keeping track of
|
|
// the last non-null element written and adding the appropriate number of null
|
|
// elements when writing new non-null elements. It also automatically opens and
|
|
// closes an array element on the given JSON writer.
|
|
//
|
|
// Example usage:
|
|
//
|
|
// // Define the schema of elements in this type of array: [FOO, BAR, BAZ]
|
|
// enum Schema : uint32_t {
|
|
// FOO = 0,
|
|
// BAR = 1,
|
|
// BAZ = 2
|
|
// };
|
|
//
|
|
// AutoArraySchemaWriter writer(someJsonWriter, someUniqueStrings);
|
|
// if (shouldWriteFoo) {
|
|
// writer.IntElement(FOO, getFoo());
|
|
// }
|
|
// ... etc ...
|
|
class MOZ_RAII AutoArraySchemaWriter
|
|
{
|
|
friend class AutoObjectWriter;
|
|
|
|
SpliceableJSONWriter& mJSONWriter;
|
|
UniqueJSONStrings* mStrings;
|
|
uint32_t mNextFreeIndex;
|
|
|
|
public:
|
|
AutoArraySchemaWriter(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aStrings)
|
|
: mJSONWriter(aWriter)
|
|
, mStrings(&aStrings)
|
|
, mNextFreeIndex(0)
|
|
{
|
|
mJSONWriter.StartArrayElement();
|
|
}
|
|
|
|
// If you don't have access to a UniqueStrings, you had better not try and
|
|
// write a string element down the line!
|
|
explicit AutoArraySchemaWriter(SpliceableJSONWriter& aWriter)
|
|
: mJSONWriter(aWriter)
|
|
, mStrings(nullptr)
|
|
, mNextFreeIndex(0)
|
|
{
|
|
mJSONWriter.StartArrayElement();
|
|
}
|
|
|
|
~AutoArraySchemaWriter() {
|
|
mJSONWriter.EndArray();
|
|
}
|
|
|
|
void FillUpTo(uint32_t aIndex) {
|
|
MOZ_ASSERT(aIndex >= mNextFreeIndex);
|
|
mJSONWriter.NullElements(aIndex - mNextFreeIndex);
|
|
mNextFreeIndex = aIndex + 1;
|
|
}
|
|
|
|
void IntElement(uint32_t aIndex, uint32_t aValue) {
|
|
FillUpTo(aIndex);
|
|
mJSONWriter.IntElement(aValue);
|
|
}
|
|
|
|
void DoubleElement(uint32_t aIndex, double aValue) {
|
|
FillUpTo(aIndex);
|
|
mJSONWriter.DoubleElement(aValue);
|
|
}
|
|
|
|
void StringElement(uint32_t aIndex, const char* aValue) {
|
|
MOZ_RELEASE_ASSERT(mStrings);
|
|
FillUpTo(aIndex);
|
|
mStrings->WriteElement(mJSONWriter, aValue);
|
|
}
|
|
};
|
|
|
|
class StreamOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp
|
|
{
|
|
SpliceableJSONWriter& mWriter;
|
|
UniqueJSONStrings& mUniqueStrings;
|
|
|
|
public:
|
|
StreamOptimizationAttemptsOp(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings)
|
|
: mWriter(aWriter),
|
|
mUniqueStrings(aUniqueStrings)
|
|
{ }
|
|
|
|
void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
|
|
enum Schema : uint32_t {
|
|
STRATEGY = 0,
|
|
OUTCOME = 1
|
|
};
|
|
|
|
AutoArraySchemaWriter writer(mWriter, mUniqueStrings);
|
|
writer.StringElement(STRATEGY, JS::TrackedStrategyString(strategy));
|
|
writer.StringElement(OUTCOME, JS::TrackedOutcomeString(outcome));
|
|
}
|
|
};
|
|
|
|
class StreamJSFramesOp : public JS::ForEachProfiledFrameOp
|
|
{
|
|
void* mReturnAddress;
|
|
UniqueStacks::Stack& mStack;
|
|
unsigned mDepth;
|
|
|
|
public:
|
|
StreamJSFramesOp(void* aReturnAddr, UniqueStacks::Stack& aStack)
|
|
: mReturnAddress(aReturnAddr)
|
|
, mStack(aStack)
|
|
, mDepth(0)
|
|
{ }
|
|
|
|
unsigned depth() const {
|
|
MOZ_ASSERT(mDepth > 0);
|
|
return mDepth;
|
|
}
|
|
|
|
void operator()(const JS::ForEachProfiledFrameOp::FrameHandle& aFrameHandle) override {
|
|
UniqueStacks::OnStackFrameKey frameKey(mReturnAddress, mDepth, aFrameHandle);
|
|
mStack.AppendFrame(frameKey);
|
|
mDepth++;
|
|
}
|
|
};
|
|
#endif
|
|
|
|
uint32_t UniqueJSONStrings::GetOrAddIndex(const char* aStr)
|
|
{
|
|
uint32_t index;
|
|
StringKey key(aStr);
|
|
|
|
auto it = mStringToIndexMap.find(key);
|
|
|
|
if (it != mStringToIndexMap.end()) {
|
|
return it->second;
|
|
}
|
|
index = mStringToIndexMap.size();
|
|
mStringToIndexMap[key] = index;
|
|
mStringTableWriter.StringElement(aStr);
|
|
return index;
|
|
}
|
|
|
|
bool UniqueStacks::FrameKey::operator==(const FrameKey& aOther) const
|
|
{
|
|
return mLocation == aOther.mLocation &&
|
|
mLine == aOther.mLine &&
|
|
mCategory == aOther.mCategory &&
|
|
mJITAddress == aOther.mJITAddress &&
|
|
mJITDepth == aOther.mJITDepth;
|
|
}
|
|
|
|
bool UniqueStacks::StackKey::operator==(const StackKey& aOther) const
|
|
{
|
|
MOZ_ASSERT_IF(mPrefix == aOther.mPrefix, mPrefixHash == aOther.mPrefixHash);
|
|
return mPrefix == aOther.mPrefix && mFrame == aOther.mFrame;
|
|
}
|
|
|
|
UniqueStacks::Stack::Stack(UniqueStacks& aUniqueStacks, const OnStackFrameKey& aRoot)
|
|
: mUniqueStacks(aUniqueStacks)
|
|
, mStack(aUniqueStacks.GetOrAddFrameIndex(aRoot))
|
|
{
|
|
}
|
|
|
|
void UniqueStacks::Stack::AppendFrame(const OnStackFrameKey& aFrame)
|
|
{
|
|
// Compute the prefix hash and index before mutating mStack.
|
|
uint32_t prefixHash = mStack.Hash();
|
|
uint32_t prefix = mUniqueStacks.GetOrAddStackIndex(mStack);
|
|
mStack.UpdateHash(prefixHash, prefix, mUniqueStacks.GetOrAddFrameIndex(aFrame));
|
|
}
|
|
|
|
uint32_t UniqueStacks::Stack::GetOrAddIndex() const
|
|
{
|
|
return mUniqueStacks.GetOrAddStackIndex(mStack);
|
|
}
|
|
|
|
uint32_t UniqueStacks::FrameKey::Hash() const
|
|
{
|
|
uint32_t hash = 0;
|
|
if (!mLocation.IsEmpty()) {
|
|
#ifdef SPS_STANDALONE
|
|
hash = mozilla::HashString(mLocation.c_str());
|
|
#else
|
|
hash = mozilla::HashString(mLocation.get());
|
|
#endif
|
|
}
|
|
if (mLine.isSome()) {
|
|
hash = mozilla::AddToHash(hash, *mLine);
|
|
}
|
|
if (mCategory.isSome()) {
|
|
hash = mozilla::AddToHash(hash, *mCategory);
|
|
}
|
|
if (mJITAddress.isSome()) {
|
|
hash = mozilla::AddToHash(hash, *mJITAddress);
|
|
if (mJITDepth.isSome()) {
|
|
hash = mozilla::AddToHash(hash, *mJITDepth);
|
|
}
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
uint32_t UniqueStacks::StackKey::Hash() const
|
|
{
|
|
if (mPrefix.isNothing()) {
|
|
return mozilla::HashGeneric(mFrame);
|
|
}
|
|
return mozilla::AddToHash(*mPrefixHash, mFrame);
|
|
}
|
|
|
|
UniqueStacks::Stack UniqueStacks::BeginStack(const OnStackFrameKey& aRoot)
|
|
{
|
|
return Stack(*this, aRoot);
|
|
}
|
|
|
|
UniqueStacks::UniqueStacks(JSRuntime* aRuntime)
|
|
: mRuntime(aRuntime)
|
|
, mFrameCount(0)
|
|
{
|
|
mFrameTableWriter.StartBareList();
|
|
mStackTableWriter.StartBareList();
|
|
}
|
|
|
|
#ifdef SPS_STANDALONE
|
|
uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack)
|
|
{
|
|
uint32_t index;
|
|
auto it = mStackToIndexMap.find(aStack);
|
|
|
|
if (it != mStackToIndexMap.end()) {
|
|
return it->second;
|
|
}
|
|
|
|
index = mStackToIndexMap.size();
|
|
mStackToIndexMap[aStack] = index;
|
|
StreamStack(aStack);
|
|
return index;
|
|
}
|
|
#else
|
|
uint32_t UniqueStacks::GetOrAddStackIndex(const StackKey& aStack)
|
|
{
|
|
uint32_t index;
|
|
if (mStackToIndexMap.Get(aStack, &index)) {
|
|
MOZ_ASSERT(index < mStackToIndexMap.Count());
|
|
return index;
|
|
}
|
|
|
|
index = mStackToIndexMap.Count();
|
|
mStackToIndexMap.Put(aStack, index);
|
|
StreamStack(aStack);
|
|
return index;
|
|
}
|
|
#endif
|
|
|
|
#ifdef SPS_STANDALONE
|
|
uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame)
|
|
{
|
|
uint32_t index;
|
|
auto it = mFrameToIndexMap.find(aFrame);
|
|
if (it != mFrameToIndexMap.end()) {
|
|
MOZ_ASSERT(it->second < mFrameCount);
|
|
return it->second;
|
|
}
|
|
|
|
// A manual count is used instead of mFrameToIndexMap.Count() due to
|
|
// forwarding of canonical JIT frames above.
|
|
index = mFrameCount++;
|
|
mFrameToIndexMap[aFrame] = index;
|
|
StreamFrame(aFrame);
|
|
return index;
|
|
}
|
|
#else
|
|
uint32_t UniqueStacks::GetOrAddFrameIndex(const OnStackFrameKey& aFrame)
|
|
{
|
|
uint32_t index;
|
|
if (mFrameToIndexMap.Get(aFrame, &index)) {
|
|
MOZ_ASSERT(index < mFrameCount);
|
|
return index;
|
|
}
|
|
|
|
// If aFrame isn't canonical, forward it to the canonical frame's index.
|
|
if (aFrame.mJITFrameHandle) {
|
|
void* canonicalAddr = aFrame.mJITFrameHandle->canonicalAddress();
|
|
if (canonicalAddr != *aFrame.mJITAddress) {
|
|
OnStackFrameKey canonicalKey(canonicalAddr, *aFrame.mJITDepth, *aFrame.mJITFrameHandle);
|
|
uint32_t canonicalIndex = GetOrAddFrameIndex(canonicalKey);
|
|
mFrameToIndexMap.Put(aFrame, canonicalIndex);
|
|
return canonicalIndex;
|
|
}
|
|
}
|
|
|
|
// A manual count is used instead of mFrameToIndexMap.Count() due to
|
|
// forwarding of canonical JIT frames above.
|
|
index = mFrameCount++;
|
|
mFrameToIndexMap.Put(aFrame, index);
|
|
StreamFrame(aFrame);
|
|
return index;
|
|
}
|
|
#endif
|
|
|
|
uint32_t UniqueStacks::LookupJITFrameDepth(void* aAddr)
|
|
{
|
|
uint32_t depth;
|
|
|
|
auto it = mJITFrameDepthMap.find(aAddr);
|
|
if (it != mJITFrameDepthMap.end()) {
|
|
depth = it->second;
|
|
MOZ_ASSERT(depth > 0);
|
|
return depth;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void UniqueStacks::AddJITFrameDepth(void* aAddr, unsigned depth)
|
|
{
|
|
mJITFrameDepthMap[aAddr] = depth;
|
|
}
|
|
|
|
void UniqueStacks::SpliceFrameTableElements(SpliceableJSONWriter& aWriter)
|
|
{
|
|
mFrameTableWriter.EndBareList();
|
|
aWriter.TakeAndSplice(mFrameTableWriter.WriteFunc());
|
|
}
|
|
|
|
void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter)
|
|
{
|
|
mStackTableWriter.EndBareList();
|
|
aWriter.TakeAndSplice(mStackTableWriter.WriteFunc());
|
|
}
|
|
|
|
void UniqueStacks::StreamStack(const StackKey& aStack)
|
|
{
|
|
enum Schema : uint32_t {
|
|
PREFIX = 0,
|
|
FRAME = 1
|
|
};
|
|
|
|
AutoArraySchemaWriter writer(mStackTableWriter, mUniqueStrings);
|
|
if (aStack.mPrefix.isSome()) {
|
|
writer.IntElement(PREFIX, *aStack.mPrefix);
|
|
}
|
|
writer.IntElement(FRAME, aStack.mFrame);
|
|
}
|
|
|
|
void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame)
|
|
{
|
|
enum Schema : uint32_t {
|
|
LOCATION = 0,
|
|
IMPLEMENTATION = 1,
|
|
OPTIMIZATIONS = 2,
|
|
LINE = 3,
|
|
CATEGORY = 4
|
|
};
|
|
|
|
AutoArraySchemaWriter writer(mFrameTableWriter, mUniqueStrings);
|
|
|
|
#ifndef SPS_STANDALONE
|
|
if (!aFrame.mJITFrameHandle) {
|
|
#else
|
|
{
|
|
#endif
|
|
#ifdef SPS_STANDALONE
|
|
writer.StringElement(LOCATION, aFrame.mLocation.c_str());
|
|
#else
|
|
writer.StringElement(LOCATION, aFrame.mLocation.get());
|
|
#endif
|
|
if (aFrame.mLine.isSome()) {
|
|
writer.IntElement(LINE, *aFrame.mLine);
|
|
}
|
|
if (aFrame.mCategory.isSome()) {
|
|
writer.IntElement(CATEGORY, *aFrame.mCategory);
|
|
}
|
|
}
|
|
#ifndef SPS_STANDALONE
|
|
else {
|
|
const JS::ForEachProfiledFrameOp::FrameHandle& jitFrame = *aFrame.mJITFrameHandle;
|
|
|
|
writer.StringElement(LOCATION, jitFrame.label());
|
|
|
|
JS::ProfilingFrameIterator::FrameKind frameKind = jitFrame.frameKind();
|
|
MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion ||
|
|
frameKind == JS::ProfilingFrameIterator::Frame_Baseline);
|
|
writer.StringElement(IMPLEMENTATION,
|
|
frameKind == JS::ProfilingFrameIterator::Frame_Ion
|
|
? "ion"
|
|
: "baseline");
|
|
|
|
if (jitFrame.hasTrackedOptimizations()) {
|
|
writer.FillUpTo(OPTIMIZATIONS);
|
|
mFrameTableWriter.StartObjectElement();
|
|
{
|
|
mFrameTableWriter.StartArrayProperty("types");
|
|
{
|
|
StreamOptimizationTypeInfoOp typeInfoOp(mFrameTableWriter, mUniqueStrings);
|
|
jitFrame.forEachOptimizationTypeInfo(typeInfoOp);
|
|
}
|
|
mFrameTableWriter.EndArray();
|
|
|
|
JS::Rooted<JSScript*> script(mRuntime);
|
|
jsbytecode* pc;
|
|
mFrameTableWriter.StartObjectProperty("attempts");
|
|
{
|
|
{
|
|
JSONSchemaWriter schema(mFrameTableWriter);
|
|
schema.WriteField("strategy");
|
|
schema.WriteField("outcome");
|
|
}
|
|
|
|
mFrameTableWriter.StartArrayProperty("data");
|
|
{
|
|
StreamOptimizationAttemptsOp attemptOp(mFrameTableWriter, mUniqueStrings);
|
|
jitFrame.forEachOptimizationAttempt(attemptOp, script.address(), &pc);
|
|
}
|
|
mFrameTableWriter.EndArray();
|
|
}
|
|
mFrameTableWriter.EndObject();
|
|
|
|
if (JSAtom* name = js::GetPropertyNameFromPC(script, pc)) {
|
|
char buf[512];
|
|
JS_PutEscapedFlatString(buf, mozilla::ArrayLength(buf), js::AtomToFlatString(name), 0);
|
|
mUniqueStrings.WriteProperty(mFrameTableWriter, "propertyName", buf);
|
|
}
|
|
|
|
unsigned line, column;
|
|
line = JS_PCToLineNumber(script, pc, &column);
|
|
mFrameTableWriter.IntProperty("line", line);
|
|
mFrameTableWriter.IntProperty("column", column);
|
|
}
|
|
mFrameTableWriter.EndObject();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
struct ProfileSample
|
|
{
|
|
uint32_t mStack;
|
|
Maybe<double> mTime;
|
|
Maybe<double> mResponsiveness;
|
|
Maybe<double> mRSS;
|
|
Maybe<double> mUSS;
|
|
Maybe<int> mFrameNumber;
|
|
Maybe<double> mPower;
|
|
};
|
|
|
|
static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample)
|
|
{
|
|
enum Schema : uint32_t {
|
|
STACK = 0,
|
|
TIME = 1,
|
|
RESPONSIVENESS = 2,
|
|
RSS = 3,
|
|
USS = 4,
|
|
FRAME_NUMBER = 5,
|
|
POWER = 6
|
|
};
|
|
|
|
AutoArraySchemaWriter writer(aWriter);
|
|
|
|
writer.IntElement(STACK, aSample.mStack);
|
|
|
|
if (aSample.mTime.isSome()) {
|
|
writer.DoubleElement(TIME, *aSample.mTime);
|
|
}
|
|
|
|
if (aSample.mResponsiveness.isSome()) {
|
|
writer.DoubleElement(RESPONSIVENESS, *aSample.mResponsiveness);
|
|
}
|
|
|
|
if (aSample.mRSS.isSome()) {
|
|
writer.DoubleElement(RSS, *aSample.mRSS);
|
|
}
|
|
|
|
if (aSample.mUSS.isSome()) {
|
|
writer.DoubleElement(USS, *aSample.mUSS);
|
|
}
|
|
|
|
if (aSample.mFrameNumber.isSome()) {
|
|
writer.IntElement(FRAME_NUMBER, *aSample.mFrameNumber);
|
|
}
|
|
|
|
if (aSample.mPower.isSome()) {
|
|
writer.DoubleElement(POWER, *aSample.mPower);
|
|
}
|
|
}
|
|
|
|
void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
|
|
double aSinceTime, JSRuntime* aRuntime,
|
|
UniqueStacks& aUniqueStacks)
|
|
{
|
|
Maybe<ProfileSample> sample;
|
|
int readPos = mReadPos;
|
|
int currentThreadID = -1;
|
|
Maybe<double> currentTime;
|
|
UniquePtr<char[]> tagBuff = MakeUnique<char[]>(DYNAMIC_MAX_STRING);
|
|
|
|
while (readPos != mWritePos) {
|
|
ProfileEntry entry = mEntries[readPos];
|
|
if (entry.mTagName == 'T') {
|
|
currentThreadID = entry.mTagInt;
|
|
currentTime.reset();
|
|
int readAheadPos = (readPos + 1) % mEntrySize;
|
|
if (readAheadPos != mWritePos) {
|
|
ProfileEntry readAheadEntry = mEntries[readAheadPos];
|
|
if (readAheadEntry.mTagName == 't') {
|
|
currentTime = Some(readAheadEntry.mTagDouble);
|
|
}
|
|
}
|
|
}
|
|
if (currentThreadID == aThreadId && (currentTime.isNothing() || *currentTime >= aSinceTime)) {
|
|
switch (entry.mTagName) {
|
|
case 'r':
|
|
if (sample.isSome()) {
|
|
sample->mResponsiveness = Some(entry.mTagDouble);
|
|
}
|
|
break;
|
|
case 'p':
|
|
if (sample.isSome()) {
|
|
sample->mPower = Some(entry.mTagDouble);
|
|
}
|
|
break;
|
|
case 'R':
|
|
if (sample.isSome()) {
|
|
sample->mRSS = Some(entry.mTagDouble);
|
|
}
|
|
break;
|
|
case 'U':
|
|
if (sample.isSome()) {
|
|
sample->mUSS = Some(entry.mTagDouble);
|
|
}
|
|
break;
|
|
case 'f':
|
|
if (sample.isSome()) {
|
|
sample->mFrameNumber = Some(entry.mTagInt);
|
|
}
|
|
break;
|
|
case 's':
|
|
{
|
|
// end the previous sample if there was one
|
|
if (sample.isSome()) {
|
|
WriteSample(aWriter, *sample);
|
|
sample.reset();
|
|
}
|
|
// begin the next sample
|
|
sample.emplace();
|
|
sample->mTime = currentTime;
|
|
|
|
// Seek forward through the entire sample, looking for frames
|
|
// this is an easier approach to reason about than adding more
|
|
// control variables and cases to the loop that goes through the buffer once
|
|
|
|
UniqueStacks::Stack stack =
|
|
aUniqueStacks.BeginStack(UniqueStacks::OnStackFrameKey("(root)"));
|
|
|
|
int framePos = (readPos + 1) % mEntrySize;
|
|
ProfileEntry frame = mEntries[framePos];
|
|
while (framePos != mWritePos && frame.mTagName != 's' && frame.mTagName != 'T') {
|
|
int incBy = 1;
|
|
frame = mEntries[framePos];
|
|
|
|
// Read ahead to the next tag, if it's a 'd' tag process it now
|
|
const char* tagStringData = frame.mTagData;
|
|
int readAheadPos = (framePos + 1) % mEntrySize;
|
|
// Make sure the string is always null terminated if it fills up
|
|
// DYNAMIC_MAX_STRING-2
|
|
tagBuff[DYNAMIC_MAX_STRING-1] = '\0';
|
|
|
|
if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') {
|
|
tagStringData = processDynamicTag(framePos, &incBy, tagBuff.get());
|
|
}
|
|
|
|
// Write one frame. It can have either
|
|
// 1. only location - 'l' containing a memory address
|
|
// 2. location and line number - 'c' followed by 'd's,
|
|
// an optional 'n' and an optional 'y'
|
|
// 3. a JIT return address - 'j' containing native code address
|
|
if (frame.mTagName == 'l') {
|
|
// Bug 753041
|
|
// We need a double cast here to tell GCC that we don't want to sign
|
|
// extend 32-bit addresses starting with 0xFXXXXXX.
|
|
unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr;
|
|
snprintf(tagBuff.get(), DYNAMIC_MAX_STRING, "%#llx", pc);
|
|
stack.AppendFrame(UniqueStacks::OnStackFrameKey(tagBuff.get()));
|
|
} else if (frame.mTagName == 'c') {
|
|
UniqueStacks::OnStackFrameKey frameKey(tagStringData);
|
|
readAheadPos = (framePos + incBy) % mEntrySize;
|
|
if (readAheadPos != mWritePos &&
|
|
mEntries[readAheadPos].mTagName == 'n') {
|
|
frameKey.mLine = Some((unsigned) mEntries[readAheadPos].mTagInt);
|
|
incBy++;
|
|
}
|
|
readAheadPos = (framePos + incBy) % mEntrySize;
|
|
if (readAheadPos != mWritePos &&
|
|
mEntries[readAheadPos].mTagName == 'y') {
|
|
frameKey.mCategory = Some((unsigned) mEntries[readAheadPos].mTagInt);
|
|
incBy++;
|
|
}
|
|
stack.AppendFrame(frameKey);
|
|
#ifndef SPS_STANDALONE
|
|
} else if (frame.mTagName == 'J') {
|
|
// A JIT frame may expand to multiple frames due to inlining.
|
|
void* pc = frame.mTagPtr;
|
|
unsigned depth = aUniqueStacks.LookupJITFrameDepth(pc);
|
|
if (depth == 0) {
|
|
StreamJSFramesOp framesOp(pc, stack);
|
|
JS::ForEachProfiledFrame(aRuntime, pc, framesOp);
|
|
aUniqueStacks.AddJITFrameDepth(pc, framesOp.depth());
|
|
} else {
|
|
for (unsigned i = 0; i < depth; i++) {
|
|
UniqueStacks::OnStackFrameKey inlineFrameKey(pc, i);
|
|
stack.AppendFrame(inlineFrameKey);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
framePos = (framePos + incBy) % mEntrySize;
|
|
}
|
|
|
|
sample->mStack = stack.GetOrAddIndex();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
readPos = (readPos + 1) % mEntrySize;
|
|
}
|
|
if (sample.isSome()) {
|
|
WriteSample(aWriter, *sample);
|
|
}
|
|
}
|
|
|
|
void ProfileBuffer::StreamMarkersToJSON(SpliceableJSONWriter& aWriter, int aThreadId,
|
|
double aSinceTime, UniqueStacks& aUniqueStacks)
|
|
{
|
|
int readPos = mReadPos;
|
|
int currentThreadID = -1;
|
|
while (readPos != mWritePos) {
|
|
ProfileEntry entry = mEntries[readPos];
|
|
if (entry.mTagName == 'T') {
|
|
currentThreadID = entry.mTagInt;
|
|
} else if (currentThreadID == aThreadId && entry.mTagName == 'm') {
|
|
const ProfilerMarker* marker = entry.getMarker();
|
|
if (marker->GetTime() >= aSinceTime) {
|
|
entry.getMarker()->StreamJSON(aWriter, aUniqueStacks);
|
|
}
|
|
}
|
|
readPos = (readPos + 1) % mEntrySize;
|
|
}
|
|
}
|
|
|
|
int ProfileBuffer::FindLastSampleOfThread(int aThreadId)
|
|
{
|
|
// We search backwards from mWritePos-1 to mReadPos.
|
|
// Adding mEntrySize makes the result of the modulus positive.
|
|
for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize;
|
|
readPos != (mReadPos + mEntrySize - 1) % mEntrySize;
|
|
readPos = (readPos + mEntrySize - 1) % mEntrySize) {
|
|
ProfileEntry entry = mEntries[readPos];
|
|
if (entry.mTagName == 'T' && entry.mTagInt == aThreadId) {
|
|
return readPos;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void ProfileBuffer::DuplicateLastSample(int aThreadId)
|
|
{
|
|
int lastSampleStartPos = FindLastSampleOfThread(aThreadId);
|
|
if (lastSampleStartPos == -1) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mEntries[lastSampleStartPos].mTagName == 'T');
|
|
|
|
addTag(mEntries[lastSampleStartPos]);
|
|
|
|
// Go through the whole entry and duplicate it, until we find the next one.
|
|
for (int readPos = (lastSampleStartPos + 1) % mEntrySize;
|
|
readPos != mWritePos;
|
|
readPos = (readPos + 1) % mEntrySize) {
|
|
switch (mEntries[readPos].mTagName) {
|
|
case 'T':
|
|
// We're done.
|
|
return;
|
|
case 't':
|
|
// Copy with new time
|
|
addTag(ProfileEntry('t', (mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()));
|
|
break;
|
|
case 'm':
|
|
// Don't copy markers
|
|
break;
|
|
// Copy anything else we don't know about
|
|
// L, B, S, c, s, d, l, f, h, r, t, p
|
|
default:
|
|
addTag(mEntries[readPos]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// END ProfileBuffer
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
// BEGIN ThreadProfile
|
|
|
|
// END ThreadProfile
|
|
////////////////////////////////////////////////////////////////////////
|