mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
Bug 1385953 - Remove MemProfiler; r=jandem
This feature isn't currently used or being planned to be used in the near future and has some overhead that makes it hard to justify to keep around, so it's better to remove it and revive it from VCS history if we need it later.
This commit is contained in:
parent
f47c170811
commit
38bfadf1a9
@ -261,9 +261,6 @@
|
||||
@RESPATH@/components/layout_xul_tree.xpt
|
||||
@RESPATH@/components/layout_xul.xpt
|
||||
@RESPATH@/components/locale.xpt
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
@RESPATH@/components/memory_profiler.xpt
|
||||
#endif
|
||||
@RESPATH@/browser/components/migration.xpt
|
||||
@RESPATH@/components/mimetype.xpt
|
||||
@RESPATH@/components/mozfind.xpt
|
||||
|
@ -1123,8 +1123,6 @@ class GCRuntime
|
||||
GCSchedulingTunables tunables;
|
||||
GCSchedulingState schedulingState;
|
||||
|
||||
MemProfiler mMemProfiler;
|
||||
|
||||
// State used for managing atom mark bitmaps in each zone. Protected by the
|
||||
// exclusive access lock.
|
||||
AtomMarkingRuntime atomMarking;
|
||||
|
@ -460,7 +460,6 @@ class FreeSpan
|
||||
}
|
||||
checkSpan(arena);
|
||||
JS_EXTRA_POISON(reinterpret_cast<void*>(thing), JS_ALLOCATED_TENURED_PATTERN, thingSize);
|
||||
MemProfiler::SampleTenured(reinterpret_cast<void*>(thing), thingSize);
|
||||
return reinterpret_cast<TenuredCell*>(thing);
|
||||
}
|
||||
|
||||
|
@ -2848,7 +2848,6 @@ js::TenuringTracer::moveToTenured(JSObject* src)
|
||||
insertIntoFixupList(overlay);
|
||||
|
||||
TracePromoteToTenured(src, dst);
|
||||
MemProfiler::MoveNurseryToTenured(src, dst);
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
@ -1,49 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 "jscntxt.h"
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
#include "vm/Runtime.h"
|
||||
|
||||
using js::gc::Cell;
|
||||
|
||||
mozilla::Atomic<uint32_t, mozilla::Relaxed> MemProfiler::sActiveProfilerCount;
|
||||
NativeProfiler* MemProfiler::sNativeProfiler;
|
||||
|
||||
GCHeapProfiler*
|
||||
MemProfiler::GetGCHeapProfiler(void* addr)
|
||||
{
|
||||
JSRuntime* runtime = reinterpret_cast<Cell*>(addr)->runtimeFromAnyThread();
|
||||
return runtime->gc.mMemProfiler.mGCHeapProfiler;
|
||||
}
|
||||
|
||||
GCHeapProfiler*
|
||||
MemProfiler::GetGCHeapProfiler(JSRuntime* runtime)
|
||||
{
|
||||
return runtime->gc.mMemProfiler.mGCHeapProfiler;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(MemProfiler*)
|
||||
MemProfiler::GetMemProfiler(JSContext* context)
|
||||
{
|
||||
return &context->runtime()->gc.mMemProfiler;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(void)
|
||||
MemProfiler::start(GCHeapProfiler* aGCHeapProfiler)
|
||||
{
|
||||
ReleaseAllJITCode(mRuntime->defaultFreeOp());
|
||||
mGCHeapProfiler = aGCHeapProfiler;
|
||||
sActiveProfilerCount++;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(void)
|
||||
MemProfiler::stop()
|
||||
{
|
||||
sActiveProfilerCount--;
|
||||
mGCHeapProfiler = nullptr;
|
||||
}
|
@ -313,7 +313,6 @@ js::Nursery::allocate(size_t size)
|
||||
}
|
||||
#endif
|
||||
|
||||
MemProfiler::SampleNursery(reinterpret_cast<void*>(thing), size);
|
||||
return thing;
|
||||
}
|
||||
|
||||
@ -887,7 +886,6 @@ js::Nursery::sweep()
|
||||
|
||||
/* Set current start position for isEmpty checks. */
|
||||
setStartPosition();
|
||||
MemProfiler::SweepNursery(runtime());
|
||||
}
|
||||
|
||||
size_t
|
||||
|
@ -735,9 +735,8 @@ MacroAssembler::checkUnboxedArrayCapacity(Register obj, const RegisterOrInt32Con
|
||||
void
|
||||
MacroAssembler::checkAllocatorState(Label* fail)
|
||||
{
|
||||
// Don't execute the inline path if we are tracing allocations,
|
||||
// or when the memory profiler is enabled.
|
||||
if (js::gc::TraceEnabled() || MemProfiler::enabled())
|
||||
// Don't execute the inline path if we are tracing allocations.
|
||||
if (js::gc::TraceEnabled())
|
||||
jump(fail);
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
|
@ -3058,162 +3058,4 @@ SystemZoneAvailable(JSContext* cx);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
class NativeProfiler
|
||||
{
|
||||
public:
|
||||
virtual ~NativeProfiler() {};
|
||||
virtual void sampleNative(void* addr, uint32_t size) = 0;
|
||||
virtual void removeNative(void* addr) = 0;
|
||||
virtual void reset() = 0;
|
||||
};
|
||||
|
||||
class GCHeapProfiler
|
||||
{
|
||||
public:
|
||||
virtual ~GCHeapProfiler() {};
|
||||
virtual void sampleTenured(void* addr, uint32_t size) = 0;
|
||||
virtual void sampleNursery(void* addr, uint32_t size) = 0;
|
||||
virtual void markTenuredStart() = 0;
|
||||
virtual void markTenured(void* addr) = 0;
|
||||
virtual void sweepTenured() = 0;
|
||||
virtual void sweepNursery() = 0;
|
||||
virtual void moveNurseryToTenured(void* addrOld, void* addrNew) = 0;
|
||||
virtual void reset() = 0;
|
||||
};
|
||||
|
||||
class MemProfiler
|
||||
{
|
||||
static mozilla::Atomic<uint32_t, mozilla::Relaxed> sActiveProfilerCount;
|
||||
static JS_FRIEND_DATA(NativeProfiler*) sNativeProfiler;
|
||||
|
||||
static GCHeapProfiler* GetGCHeapProfiler(void* addr);
|
||||
static GCHeapProfiler* GetGCHeapProfiler(JSRuntime* runtime);
|
||||
|
||||
static NativeProfiler* GetNativeProfiler() {
|
||||
return sNativeProfiler;
|
||||
}
|
||||
|
||||
GCHeapProfiler* mGCHeapProfiler;
|
||||
JSRuntime* mRuntime;
|
||||
|
||||
public:
|
||||
explicit MemProfiler(JSRuntime* aRuntime) : mGCHeapProfiler(nullptr), mRuntime(aRuntime) {}
|
||||
|
||||
JS_FRIEND_API(void) start(GCHeapProfiler* aGCHeapProfiler);
|
||||
JS_FRIEND_API(void) stop();
|
||||
|
||||
GCHeapProfiler* getGCHeapProfiler() const {
|
||||
return mGCHeapProfiler;
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE bool enabled() {
|
||||
return sActiveProfilerCount > 0;
|
||||
}
|
||||
|
||||
static JS_FRIEND_API(MemProfiler*) GetMemProfiler(JSContext* context);
|
||||
|
||||
static void SetNativeProfiler(NativeProfiler* aProfiler) {
|
||||
sNativeProfiler = aProfiler;
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void SampleNative(void* addr, uint32_t size) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
NativeProfiler* profiler = GetNativeProfiler();
|
||||
if (profiler)
|
||||
profiler->sampleNative(addr, size);
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void SampleTenured(void* addr, uint32_t size) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
GCHeapProfiler* profiler = GetGCHeapProfiler(addr);
|
||||
if (profiler)
|
||||
profiler->sampleTenured(addr, size);
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void SampleNursery(void* addr, uint32_t size) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
GCHeapProfiler* profiler = GetGCHeapProfiler(addr);
|
||||
if (profiler)
|
||||
profiler->sampleNursery(addr, size);
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void RemoveNative(void* addr) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
NativeProfiler* profiler = GetNativeProfiler();
|
||||
if (profiler)
|
||||
profiler->removeNative(addr);
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void MarkTenuredStart(JSRuntime* runtime) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
GCHeapProfiler* profiler = GetGCHeapProfiler(runtime);
|
||||
if (profiler)
|
||||
profiler->markTenuredStart();
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void MarkTenured(void* addr) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
GCHeapProfiler* profiler = GetGCHeapProfiler(addr);
|
||||
if (profiler)
|
||||
profiler->markTenured(addr);
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void SweepTenured(JSRuntime* runtime) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
GCHeapProfiler* profiler = GetGCHeapProfiler(runtime);
|
||||
if (profiler)
|
||||
profiler->sweepTenured();
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void SweepNursery(JSRuntime* runtime) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
GCHeapProfiler* profiler = GetGCHeapProfiler(runtime);
|
||||
if (profiler)
|
||||
profiler->sweepNursery();
|
||||
}
|
||||
|
||||
static MOZ_ALWAYS_INLINE void MoveNurseryToTenured(void* addrOld, void* addrNew) {
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
|
||||
if (MOZ_LIKELY(!enabled()))
|
||||
return;
|
||||
|
||||
GCHeapProfiler* profiler = GetGCHeapProfiler(addrOld);
|
||||
if (profiler)
|
||||
profiler->moveNurseryToTenured(addrOld, addrNew);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* jsfriendapi_h */
|
||||
|
@ -457,14 +457,6 @@ Arena::finalize(FreeOp* fop, AllocKind thingKind, size_t thingSize)
|
||||
FreeSpan* newListTail = &newListHead;
|
||||
size_t nmarked = 0;
|
||||
|
||||
if (MOZ_UNLIKELY(MemProfiler::enabled())) {
|
||||
for (ArenaCellIterUnderFinalize i(this); !i.done(); i.next()) {
|
||||
T* t = i.get<T>();
|
||||
if (t->asTenured().isMarkedAny())
|
||||
MemProfiler::MarkTenured(reinterpret_cast<void*>(t));
|
||||
}
|
||||
}
|
||||
|
||||
for (ArenaCellIterUnderFinalize i(this); !i.done(); i.next()) {
|
||||
T* t = i.get<T>();
|
||||
if (t->asTenured().isMarkedAny()) {
|
||||
@ -832,7 +824,6 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
|
||||
stats_(rt),
|
||||
marker(rt),
|
||||
usage(nullptr),
|
||||
mMemProfiler(rt),
|
||||
nextCellUniqueId_(LargestTaggedNullCellPointer + 1), // Ensure disjoint from null tagged pointers.
|
||||
numArenasFreeCommitted(0),
|
||||
verifyPreData(nullptr),
|
||||
@ -3984,7 +3975,6 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAcces
|
||||
zone->arenas.prepareForIncrementalGC();
|
||||
}
|
||||
|
||||
MemProfiler::MarkTenuredStart(rt);
|
||||
marker.start();
|
||||
GCMarker* gcmarker = ▮
|
||||
|
||||
@ -6261,7 +6251,6 @@ GCRuntime::finishCollection(JS::gcreason::Reason reason)
|
||||
MOZ_ASSERT(marker.isDrained());
|
||||
marker.stop();
|
||||
clearBufferedGrayRoots();
|
||||
MemProfiler::SweepTenured(rt);
|
||||
|
||||
uint64_t currentTime = PRMJ_Now();
|
||||
schedulingState.updateHighFrequencyMode(lastGCTime, currentTime, tunables);
|
||||
|
@ -181,7 +181,6 @@ UNIFIED_SOURCES += [
|
||||
'gc/Iteration.cpp',
|
||||
'gc/Marking.cpp',
|
||||
'gc/Memory.cpp',
|
||||
'gc/MemoryProfiler.cpp',
|
||||
'gc/Nursery.cpp',
|
||||
'gc/RootMarking.cpp',
|
||||
'gc/Statistics.cpp',
|
||||
|
@ -576,7 +576,6 @@ class js::WasmArrayRawBuffer
|
||||
VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta);
|
||||
# endif
|
||||
|
||||
MemProfiler::SampleNative(dataEnd, delta);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -661,7 +660,6 @@ WasmArrayRawBuffer::Allocate(uint32_t numBytes, const Maybe<uint32_t>& maxSize)
|
||||
return nullptr;
|
||||
}
|
||||
# endif // !XP_WIN
|
||||
MemProfiler::SampleNative(data, numBytesWithHeader);
|
||||
|
||||
# if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
|
||||
VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)data + numBytesWithHeader,
|
||||
@ -683,7 +681,6 @@ WasmArrayRawBuffer::Release(void* mem)
|
||||
MOZ_RELEASE_ASSERT(header->mappedSize() <= SIZE_MAX - gc::SystemPageSize());
|
||||
size_t mappedSizeWithHeader = header->mappedSize() + gc::SystemPageSize();
|
||||
|
||||
MemProfiler::RemoveNative(base);
|
||||
# ifdef XP_WIN
|
||||
VirtualFree(base, 0, MEM_RELEASE);
|
||||
# else // XP_WIN
|
||||
@ -833,7 +830,6 @@ ArrayBufferObject::BufferContents
|
||||
ArrayBufferObject::createMappedContents(int fd, size_t offset, size_t length)
|
||||
{
|
||||
void* data = AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
|
||||
MemProfiler::SampleNative(data, length);
|
||||
return BufferContents::create<MAPPED>(data);
|
||||
}
|
||||
|
||||
@ -865,7 +861,6 @@ ArrayBufferObject::releaseData(FreeOp* fop)
|
||||
fop->free_(dataPointer());
|
||||
break;
|
||||
case MAPPED:
|
||||
MemProfiler::RemoveNative(dataPointer());
|
||||
DeallocateMappedContent(dataPointer(), byteLength());
|
||||
break;
|
||||
case WASM:
|
||||
@ -1817,7 +1812,6 @@ JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length)
|
||||
JS_PUBLIC_API(void)
|
||||
JS_ReleaseMappedArrayBufferContents(void* contents, size_t length)
|
||||
{
|
||||
MemProfiler::RemoveNative(contents);
|
||||
DeallocateMappedContent(contents, length);
|
||||
}
|
||||
|
||||
|
@ -172,9 +172,6 @@
|
||||
@BINPATH@/components/layout_xul_tree.xpt
|
||||
@BINPATH@/components/layout_xul.xpt
|
||||
@BINPATH@/components/locale.xpt
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
@BINPATH@/components/memory_profiler.xpt
|
||||
#endif
|
||||
@BINPATH@/components/mimetype.xpt
|
||||
@BINPATH@/components/mozfind.xpt
|
||||
#ifdef ENABLE_INTL_API
|
||||
|
@ -108,7 +108,6 @@ DIRS += [
|
||||
'/tools/code-coverage',
|
||||
'/tools/power',
|
||||
'/tools/profiler',
|
||||
'/tools/memory-profiler',
|
||||
'/xpfe/components',
|
||||
]
|
||||
|
||||
|
@ -1,116 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef memory_profiler_CompactTraceTable_h
|
||||
#define memory_profiler_CompactTraceTable_h
|
||||
|
||||
#include "mozilla/HashFunctions.h"
|
||||
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
struct TrieNode final
|
||||
{
|
||||
uint32_t parentIdx;
|
||||
uint32_t nameIdx;
|
||||
bool operator==(const TrieNode t) const
|
||||
{
|
||||
return parentIdx == t.parentIdx && nameIdx == t.nameIdx;
|
||||
}
|
||||
uint32_t Hash() const
|
||||
{
|
||||
return HashGeneric(parentIdx, nameIdx);
|
||||
}
|
||||
};
|
||||
|
||||
// This class maps a Node of type T to its parent's index in the
|
||||
// map. When serializing, the map is traversed and put into an ordered
|
||||
// array of Nodes.
|
||||
template<typename KeyClass, typename T>
|
||||
class NodeIndexMap final
|
||||
{
|
||||
public:
|
||||
uint32_t Insert(const T& e)
|
||||
{
|
||||
uint32_t index = mMap.Count();
|
||||
if (!mMap.Get(e, &index)) {
|
||||
mMap.Put(e, index);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
nsTArray<T> Serialize() const
|
||||
{
|
||||
nsTArray<T> v;
|
||||
v.SetLength(mMap.Count());
|
||||
for (auto iter = mMap.ConstIter(); !iter.Done(); iter.Next()) {
|
||||
v[iter.Data()] = iter.Key();
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
uint32_t Size() const
|
||||
{
|
||||
return mMap.Count();
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
mMap.Clear();
|
||||
}
|
||||
private:
|
||||
nsDataHashtable<KeyClass, uint32_t> mMap;
|
||||
};
|
||||
|
||||
// Backtraces are stored in a trie to save spaces.
|
||||
// Function names are stored in an unique table and TrieNodes contain indexes
|
||||
// into that table.
|
||||
// The trie is implemented with a hash table; children are stored in
|
||||
// traces[TrieNode{parent node index, branch/function name index}].
|
||||
class CompactTraceTable final
|
||||
{
|
||||
public:
|
||||
CompactTraceTable()
|
||||
{
|
||||
mNames.Insert(nsAutoCString("(unknown)"));
|
||||
mTraces.Insert(TrieNode{0, 0});
|
||||
}
|
||||
|
||||
nsTArray<nsCString> GetNames() const
|
||||
{
|
||||
return mNames.Serialize();
|
||||
}
|
||||
|
||||
nsTArray<TrieNode> GetTraces() const
|
||||
{
|
||||
return mTraces.Serialize();
|
||||
}
|
||||
|
||||
// Returns an ID to a stacktrace.
|
||||
uint32_t Insert(const nsTArray<nsCString>& aRawStacktrace)
|
||||
{
|
||||
uint32_t parent = 0;
|
||||
for (auto& frame: aRawStacktrace) {
|
||||
parent = mTraces.Insert(TrieNode{parent, mNames.Insert(frame)});
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
mNames.Clear();
|
||||
mTraces.Clear();
|
||||
}
|
||||
private:
|
||||
NodeIndexMap<nsCStringHashKey, nsCString> mNames;
|
||||
NodeIndexMap<nsGenericHashKey<TrieNode>, TrieNode> mTraces;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // memory_profiler_CompactTraceTable_h
|
@ -1,168 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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 "GCHeapProfilerImpl.h"
|
||||
|
||||
#include "UncensoredAllocator.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
GCHeapProfilerImpl::GCHeapProfilerImpl()
|
||||
{
|
||||
mLock = PR_NewLock();
|
||||
mMarking = false;
|
||||
}
|
||||
|
||||
GCHeapProfilerImpl::~GCHeapProfilerImpl()
|
||||
{
|
||||
if (mLock) {
|
||||
PR_DestroyLock(mLock);
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<nsCString>
|
||||
GCHeapProfilerImpl::GetNames() const
|
||||
{
|
||||
return mTraceTable.GetNames();
|
||||
}
|
||||
|
||||
nsTArray<TrieNode>
|
||||
GCHeapProfilerImpl::GetTraces() const
|
||||
{
|
||||
return mTraceTable.GetTraces();
|
||||
}
|
||||
|
||||
const nsTArray<AllocEvent>&
|
||||
GCHeapProfilerImpl::GetEvents() const
|
||||
{
|
||||
return mAllocEvents;
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::reset()
|
||||
{
|
||||
mTraceTable.Reset();
|
||||
mAllocEvents.Clear();
|
||||
mNurseryEntries.Clear();
|
||||
mTenuredEntriesFG.Clear();
|
||||
mTenuredEntriesBG.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::sampleTenured(void* addr, uint32_t size)
|
||||
{
|
||||
SampleInternal(addr, size, mTenuredEntriesFG);
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::sampleNursery(void* addr, uint32_t size)
|
||||
{
|
||||
SampleInternal(addr, size, mNurseryEntries);
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::markTenuredStart()
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
if (!mMarking) {
|
||||
mMarking = true;
|
||||
mTenuredEntriesFG.SwapElements(mTenuredEntriesBG);
|
||||
MOZ_ASSERT(mTenuredEntriesFG.Count() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::markTenured(void* addr)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
if (mMarking) {
|
||||
AllocEntry entry;
|
||||
if (mTenuredEntriesBG.Get(addr, &entry)) {
|
||||
entry.mMarked = true;
|
||||
mTenuredEntriesBG.Put(addr, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::sweepTenured()
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
if (mMarking) {
|
||||
mMarking = false;
|
||||
for (auto iter = mTenuredEntriesBG.Iter(); !iter.Done(); iter.Next()) {
|
||||
if (iter.Data().mMarked) {
|
||||
iter.Data().mMarked = false;
|
||||
mTenuredEntriesFG.Put(iter.Key(), iter.Data());
|
||||
} else {
|
||||
AllocEvent& oldEvent = mAllocEvents[iter.Data().mEventIdx];
|
||||
AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
|
||||
mAllocEvents.AppendElement(newEvent);
|
||||
}
|
||||
}
|
||||
mTenuredEntriesBG.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::sweepNursery()
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
for (auto iter = mNurseryEntries.Iter(); !iter.Done(); iter.Next()) {
|
||||
AllocEvent& oldEvent = mAllocEvents[iter.Data().mEventIdx];
|
||||
AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
|
||||
mAllocEvents.AppendElement(newEvent);
|
||||
}
|
||||
mNurseryEntries.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::moveNurseryToTenured(void* addrOld, void* addrNew)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
AllocEntry entryOld;
|
||||
if (!mNurseryEntries.Get(addrOld, &entryOld)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Because the tenured heap is sampled, the address might already be there.
|
||||
// If not, the address is inserted with the old event.
|
||||
AllocEntry tenuredEntryOld;
|
||||
if (!mTenuredEntriesFG.Get(addrNew, &tenuredEntryOld)) {
|
||||
mTenuredEntriesFG.Put(addrNew, AllocEntry(entryOld.mEventIdx));
|
||||
} else {
|
||||
// If it is already inserted, the insertion above will fail and the
|
||||
// iterator of the already-inserted element is returned.
|
||||
// We choose to ignore the the new event by setting its size zero and point
|
||||
// the newly allocated address to the old event.
|
||||
// An event of size zero will be skipped when reporting.
|
||||
mAllocEvents[entryOld.mEventIdx].mSize = 0;
|
||||
tenuredEntryOld.mEventIdx = entryOld.mEventIdx;
|
||||
mTenuredEntriesFG.Put(addrNew, tenuredEntryOld);
|
||||
}
|
||||
mNurseryEntries.Remove(addrOld);
|
||||
}
|
||||
|
||||
void
|
||||
GCHeapProfilerImpl::SampleInternal(void* aAddr, uint32_t aSize, AllocMap& aTable)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
size_t nSamples = AddBytesSampled(aSize);
|
||||
if (nSamples > 0) {
|
||||
nsTArray<nsCString> trace = GetStacktrace();
|
||||
AllocEvent ai(mTraceTable.Insert(trace), nSamples * mSampleSize, TimeStamp::Now());
|
||||
aTable.Put(aAddr, AllocEntry(mAllocEvents.Length()));
|
||||
mAllocEvents.AppendElement(ai);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,53 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef memory_profiler_GCHeapProfilerImpl_h
|
||||
#define memory_profiler_GCHeapProfilerImpl_h
|
||||
|
||||
#include "CompactTraceTable.h"
|
||||
#include "MemoryProfiler.h"
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class GCHeapProfilerImpl final : public GCHeapProfiler
|
||||
, public ProfilerImpl
|
||||
{
|
||||
public:
|
||||
GCHeapProfilerImpl();
|
||||
~GCHeapProfilerImpl() override;
|
||||
|
||||
nsTArray<nsCString> GetNames() const override;
|
||||
nsTArray<TrieNode> GetTraces() const override;
|
||||
const nsTArray<AllocEvent>& GetEvents() const override;
|
||||
|
||||
void reset() override;
|
||||
void sampleTenured(void* addr, uint32_t size) override;
|
||||
void sampleNursery(void* addr, uint32_t size) override;
|
||||
void markTenuredStart() override;
|
||||
void markTenured(void* addr) override;
|
||||
void sweepTenured() override;
|
||||
void sweepNursery() override;
|
||||
void moveNurseryToTenured(void* addrOld, void* addrNew) override;
|
||||
|
||||
private:
|
||||
void SampleInternal(void* addr, uint32_t size, AllocMap& table);
|
||||
|
||||
PRLock* mLock;
|
||||
bool mMarking;
|
||||
|
||||
AllocMap mNurseryEntries;
|
||||
AllocMap mTenuredEntriesFG;
|
||||
AllocMap mTenuredEntriesBG;
|
||||
|
||||
nsTArray<AllocEvent> mAllocEvents;
|
||||
CompactTraceTable mTraceTable;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // memory_profiler_GCHeapProfilerImpl_h
|
@ -1,323 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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 "MemoryProfiler.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include "GCHeapProfilerImpl.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "NativeProfilerImpl.h"
|
||||
#include "UncensoredAllocator.h"
|
||||
#include "js/TypeDecls.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "nsIDOMClassInfo.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "prtime.h"
|
||||
#include "xpcprivate.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#define MEMORY_PROFILER_SAMPLE_SIZE 65536
|
||||
#define BACKTRACE_BUFFER_SIZE 16384
|
||||
|
||||
ProfilerImpl::ProfilerImpl()
|
||||
: mSampleSize(MEMORY_PROFILER_SAMPLE_SIZE)
|
||||
{
|
||||
mLog1minusP = std::log(1.0 - 1.0 / mSampleSize);
|
||||
mRemainingBytes = std::floor(std::log(1.0 - DRandom()) / mLog1minusP);
|
||||
}
|
||||
|
||||
nsTArray<nsCString>
|
||||
ProfilerImpl::GetStacktrace()
|
||||
{
|
||||
nsTArray<nsCString> trace;
|
||||
auto output = MakeUnique<char[]>(BACKTRACE_BUFFER_SIZE);
|
||||
|
||||
profiler_get_backtrace_noalloc(output.get(), BACKTRACE_BUFFER_SIZE);
|
||||
for (const char* p = output.get(); *p; p += strlen(p) + 1) {
|
||||
trace.AppendElement()->Assign(p);
|
||||
}
|
||||
|
||||
return trace;
|
||||
}
|
||||
|
||||
// Generate a random number in [0, 1).
|
||||
double
|
||||
ProfilerImpl::DRandom()
|
||||
{
|
||||
return double(((uint64_t(std::rand()) & ((1 << 26) - 1)) << 27) +
|
||||
(uint64_t(std::rand()) & ((1 << 27) - 1)))
|
||||
/ (uint64_t(1) << 53);
|
||||
}
|
||||
|
||||
size_t
|
||||
ProfilerImpl::AddBytesSampled(uint32_t aBytes)
|
||||
{
|
||||
size_t nSamples = 0;
|
||||
while (mRemainingBytes <= aBytes) {
|
||||
mRemainingBytes += std::floor(std::log(1.0 - DRandom()) / mLog1minusP);
|
||||
nSamples++;
|
||||
}
|
||||
mRemainingBytes -= aBytes;
|
||||
return nSamples;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS(MemoryProfiler, nsIMemoryProfiler)
|
||||
|
||||
PRLock* MemoryProfiler::sLock;
|
||||
uint32_t MemoryProfiler::sProfileContextCount;
|
||||
StaticAutoPtr<NativeProfilerImpl> MemoryProfiler::sNativeProfiler;
|
||||
StaticAutoPtr<JSContextProfilerMap> MemoryProfiler::sJSContextProfilerMap;
|
||||
TimeStamp MemoryProfiler::sStartTime;
|
||||
|
||||
void
|
||||
MemoryProfiler::InitOnce()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized) {
|
||||
MallocHook::Initialize();
|
||||
sLock = PR_NewLock();
|
||||
sProfileContextCount = 0;
|
||||
sJSContextProfilerMap = new JSContextProfilerMap();
|
||||
ClearOnShutdown(&sJSContextProfilerMap);
|
||||
ClearOnShutdown(&sNativeProfiler);
|
||||
std::srand(PR_Now());
|
||||
sStartTime = TimeStamp::ProcessCreation();
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MemoryProfiler::StartProfiler()
|
||||
{
|
||||
InitOnce();
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(sLock);
|
||||
JSContext* context = XPCJSContext::Get()->Context();
|
||||
ProfilerForJSContext profiler;
|
||||
if (!sJSContextProfilerMap->Get(context, &profiler) ||
|
||||
!profiler.mEnabled) {
|
||||
if (sProfileContextCount == 0) {
|
||||
js::EnableContextProfilingStack(context, true);
|
||||
if (!sNativeProfiler) {
|
||||
sNativeProfiler = new NativeProfilerImpl();
|
||||
}
|
||||
MemProfiler::SetNativeProfiler(sNativeProfiler);
|
||||
}
|
||||
GCHeapProfilerImpl* gp = new GCHeapProfilerImpl();
|
||||
profiler.mEnabled = true;
|
||||
profiler.mProfiler = gp;
|
||||
sJSContextProfilerMap->Put(context, profiler);
|
||||
MemProfiler::GetMemProfiler(context)->start(gp);
|
||||
if (sProfileContextCount == 0) {
|
||||
MallocHook::Enable(sNativeProfiler);
|
||||
}
|
||||
sProfileContextCount++;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MemoryProfiler::StopProfiler()
|
||||
{
|
||||
InitOnce();
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(sLock);
|
||||
JSContext* context = XPCJSContext::Get()->Context();
|
||||
ProfilerForJSContext profiler;
|
||||
if (sJSContextProfilerMap->Get(context, &profiler) &&
|
||||
profiler.mEnabled) {
|
||||
MemProfiler::GetMemProfiler(context)->stop();
|
||||
if (--sProfileContextCount == 0) {
|
||||
MallocHook::Disable();
|
||||
MemProfiler::SetNativeProfiler(nullptr);
|
||||
js::EnableContextProfilingStack(context, false);
|
||||
}
|
||||
profiler.mEnabled = false;
|
||||
sJSContextProfilerMap->Put(context, profiler);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MemoryProfiler::ResetProfiler()
|
||||
{
|
||||
InitOnce();
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(sLock);
|
||||
JSContext* context = XPCJSContext::Get()->Context();
|
||||
ProfilerForJSContext profiler;
|
||||
if (!sJSContextProfilerMap->Get(context, &profiler) ||
|
||||
!profiler.mEnabled) {
|
||||
delete profiler.mProfiler;
|
||||
profiler.mProfiler = nullptr;
|
||||
sJSContextProfilerMap->Put(context, profiler);
|
||||
}
|
||||
if (sProfileContextCount == 0) {
|
||||
sNativeProfiler = nullptr;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
struct MergedTraces
|
||||
{
|
||||
nsTArray<nsCString> mNames;
|
||||
nsTArray<TrieNode> mTraces;
|
||||
nsTArray<AllocEvent> mEvents;
|
||||
};
|
||||
|
||||
// Merge events and corresponding traces and names.
|
||||
static MergedTraces
|
||||
MergeResults(const nsTArray<nsCString>& names0,
|
||||
const nsTArray<TrieNode>& traces0,
|
||||
const nsTArray<AllocEvent>& events0,
|
||||
const nsTArray<nsCString>& names1,
|
||||
const nsTArray<TrieNode>& traces1,
|
||||
const nsTArray<AllocEvent>& events1)
|
||||
{
|
||||
NodeIndexMap<nsCStringHashKey, nsCString> names;
|
||||
NodeIndexMap<nsGenericHashKey<TrieNode>, TrieNode> traces;
|
||||
nsTArray<AllocEvent> events;
|
||||
|
||||
nsTArray<size_t> names1Tonames0(names1.Length());
|
||||
nsTArray<size_t> traces1Totraces0(traces1.Length());
|
||||
|
||||
// Merge names.
|
||||
for (auto& i: names0) {
|
||||
names.Insert(i);
|
||||
}
|
||||
for (auto& i: names1) {
|
||||
names1Tonames0.AppendElement(names.Insert(i));
|
||||
}
|
||||
|
||||
// Merge traces. Note that traces1[i].parentIdx < i for all i > 0.
|
||||
for (auto& i: traces0) {
|
||||
traces.Insert(i);
|
||||
}
|
||||
traces1Totraces0.AppendElement(0);
|
||||
for (size_t i = 1; i < traces1.Length(); i++) {
|
||||
TrieNode node = traces1[i];
|
||||
node.parentIdx = traces1Totraces0[node.parentIdx];
|
||||
node.nameIdx = names1Tonames0[node.nameIdx];
|
||||
traces1Totraces0.AppendElement(traces.Insert(node));
|
||||
}
|
||||
|
||||
// Merge the events according to timestamps.
|
||||
auto p0 = events0.begin();
|
||||
auto p1 = events1.begin();
|
||||
|
||||
while (p0 != events0.end() && p1 != events1.end()) {
|
||||
if (p0->mTimestamp < p1->mTimestamp) {
|
||||
events.AppendElement(*p0++);
|
||||
} else {
|
||||
events.AppendElement(*p1++);
|
||||
events.LastElement().mTraceIdx =
|
||||
traces1Totraces0[events.LastElement().mTraceIdx];
|
||||
}
|
||||
}
|
||||
|
||||
while (p0 != events0.end()) {
|
||||
events.AppendElement(*p0++);
|
||||
}
|
||||
|
||||
while (p1 != events1.end()) {
|
||||
events.AppendElement(*p1++);
|
||||
events.LastElement().mTraceIdx =
|
||||
traces1Totraces0[events.LastElement().mTraceIdx];
|
||||
}
|
||||
|
||||
return MergedTraces{names.Serialize(), traces.Serialize(), Move(events)};
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
MemoryProfiler::GetResults(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
|
||||
{
|
||||
InitOnce();
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(sLock);
|
||||
JSContext* context = XPCJSContext::Get()->Context();
|
||||
// Getting results when the profiler is running is not allowed.
|
||||
if (sProfileContextCount > 0) {
|
||||
return NS_OK;
|
||||
}
|
||||
// Return immediately when native profiler does not exist.
|
||||
if (!sNativeProfiler) {
|
||||
return NS_OK;
|
||||
}
|
||||
// Return immediately when there's no result in current context.
|
||||
ProfilerForJSContext profiler;
|
||||
if (!sJSContextProfilerMap->Get(context, &profiler) ||
|
||||
!profiler.mProfiler) {
|
||||
return NS_OK;
|
||||
}
|
||||
GCHeapProfilerImpl* gp = profiler.mProfiler;
|
||||
|
||||
auto results = MergeResults(gp->GetNames(), gp->GetTraces(), gp->GetEvents(),
|
||||
sNativeProfiler->GetNames(),
|
||||
sNativeProfiler->GetTraces(),
|
||||
sNativeProfiler->GetEvents());
|
||||
const nsTArray<nsCString>& names = results.mNames;
|
||||
const nsTArray<TrieNode>& traces = results.mTraces;
|
||||
const nsTArray<AllocEvent>& events = results.mEvents;
|
||||
|
||||
JS::RootedObject jsnames(cx, JS_NewArrayObject(cx, names.Length()));
|
||||
JS::RootedObject jstraces(cx, JS_NewArrayObject(cx, traces.Length()));
|
||||
JS::RootedObject jsevents(cx, JS_NewArrayObject(cx, events.Length()));
|
||||
|
||||
for (size_t i = 0; i < names.Length(); i++) {
|
||||
JS::RootedString name(cx, JS_NewStringCopyZ(cx, names[i].get()));
|
||||
JS_SetElement(cx, jsnames, i, name);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < traces.Length(); i++) {
|
||||
JS::RootedObject tn(cx, JS_NewPlainObject(cx));
|
||||
JS::RootedValue nameIdx(cx, JS_NumberValue(traces[i].nameIdx));
|
||||
JS::RootedValue parentIdx(cx, JS_NumberValue(traces[i].parentIdx));
|
||||
JS_SetProperty(cx, tn, "nameIdx", nameIdx);
|
||||
JS_SetProperty(cx, tn, "parentIdx", parentIdx);
|
||||
JS_SetElement(cx, jstraces, i, tn);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (auto ent: events) {
|
||||
if (ent.mSize == 0) {
|
||||
continue;
|
||||
}
|
||||
MOZ_ASSERT(!sStartTime.IsNull());
|
||||
double time = (ent.mTimestamp - sStartTime).ToMilliseconds();
|
||||
JS::RootedObject tn(cx, JS_NewPlainObject(cx));
|
||||
JS::RootedValue size(cx, JS_NumberValue(ent.mSize));
|
||||
JS::RootedValue traceIdx(cx, JS_NumberValue(ent.mTraceIdx));
|
||||
JS::RootedValue timestamp(cx, JS_NumberValue(time));
|
||||
JS_SetProperty(cx, tn, "size", size);
|
||||
JS_SetProperty(cx, tn, "traceIdx", traceIdx);
|
||||
JS_SetProperty(cx, tn, "timestamp", timestamp);
|
||||
JS_SetElement(cx, jsevents, i++, tn);
|
||||
}
|
||||
JS_SetArrayLength(cx, jsevents, i);
|
||||
|
||||
JS::RootedObject result(cx, JS_NewPlainObject(cx));
|
||||
JS::RootedValue objnames(cx, ObjectOrNullValue(jsnames));
|
||||
JS_SetProperty(cx, result, "names", objnames);
|
||||
JS::RootedValue objtraces(cx, ObjectOrNullValue(jstraces));
|
||||
JS_SetProperty(cx, result, "traces", objtraces);
|
||||
JS::RootedValue objevents(cx, ObjectOrNullValue(jsevents));
|
||||
JS_SetProperty(cx, result, "events", objevents);
|
||||
aResult.setObject(*result);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,159 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef tools_profiler_MemoryProfiler_h
|
||||
#define tools_profiler_MemoryProfiler_h
|
||||
|
||||
#include "nsIMemoryProfiler.h"
|
||||
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
|
||||
#include "CompactTraceTable.h"
|
||||
#include "nsTArray.h"
|
||||
#include "prlock.h"
|
||||
|
||||
#define MEMORY_PROFILER_CID \
|
||||
{ 0xf976eaa2, 0xcc1f, 0x47ee, \
|
||||
{ 0x81, 0x29, 0xb8, 0x26, 0x2a, 0x3d, 0xb6, 0xb2 } }
|
||||
|
||||
#define MEMORY_PROFILER_CONTRACT_ID "@mozilla.org/tools/memory-profiler;1"
|
||||
|
||||
struct PRLock;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class NativeProfilerImpl;
|
||||
class GCHeapProfilerImpl;
|
||||
|
||||
struct ProfilerForJSContext
|
||||
{
|
||||
ProfilerForJSContext()
|
||||
: mProfiler(nullptr)
|
||||
, mEnabled(false)
|
||||
{}
|
||||
GCHeapProfilerImpl* mProfiler;
|
||||
bool mEnabled;
|
||||
};
|
||||
using JSContextProfilerMap =
|
||||
nsDataHashtable<nsClearingPtrHashKey<JSContext>, ProfilerForJSContext>;
|
||||
|
||||
class MemoryProfiler final : public nsIMemoryProfiler
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIMEMORYPROFILER
|
||||
|
||||
private:
|
||||
static void InitOnce();
|
||||
~MemoryProfiler() {}
|
||||
|
||||
// The accesses to other static member are guarded by sLock and
|
||||
// sProfileContextCount.
|
||||
static PRLock* sLock;
|
||||
static uint32_t sProfileContextCount;
|
||||
|
||||
static StaticAutoPtr<NativeProfilerImpl> sNativeProfiler;
|
||||
static StaticAutoPtr<JSContextProfilerMap> sJSContextProfilerMap;
|
||||
static TimeStamp sStartTime;
|
||||
};
|
||||
|
||||
// Allocation events to be reported.
|
||||
struct AllocEvent {
|
||||
TimeStamp mTimestamp;
|
||||
// index to a stacktrace singleton.
|
||||
uint32_t mTraceIdx;
|
||||
// Allocation size
|
||||
int32_t mSize;
|
||||
|
||||
AllocEvent(uint32_t aTraceIdx, int32_t aSize, TimeStamp aTimestamp)
|
||||
: mTimestamp(aTimestamp)
|
||||
, mTraceIdx(aTraceIdx)
|
||||
, mSize(aSize)
|
||||
{}
|
||||
};
|
||||
|
||||
// Index to allocation events but also a mark bit to be GC-able.
|
||||
struct AllocEntry {
|
||||
uint32_t mEventIdx : 31;
|
||||
bool mMarked : 1;
|
||||
|
||||
// Default constructor for uninitialized stack value required by
|
||||
// getter methods.
|
||||
AllocEntry()
|
||||
: mEventIdx(0)
|
||||
, mMarked(false)
|
||||
{}
|
||||
explicit AllocEntry(int aEventIdx)
|
||||
: mEventIdx(aEventIdx)
|
||||
, mMarked(false)
|
||||
{}
|
||||
};
|
||||
|
||||
using AllocMap = nsDataHashtable<nsClearingVoidPtrHashKey, AllocEntry>;
|
||||
|
||||
class ProfilerImpl
|
||||
{
|
||||
public:
|
||||
static nsTArray<nsCString> GetStacktrace();
|
||||
static double DRandom();
|
||||
|
||||
ProfilerImpl();
|
||||
virtual nsTArray<nsCString> GetNames() const = 0;
|
||||
virtual nsTArray<TrieNode> GetTraces() const = 0;
|
||||
virtual const nsTArray<AllocEvent>& GetEvents() const = 0;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* The sampler generates a random variable which conforms to a geometric
|
||||
* distribution of probability p = 1 / mSampleSize to calculate the
|
||||
* next-to-be-sampled byte directly; It avoids rolling a dice on each byte.
|
||||
*
|
||||
* Let Bn denote a Bernoulli process with first success on n-th trial, the
|
||||
* cumulative distribution function of Bn is Cn = 1 - (1 - p) ^ n.
|
||||
* Let U denote a uniformly distributed random variable in [0, 1).
|
||||
* A geometric random variable can be generated by Cn's reverse function:
|
||||
* G = floor(log(1 - U) / log(1 - p)).
|
||||
*
|
||||
* @param aSize the number of bytes seen
|
||||
* @return the number of events sampled
|
||||
*/
|
||||
size_t AddBytesSampled(uint32_t aBytes);
|
||||
|
||||
uint32_t mSampleSize;
|
||||
|
||||
private:
|
||||
uint32_t mRemainingBytes;
|
||||
double mLog1minusP;
|
||||
};
|
||||
|
||||
/*
|
||||
* This class is used to make sure the profile data is only accessed
|
||||
* on one thread at a time. Don't use mozilla::Mutex because we don't
|
||||
* want to allocate memory.
|
||||
*/
|
||||
class AutoMPLock
|
||||
{
|
||||
public:
|
||||
explicit AutoMPLock(PRLock* aLock)
|
||||
{
|
||||
MOZ_ASSERT(aLock);
|
||||
mLock = aLock;
|
||||
PR_Lock(mLock);
|
||||
}
|
||||
|
||||
~AutoMPLock()
|
||||
{
|
||||
PR_Unlock(mLock);
|
||||
}
|
||||
|
||||
private:
|
||||
PRLock* mLock;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
@ -1,82 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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 "NativeProfilerImpl.h"
|
||||
|
||||
#include "UncensoredAllocator.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NativeProfilerImpl::NativeProfilerImpl()
|
||||
{
|
||||
mLock = PR_NewLock();
|
||||
}
|
||||
|
||||
NativeProfilerImpl::~NativeProfilerImpl()
|
||||
{
|
||||
if (mLock) {
|
||||
PR_DestroyLock(mLock);
|
||||
}
|
||||
}
|
||||
|
||||
nsTArray<nsCString>
|
||||
NativeProfilerImpl::GetNames() const
|
||||
{
|
||||
return mTraceTable.GetNames();
|
||||
}
|
||||
|
||||
nsTArray<TrieNode>
|
||||
NativeProfilerImpl::GetTraces() const
|
||||
{
|
||||
return mTraceTable.GetTraces();
|
||||
}
|
||||
|
||||
const nsTArray<AllocEvent>&
|
||||
NativeProfilerImpl::GetEvents() const
|
||||
{
|
||||
return mAllocEvents;
|
||||
}
|
||||
|
||||
void
|
||||
NativeProfilerImpl::reset()
|
||||
{
|
||||
mTraceTable.Reset();
|
||||
mAllocEvents.Clear();
|
||||
mNativeEntries.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
NativeProfilerImpl::sampleNative(void* addr, uint32_t size)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
size_t nSamples = AddBytesSampled(size);
|
||||
if (nSamples > 0) {
|
||||
nsTArray<nsCString> trace = GetStacktrace();
|
||||
AllocEvent ai(mTraceTable.Insert(trace), nSamples * mSampleSize, TimeStamp::Now());
|
||||
mNativeEntries.Put(addr, AllocEntry(mAllocEvents.Length()));
|
||||
mAllocEvents.AppendElement(ai);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
NativeProfilerImpl::removeNative(void* addr)
|
||||
{
|
||||
AutoUseUncensoredAllocator ua;
|
||||
AutoMPLock lock(mLock);
|
||||
|
||||
AllocEntry entry;
|
||||
if (!mNativeEntries.Get(addr, &entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AllocEvent& oldEvent = mAllocEvents[entry.mEventIdx];
|
||||
AllocEvent newEvent(oldEvent.mTraceIdx, -oldEvent.mSize, TimeStamp::Now());
|
||||
mAllocEvents.AppendElement(newEvent);
|
||||
mNativeEntries.Remove(addr);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,43 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef memory_profiler_NativeProfilerImpl_h
|
||||
#define memory_profiler_NativeProfilerImpl_h
|
||||
|
||||
#include "CompactTraceTable.h"
|
||||
#include "MemoryProfiler.h"
|
||||
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
struct PRLock;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class NativeProfilerImpl final : public NativeProfiler
|
||||
, public ProfilerImpl
|
||||
{
|
||||
public:
|
||||
NativeProfilerImpl();
|
||||
~NativeProfilerImpl() override;
|
||||
|
||||
nsTArray<nsCString> GetNames() const override;
|
||||
nsTArray<TrieNode> GetTraces() const override;
|
||||
const nsTArray<AllocEvent>& GetEvents() const override;
|
||||
|
||||
void reset() override;
|
||||
void sampleNative(void* addr, uint32_t size) override;
|
||||
void removeNative(void* addr) override;
|
||||
|
||||
private:
|
||||
PRLock* mLock;
|
||||
AllocMap mNativeEntries;
|
||||
nsTArray<AllocEvent> mAllocEvents;
|
||||
CompactTraceTable mTraceTable;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // memory_profiler_NativeProfilerImpl_h
|
@ -1,121 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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 "UncensoredAllocator.h"
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
#include "MainThreadUtils.h"
|
||||
#include "jsfriendapi.h"
|
||||
#include "nsDebug.h"
|
||||
#include "prlock.h"
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
#include "replace_malloc_bridge.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MOZ_THREAD_LOCAL(bool) MallocHook::mEnabledTLS;
|
||||
NativeProfiler* MallocHook::mNativeProfiler;
|
||||
malloc_hook_table_t MallocHook::mMallocHook;
|
||||
#endif
|
||||
|
||||
AutoUseUncensoredAllocator::AutoUseUncensoredAllocator()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MallocHook::mEnabledTLS.set(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
AutoUseUncensoredAllocator::~AutoUseUncensoredAllocator()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MallocHook::mEnabledTLS.set(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
MallocHook::Enabled()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
return mEnabledTLS.get() && mNativeProfiler;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void*
|
||||
MallocHook::SampleNative(void* aAddr, size_t aSize)
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
if (MallocHook::Enabled()) {
|
||||
mNativeProfiler->sampleNative(aAddr, aSize);
|
||||
}
|
||||
#endif
|
||||
return aAddr;
|
||||
}
|
||||
|
||||
void
|
||||
MallocHook::RemoveNative(void* aAddr)
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
if (MallocHook::Enabled()) {
|
||||
mNativeProfiler->removeNative(aAddr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
MallocHook::Initialize()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mMallocHook.free_hook = RemoveNative;
|
||||
mMallocHook.malloc_hook = SampleNative;
|
||||
ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
|
||||
if (bridge) {
|
||||
mozilla::Unused << bridge->RegisterHook("memory-profiler", nullptr, nullptr);
|
||||
}
|
||||
|
||||
bool success = mEnabledTLS.init();
|
||||
if (NS_WARN_IF(!success)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
MallocHook::Enable(NativeProfiler* aNativeProfiler)
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
|
||||
if (bridge) {
|
||||
const malloc_table_t* alloc_funcs =
|
||||
bridge->RegisterHook("memory-profiler", nullptr, &mMallocHook);
|
||||
if (alloc_funcs) {
|
||||
mNativeProfiler = aNativeProfiler;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
MallocHook::Disable()
|
||||
{
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ReplaceMallocBridge* bridge = ReplaceMallocBridge::Get(3);
|
||||
if (bridge) {
|
||||
bridge->RegisterHook("memory-profiler", nullptr, nullptr);
|
||||
mNativeProfiler = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
@ -1,48 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef memory_profiler_UncensoredAllocator_h
|
||||
#define memory_profiler_UncensoredAllocator_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
#include "replace_malloc_bridge.h"
|
||||
#endif
|
||||
|
||||
class NativeProfiler;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class MallocHook final
|
||||
{
|
||||
public:
|
||||
static void Initialize();
|
||||
static void Enable(NativeProfiler* aNativeProfiler);
|
||||
static void Disable();
|
||||
static bool Enabled();
|
||||
private:
|
||||
static void* SampleNative(void* aAddr, size_t aSize);
|
||||
static void RemoveNative(void* aAddr);
|
||||
#ifdef MOZ_REPLACE_MALLOC
|
||||
static MOZ_THREAD_LOCAL(bool) mEnabledTLS;
|
||||
static NativeProfiler* mNativeProfiler;
|
||||
static malloc_hook_table_t mMallocHook;
|
||||
#endif
|
||||
friend class AutoUseUncensoredAllocator;
|
||||
};
|
||||
|
||||
class MOZ_RAII AutoUseUncensoredAllocator final
|
||||
{
|
||||
public:
|
||||
AutoUseUncensoredAllocator();
|
||||
~AutoUseUncensoredAllocator();
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // memory_profiler_UncensoredAllocator_h
|
@ -1,29 +0,0 @@
|
||||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
if CONFIG['MOZ_GECKO_PROFILER']:
|
||||
XPIDL_MODULE = 'memory_profiler'
|
||||
XPIDL_SOURCES += [
|
||||
'nsIMemoryProfiler.idl',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
'GCHeapProfilerImpl.cpp',
|
||||
'MemoryProfiler.cpp',
|
||||
'NativeProfilerImpl.cpp',
|
||||
'nsMemoryProfilerFactory.cpp',
|
||||
'UncensoredAllocator.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/js/xpconnect/src',
|
||||
'/xpcom/base',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
if CONFIG['GNU_CXX']:
|
||||
CXXFLAGS += ['-Wno-error=shadow']
|
@ -1,72 +0,0 @@
|
||||
/* -*- Mode: IDL; 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 "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* The memory profiler samples allocation events. An allocation event
|
||||
* includes a type (what and at where is going to be allocated), a
|
||||
* size, a timestamp and the corresponding stack trace. Free events
|
||||
* are also tracked. For managed languages, namely languages relying
|
||||
* on garbage collection, a free event is generated when an object is
|
||||
* reclaimed by the garbage collector. These sampled events can be
|
||||
* used to approximate the full history of allocations afterwards.
|
||||
* That means we can get various memory profiles of a program in
|
||||
* different perspectives by post-processing the history in different
|
||||
* ways. The profiler is designed at the very beginning to support not
|
||||
* only JavaScript but also native codes. Naturally, not only
|
||||
* JavaScript objects but also native allocations are tracked.
|
||||
*
|
||||
* The result returned is the sampled allocation event traces in a
|
||||
* compact format. The events is sorted according to the timestamp
|
||||
* when the event happened. Each event has a trace index pointing to
|
||||
* the traces table. Each trace entry has a name index pointing to the
|
||||
* names table and a parent index pointing to the parent trace in the
|
||||
* traces table. By following the trace index one could rebuild the
|
||||
* complete backtrace of an allocation event.
|
||||
*
|
||||
* [ Events ]
|
||||
* +-------+-------+ +-------+
|
||||
* | Size | Size | | Size |
|
||||
* |-------|-------| |-------|
|
||||
* | Time | Time |......| Time |
|
||||
* |-------|-------| |-------|
|
||||
* +-- Trace | Trace | | Trace |
|
||||
* | +-------+-------+ +-------+
|
||||
* |
|
||||
* | [ Traces ]
|
||||
* +->--------+--------+ +--------+ +--------+
|
||||
* -| Name | Name | | Name | | Name |
|
||||
* / |--------|--------|...|--------|...|--------|
|
||||
* | | Parent | Parent | | Parent | | Parent |
|
||||
* | +---|----+----^--++ +--^--|--+ +---^----+
|
||||
* | | | | | | |
|
||||
* | +---------+ +-------+ +----------+
|
||||
* | [ Names ]
|
||||
* | +-----------------+-----------------+
|
||||
* +-> Function name | Function name |
|
||||
* | & line numbers | & line numbers |......
|
||||
* +-----------------+-----------------+
|
||||
*
|
||||
*/
|
||||
[scriptable, uuid(1e10e7a9-bc05-4878-a687-36c9ea4428b1)]
|
||||
interface nsIMemoryProfiler : nsISupports
|
||||
{
|
||||
void startProfiler();
|
||||
void stopProfiler();
|
||||
void resetProfiler();
|
||||
|
||||
/**
|
||||
* Get results in an object which contains three tables:
|
||||
* {
|
||||
* names, // an array of function names and positions
|
||||
* traces, // an array of {nameIdx, parentIdx}
|
||||
* events, // an array of {size, timestamp, traceIdx}
|
||||
* }
|
||||
* Should only be called after stopProfiler.
|
||||
*/
|
||||
[implicit_jscontext]
|
||||
jsval getResults();
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 20; 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 "mozilla/ModuleUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "MemoryProfiler.h"
|
||||
|
||||
using mozilla::MemoryProfiler;
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(MemoryProfiler)
|
||||
|
||||
NS_DEFINE_NAMED_CID(MEMORY_PROFILER_CID);
|
||||
|
||||
static const mozilla::Module::CIDEntry kMemoryProfilerCIDs[] = {
|
||||
{ &kMEMORY_PROFILER_CID, false, nullptr, MemoryProfilerConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kMemoryProfilerContracts[] = {
|
||||
{ MEMORY_PROFILER_CONTRACT_ID, &kMEMORY_PROFILER_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module kMemoryProfilerModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kMemoryProfilerCIDs,
|
||||
kMemoryProfilerContracts
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(nsMemoryProfilerModule) = &kMemoryProfilerModule;
|
@ -25,9 +25,6 @@ with Files("docs/**"):
|
||||
with Files("lint/**"):
|
||||
BUG_COMPONENT = ("Testing", "Lint")
|
||||
|
||||
with Files("memory-profiler/**"):
|
||||
BUG_COMPONENT = ("Core", "Gecko Profiler")
|
||||
|
||||
with Files("mercurial/**"):
|
||||
BUG_COMPONENT = ("Core", "Build Config")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user