Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=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 file,
5 : * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "MemoryProfiler.h"
8 :
9 : #include <cmath>
10 : #include <cstdlib>
11 :
12 : #include "mozilla/ClearOnShutdown.h"
13 : #include "mozilla/Move.h"
14 : #include "mozilla/TimeStamp.h"
15 : #include "mozilla/UniquePtr.h"
16 :
17 : #include "GCHeapProfilerImpl.h"
18 : #include "GeckoProfiler.h"
19 : #include "NativeProfilerImpl.h"
20 : #include "UncensoredAllocator.h"
21 : #include "js/TypeDecls.h"
22 : #include "jsfriendapi.h"
23 : #include "nsIDOMClassInfo.h"
24 : #include "nsIGlobalObject.h"
25 : #include "prtime.h"
26 : #include "xpcprivate.h"
27 :
28 : namespace mozilla {
29 :
30 : #define MEMORY_PROFILER_SAMPLE_SIZE 65536
31 : #define BACKTRACE_BUFFER_SIZE 16384
32 :
33 0 : ProfilerImpl::ProfilerImpl()
34 0 : : mSampleSize(MEMORY_PROFILER_SAMPLE_SIZE)
35 : {
36 0 : mLog1minusP = std::log(1.0 - 1.0 / mSampleSize);
37 0 : mRemainingBytes = std::floor(std::log(1.0 - DRandom()) / mLog1minusP);
38 0 : }
39 :
40 : nsTArray<nsCString>
41 0 : ProfilerImpl::GetStacktrace()
42 : {
43 0 : nsTArray<nsCString> trace;
44 0 : auto output = MakeUnique<char[]>(BACKTRACE_BUFFER_SIZE);
45 :
46 0 : profiler_get_backtrace_noalloc(output.get(), BACKTRACE_BUFFER_SIZE);
47 0 : for (const char* p = output.get(); *p; p += strlen(p) + 1) {
48 0 : trace.AppendElement()->Assign(p);
49 : }
50 :
51 0 : return trace;
52 : }
53 :
54 : // Generate a random number in [0, 1).
55 : double
56 0 : ProfilerImpl::DRandom()
57 : {
58 0 : return double(((uint64_t(std::rand()) & ((1 << 26) - 1)) << 27) +
59 0 : (uint64_t(std::rand()) & ((1 << 27) - 1)))
60 0 : / (uint64_t(1) << 53);
61 : }
62 :
63 : size_t
64 0 : ProfilerImpl::AddBytesSampled(uint32_t aBytes)
65 : {
66 0 : size_t nSamples = 0;
67 0 : while (mRemainingBytes <= aBytes) {
68 0 : mRemainingBytes += std::floor(std::log(1.0 - DRandom()) / mLog1minusP);
69 0 : nSamples++;
70 : }
71 0 : mRemainingBytes -= aBytes;
72 0 : return nSamples;
73 : }
74 :
75 0 : NS_IMPL_ISUPPORTS(MemoryProfiler, nsIMemoryProfiler)
76 :
77 : PRLock* MemoryProfiler::sLock;
78 : uint32_t MemoryProfiler::sProfileContextCount;
79 3 : StaticAutoPtr<NativeProfilerImpl> MemoryProfiler::sNativeProfiler;
80 3 : StaticAutoPtr<JSContextProfilerMap> MemoryProfiler::sJSContextProfilerMap;
81 : TimeStamp MemoryProfiler::sStartTime;
82 :
83 : void
84 0 : MemoryProfiler::InitOnce()
85 : {
86 0 : MOZ_ASSERT(NS_IsMainThread());
87 :
88 : static bool initialized = false;
89 :
90 0 : if (!initialized) {
91 0 : MallocHook::Initialize();
92 0 : sLock = PR_NewLock();
93 0 : sProfileContextCount = 0;
94 0 : sJSContextProfilerMap = new JSContextProfilerMap();
95 0 : ClearOnShutdown(&sJSContextProfilerMap);
96 0 : ClearOnShutdown(&sNativeProfiler);
97 0 : std::srand(PR_Now());
98 0 : sStartTime = TimeStamp::ProcessCreation();
99 0 : initialized = true;
100 : }
101 0 : }
102 :
103 : NS_IMETHODIMP
104 0 : MemoryProfiler::StartProfiler()
105 : {
106 0 : InitOnce();
107 0 : AutoUseUncensoredAllocator ua;
108 0 : AutoMPLock lock(sLock);
109 0 : JSContext* context = XPCJSContext::Get()->Context();
110 0 : ProfilerForJSContext profiler;
111 0 : if (!sJSContextProfilerMap->Get(context, &profiler) ||
112 0 : !profiler.mEnabled) {
113 0 : if (sProfileContextCount == 0) {
114 0 : js::EnableContextProfilingStack(context, true);
115 0 : if (!sNativeProfiler) {
116 0 : sNativeProfiler = new NativeProfilerImpl();
117 : }
118 0 : MemProfiler::SetNativeProfiler(sNativeProfiler);
119 : }
120 0 : GCHeapProfilerImpl* gp = new GCHeapProfilerImpl();
121 0 : profiler.mEnabled = true;
122 0 : profiler.mProfiler = gp;
123 0 : sJSContextProfilerMap->Put(context, profiler);
124 0 : MemProfiler::GetMemProfiler(context)->start(gp);
125 0 : if (sProfileContextCount == 0) {
126 0 : MallocHook::Enable(sNativeProfiler);
127 : }
128 0 : sProfileContextCount++;
129 : }
130 0 : return NS_OK;
131 : }
132 :
133 : NS_IMETHODIMP
134 0 : MemoryProfiler::StopProfiler()
135 : {
136 0 : InitOnce();
137 0 : AutoUseUncensoredAllocator ua;
138 0 : AutoMPLock lock(sLock);
139 0 : JSContext* context = XPCJSContext::Get()->Context();
140 0 : ProfilerForJSContext profiler;
141 0 : if (sJSContextProfilerMap->Get(context, &profiler) &&
142 0 : profiler.mEnabled) {
143 0 : MemProfiler::GetMemProfiler(context)->stop();
144 0 : if (--sProfileContextCount == 0) {
145 0 : MallocHook::Disable();
146 0 : MemProfiler::SetNativeProfiler(nullptr);
147 0 : js::EnableContextProfilingStack(context, false);
148 : }
149 0 : profiler.mEnabled = false;
150 0 : sJSContextProfilerMap->Put(context, profiler);
151 : }
152 0 : return NS_OK;
153 : }
154 :
155 : NS_IMETHODIMP
156 0 : MemoryProfiler::ResetProfiler()
157 : {
158 0 : InitOnce();
159 0 : AutoUseUncensoredAllocator ua;
160 0 : AutoMPLock lock(sLock);
161 0 : JSContext* context = XPCJSContext::Get()->Context();
162 0 : ProfilerForJSContext profiler;
163 0 : if (!sJSContextProfilerMap->Get(context, &profiler) ||
164 0 : !profiler.mEnabled) {
165 0 : delete profiler.mProfiler;
166 0 : profiler.mProfiler = nullptr;
167 0 : sJSContextProfilerMap->Put(context, profiler);
168 : }
169 0 : if (sProfileContextCount == 0) {
170 0 : sNativeProfiler = nullptr;
171 : }
172 0 : return NS_OK;
173 : }
174 :
175 0 : struct MergedTraces
176 : {
177 : nsTArray<nsCString> mNames;
178 : nsTArray<TrieNode> mTraces;
179 : nsTArray<AllocEvent> mEvents;
180 : };
181 :
182 : // Merge events and corresponding traces and names.
183 : static MergedTraces
184 0 : MergeResults(const nsTArray<nsCString>& names0,
185 : const nsTArray<TrieNode>& traces0,
186 : const nsTArray<AllocEvent>& events0,
187 : const nsTArray<nsCString>& names1,
188 : const nsTArray<TrieNode>& traces1,
189 : const nsTArray<AllocEvent>& events1)
190 : {
191 0 : NodeIndexMap<nsCStringHashKey, nsCString> names;
192 0 : NodeIndexMap<nsGenericHashKey<TrieNode>, TrieNode> traces;
193 0 : nsTArray<AllocEvent> events;
194 :
195 0 : nsTArray<size_t> names1Tonames0(names1.Length());
196 0 : nsTArray<size_t> traces1Totraces0(traces1.Length());
197 :
198 : // Merge names.
199 0 : for (auto& i: names0) {
200 0 : names.Insert(i);
201 : }
202 0 : for (auto& i: names1) {
203 0 : names1Tonames0.AppendElement(names.Insert(i));
204 : }
205 :
206 : // Merge traces. Note that traces1[i].parentIdx < i for all i > 0.
207 0 : for (auto& i: traces0) {
208 0 : traces.Insert(i);
209 : }
210 0 : traces1Totraces0.AppendElement(0);
211 0 : for (size_t i = 1; i < traces1.Length(); i++) {
212 0 : TrieNode node = traces1[i];
213 0 : node.parentIdx = traces1Totraces0[node.parentIdx];
214 0 : node.nameIdx = names1Tonames0[node.nameIdx];
215 0 : traces1Totraces0.AppendElement(traces.Insert(node));
216 : }
217 :
218 : // Merge the events according to timestamps.
219 0 : auto p0 = events0.begin();
220 0 : auto p1 = events1.begin();
221 :
222 0 : while (p0 != events0.end() && p1 != events1.end()) {
223 0 : if (p0->mTimestamp < p1->mTimestamp) {
224 0 : events.AppendElement(*p0++);
225 : } else {
226 0 : events.AppendElement(*p1++);
227 0 : events.LastElement().mTraceIdx =
228 0 : traces1Totraces0[events.LastElement().mTraceIdx];
229 : }
230 : }
231 :
232 0 : while (p0 != events0.end()) {
233 0 : events.AppendElement(*p0++);
234 : }
235 :
236 0 : while (p1 != events1.end()) {
237 0 : events.AppendElement(*p1++);
238 0 : events.LastElement().mTraceIdx =
239 0 : traces1Totraces0[events.LastElement().mTraceIdx];
240 : }
241 :
242 0 : return MergedTraces{names.Serialize(), traces.Serialize(), Move(events)};
243 : }
244 :
245 : NS_IMETHODIMP
246 0 : MemoryProfiler::GetResults(JSContext* cx, JS::MutableHandle<JS::Value> aResult)
247 : {
248 0 : InitOnce();
249 0 : AutoUseUncensoredAllocator ua;
250 0 : AutoMPLock lock(sLock);
251 0 : JSContext* context = XPCJSContext::Get()->Context();
252 : // Getting results when the profiler is running is not allowed.
253 0 : if (sProfileContextCount > 0) {
254 0 : return NS_OK;
255 : }
256 : // Return immediately when native profiler does not exist.
257 0 : if (!sNativeProfiler) {
258 0 : return NS_OK;
259 : }
260 : // Return immediately when there's no result in current context.
261 0 : ProfilerForJSContext profiler;
262 0 : if (!sJSContextProfilerMap->Get(context, &profiler) ||
263 0 : !profiler.mProfiler) {
264 0 : return NS_OK;
265 : }
266 0 : GCHeapProfilerImpl* gp = profiler.mProfiler;
267 :
268 0 : auto results = MergeResults(gp->GetNames(), gp->GetTraces(), gp->GetEvents(),
269 0 : sNativeProfiler->GetNames(),
270 0 : sNativeProfiler->GetTraces(),
271 0 : sNativeProfiler->GetEvents());
272 0 : const nsTArray<nsCString>& names = results.mNames;
273 0 : const nsTArray<TrieNode>& traces = results.mTraces;
274 0 : const nsTArray<AllocEvent>& events = results.mEvents;
275 :
276 0 : JS::RootedObject jsnames(cx, JS_NewArrayObject(cx, names.Length()));
277 0 : JS::RootedObject jstraces(cx, JS_NewArrayObject(cx, traces.Length()));
278 0 : JS::RootedObject jsevents(cx, JS_NewArrayObject(cx, events.Length()));
279 :
280 0 : for (size_t i = 0; i < names.Length(); i++) {
281 0 : JS::RootedString name(cx, JS_NewStringCopyZ(cx, names[i].get()));
282 0 : JS_SetElement(cx, jsnames, i, name);
283 : }
284 :
285 0 : for (size_t i = 0; i < traces.Length(); i++) {
286 0 : JS::RootedObject tn(cx, JS_NewPlainObject(cx));
287 0 : JS::RootedValue nameIdx(cx, JS_NumberValue(traces[i].nameIdx));
288 0 : JS::RootedValue parentIdx(cx, JS_NumberValue(traces[i].parentIdx));
289 0 : JS_SetProperty(cx, tn, "nameIdx", nameIdx);
290 0 : JS_SetProperty(cx, tn, "parentIdx", parentIdx);
291 0 : JS_SetElement(cx, jstraces, i, tn);
292 : }
293 :
294 0 : int i = 0;
295 0 : for (auto ent: events) {
296 0 : if (ent.mSize == 0) {
297 0 : continue;
298 : }
299 0 : MOZ_ASSERT(!sStartTime.IsNull());
300 0 : double time = (ent.mTimestamp - sStartTime).ToMilliseconds();
301 0 : JS::RootedObject tn(cx, JS_NewPlainObject(cx));
302 0 : JS::RootedValue size(cx, JS_NumberValue(ent.mSize));
303 0 : JS::RootedValue traceIdx(cx, JS_NumberValue(ent.mTraceIdx));
304 0 : JS::RootedValue timestamp(cx, JS_NumberValue(time));
305 0 : JS_SetProperty(cx, tn, "size", size);
306 0 : JS_SetProperty(cx, tn, "traceIdx", traceIdx);
307 0 : JS_SetProperty(cx, tn, "timestamp", timestamp);
308 0 : JS_SetElement(cx, jsevents, i++, tn);
309 : }
310 0 : JS_SetArrayLength(cx, jsevents, i);
311 :
312 0 : JS::RootedObject result(cx, JS_NewPlainObject(cx));
313 0 : JS::RootedValue objnames(cx, ObjectOrNullValue(jsnames));
314 0 : JS_SetProperty(cx, result, "names", objnames);
315 0 : JS::RootedValue objtraces(cx, ObjectOrNullValue(jstraces));
316 0 : JS_SetProperty(cx, result, "traces", objtraces);
317 0 : JS::RootedValue objevents(cx, ObjectOrNullValue(jsevents));
318 0 : JS_SetProperty(cx, result, "events", objevents);
319 0 : aResult.setObject(*result);
320 0 : return NS_OK;
321 : }
322 :
323 : } // namespace mozilla
|