|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 // System |
|
7 #include <string> |
|
8 #include <stdio.h> |
|
9 #include <errno.h> |
|
10 #include <ostream> |
|
11 #include <fstream> |
|
12 #include <sstream> |
|
13 |
|
14 // Profiler |
|
15 #include "PlatformMacros.h" |
|
16 #include "GeckoProfiler.h" |
|
17 #include "platform.h" |
|
18 #include "nsXULAppAPI.h" |
|
19 #include "nsThreadUtils.h" |
|
20 #include "prenv.h" |
|
21 #include "shared-libraries.h" |
|
22 #include "mozilla/StackWalk.h" |
|
23 #include "ProfileEntry.h" |
|
24 #include "SyncProfile.h" |
|
25 #include "SaveProfileTask.h" |
|
26 #include "UnwinderThread2.h" |
|
27 #include "TableTicker.h" |
|
28 |
|
29 // Meta |
|
30 #include "nsXPCOM.h" |
|
31 #include "nsXPCOMCID.h" |
|
32 #include "nsIHttpProtocolHandler.h" |
|
33 #include "nsServiceManagerUtils.h" |
|
34 #include "nsIXULRuntime.h" |
|
35 #include "nsIXULAppInfo.h" |
|
36 #include "nsDirectoryServiceUtils.h" |
|
37 #include "nsDirectoryServiceDefs.h" |
|
38 #include "nsIObserverService.h" |
|
39 #include "mozilla/Services.h" |
|
40 |
|
41 // JS |
|
42 #include "js/OldDebugAPI.h" |
|
43 |
|
44 // This file's exports are listed in GeckoProfilerImpl.h. |
|
45 |
|
46 /* These will be set to something sensible before we take the first |
|
47 sample. */ |
|
48 UnwMode sUnwindMode = UnwINVALID; |
|
49 int sUnwindInterval = 0; |
|
50 int sUnwindStackScan = 0; |
|
51 int sProfileEntries = 0; |
|
52 |
|
53 using std::string; |
|
54 using namespace mozilla; |
|
55 |
|
56 #if _MSC_VER |
|
57 #define snprintf _snprintf |
|
58 #endif |
|
59 |
|
60 |
|
61 //////////////////////////////////////////////////////////////////////// |
|
62 // BEGIN take samples. |
|
63 // Everything in this section RUNS IN SIGHANDLER CONTEXT |
|
64 |
|
65 // RUNS IN SIGHANDLER CONTEXT |
|
66 static |
|
67 void genProfileEntry(/*MODIFIED*/UnwinderThreadBuffer* utb, |
|
68 volatile StackEntry &entry, |
|
69 PseudoStack *stack, void *lastpc) |
|
70 { |
|
71 int lineno = -1; |
|
72 |
|
73 // Add a pseudostack-entry start label |
|
74 utb__addEntry( utb, ProfileEntry('h', 'P') ); |
|
75 // And the SP value, if it is non-zero |
|
76 if (entry.stackAddress() != 0) { |
|
77 utb__addEntry( utb, ProfileEntry('S', entry.stackAddress()) ); |
|
78 } |
|
79 |
|
80 // First entry has tagName 's' (start) |
|
81 // Check for magic pointer bit 1 to indicate copy |
|
82 const char* sampleLabel = entry.label(); |
|
83 if (entry.isCopyLabel()) { |
|
84 // Store the string using 1 or more 'd' (dynamic) tags |
|
85 // that will happen to the preceding tag |
|
86 |
|
87 utb__addEntry( utb, ProfileEntry('c', "") ); |
|
88 // Add one to store the null termination |
|
89 size_t strLen = strlen(sampleLabel) + 1; |
|
90 for (size_t j = 0; j < strLen;) { |
|
91 // Store as many characters in the void* as the platform allows |
|
92 char text[sizeof(void*)]; |
|
93 for (size_t pos = 0; pos < sizeof(void*) && j+pos < strLen; pos++) { |
|
94 text[pos] = sampleLabel[j+pos]; |
|
95 } |
|
96 j += sizeof(void*)/sizeof(char); |
|
97 // Cast to *((void**) to pass the text data to a void* |
|
98 utb__addEntry( utb, ProfileEntry('d', *((void**)(&text[0]))) ); |
|
99 } |
|
100 if (entry.js()) { |
|
101 if (!entry.pc()) { |
|
102 // The JIT only allows the top-most entry to have a nullptr pc |
|
103 MOZ_ASSERT(&entry == &stack->mStack[stack->stackSize() - 1]); |
|
104 // If stack-walking was disabled, then that's just unfortunate |
|
105 if (lastpc) { |
|
106 jsbytecode *jspc = js::ProfilingGetPC(stack->mRuntime, entry.script(), |
|
107 lastpc); |
|
108 if (jspc) { |
|
109 lineno = JS_PCToLineNumber(nullptr, entry.script(), jspc); |
|
110 } |
|
111 } |
|
112 } else { |
|
113 lineno = JS_PCToLineNumber(nullptr, entry.script(), entry.pc()); |
|
114 } |
|
115 } else { |
|
116 lineno = entry.line(); |
|
117 } |
|
118 } else { |
|
119 utb__addEntry( utb, ProfileEntry('c', sampleLabel) ); |
|
120 lineno = entry.line(); |
|
121 } |
|
122 if (lineno != -1) { |
|
123 utb__addEntry( utb, ProfileEntry('n', lineno) ); |
|
124 } |
|
125 |
|
126 // Add a pseudostack-entry end label |
|
127 utb__addEntry( utb, ProfileEntry('h', 'Q') ); |
|
128 } |
|
129 |
|
130 // RUNS IN SIGHANDLER CONTEXT |
|
131 // Generate pseudo-backtrace entries and put them in |utb|, with |
|
132 // the order outermost frame first. |
|
133 void genPseudoBacktraceEntries(/*MODIFIED*/UnwinderThreadBuffer* utb, |
|
134 PseudoStack *aStack, TickSample *sample) |
|
135 { |
|
136 // Call genProfileEntry to generate tags for each profile |
|
137 // entry. Each entry will be bounded by a 'h' 'P' tag to |
|
138 // mark the start and a 'h' 'Q' tag to mark the end. |
|
139 uint32_t nInStack = aStack->stackSize(); |
|
140 for (uint32_t i = 0; i < nInStack; i++) { |
|
141 genProfileEntry(utb, aStack->mStack[i], aStack, nullptr); |
|
142 } |
|
143 # ifdef ENABLE_SPS_LEAF_DATA |
|
144 if (sample) { |
|
145 utb__addEntry( utb, ProfileEntry('l', (void*)sample->pc) ); |
|
146 # ifdef ENABLE_ARM_LR_SAVING |
|
147 utb__addEntry( utb, ProfileEntry('L', (void*)sample->lr) ); |
|
148 # endif |
|
149 } |
|
150 # endif |
|
151 } |
|
152 |
|
153 // RUNS IN SIGHANDLER CONTEXT |
|
154 static |
|
155 void populateBuffer(UnwinderThreadBuffer* utb, TickSample* sample, |
|
156 UTB_RELEASE_FUNC releaseFunction, bool jankOnly) |
|
157 { |
|
158 ThreadProfile& sampledThreadProfile = *sample->threadProfile; |
|
159 PseudoStack* stack = sampledThreadProfile.GetPseudoStack(); |
|
160 |
|
161 /* Manufacture the ProfileEntries that we will give to the unwinder |
|
162 thread, and park them in |utb|. */ |
|
163 bool recordSample = true; |
|
164 |
|
165 /* Don't process the PeudoStack's markers or honour jankOnly if we're |
|
166 immediately sampling the current thread. */ |
|
167 if (!sample->isSamplingCurrentThread) { |
|
168 // LinkedUWTBuffers before markers |
|
169 UWTBufferLinkedList* syncBufs = stack->getLinkedUWTBuffers(); |
|
170 while (syncBufs && syncBufs->peek()) { |
|
171 LinkedUWTBuffer* syncBuf = syncBufs->popHead(); |
|
172 utb__addEntry(utb, ProfileEntry('B', syncBuf->GetBuffer())); |
|
173 } |
|
174 // Marker(s) come before the sample |
|
175 ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers(); |
|
176 while (pendingMarkersList && pendingMarkersList->peek()) { |
|
177 ProfilerMarker* marker = pendingMarkersList->popHead(); |
|
178 stack->addStoredMarker(marker); |
|
179 utb__addEntry( utb, ProfileEntry('m', marker) ); |
|
180 } |
|
181 stack->updateGeneration(sampledThreadProfile.GetGenerationID()); |
|
182 if (jankOnly) { |
|
183 // if we are on a different event we can discard any temporary samples |
|
184 // we've kept around |
|
185 if (sLastSampledEventGeneration != sCurrentEventGeneration) { |
|
186 // XXX: we also probably want to add an entry to the profile to help |
|
187 // distinguish which samples are part of the same event. That, or record |
|
188 // the event generation in each sample |
|
189 sampledThreadProfile.erase(); |
|
190 } |
|
191 sLastSampledEventGeneration = sCurrentEventGeneration; |
|
192 |
|
193 recordSample = false; |
|
194 // only record the events when we have a we haven't seen a tracer |
|
195 // event for 100ms |
|
196 if (!sLastTracerEvent.IsNull()) { |
|
197 TimeDuration delta = sample->timestamp - sLastTracerEvent; |
|
198 if (delta.ToMilliseconds() > 100.0) { |
|
199 recordSample = true; |
|
200 } |
|
201 } |
|
202 } |
|
203 } |
|
204 |
|
205 // JRS 2012-Sept-27: this logic used to involve mUseStackWalk. |
|
206 // That should be reinstated, but for the moment, use the |
|
207 // settings in sUnwindMode and sUnwindInterval. |
|
208 // Add a native-backtrace request, or add pseudo backtrace entries, |
|
209 // or both. |
|
210 switch (sUnwindMode) { |
|
211 case UnwNATIVE: /* Native only */ |
|
212 // add a "do native stack trace now" hint. This will be actioned |
|
213 // by the unwinder thread as it processes the entries in this |
|
214 // sample. |
|
215 utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'N'/*native-trace*/) ); |
|
216 break; |
|
217 case UnwPSEUDO: /* Pseudo only */ |
|
218 /* Add into |utb|, the pseudo backtrace entries */ |
|
219 genPseudoBacktraceEntries(utb, stack, sample); |
|
220 break; |
|
221 case UnwCOMBINED: /* Both Native and Pseudo */ |
|
222 utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'N'/*native-trace*/) ); |
|
223 genPseudoBacktraceEntries(utb, stack, sample); |
|
224 break; |
|
225 case UnwINVALID: |
|
226 default: |
|
227 MOZ_CRASH(); |
|
228 } |
|
229 |
|
230 if (recordSample) { |
|
231 // add a "flush now" hint |
|
232 utb__addEntry( utb, ProfileEntry('h'/*hint*/, 'F'/*flush*/) ); |
|
233 } |
|
234 |
|
235 // Add any extras |
|
236 if (!sLastTracerEvent.IsNull() && sample) { |
|
237 TimeDuration delta = sample->timestamp - sLastTracerEvent; |
|
238 utb__addEntry( utb, ProfileEntry('r', static_cast<float>(delta.ToMilliseconds())) ); |
|
239 } |
|
240 |
|
241 if (sample) { |
|
242 TimeDuration delta = sample->timestamp - sStartTime; |
|
243 utb__addEntry( utb, ProfileEntry('t', static_cast<float>(delta.ToMilliseconds())) ); |
|
244 } |
|
245 |
|
246 if (sLastFrameNumber != sFrameNumber) { |
|
247 utb__addEntry( utb, ProfileEntry('f', sFrameNumber) ); |
|
248 sLastFrameNumber = sFrameNumber; |
|
249 } |
|
250 |
|
251 /* So now we have, in |utb|, the complete set of entries we want to |
|
252 push into the circular buffer. This may also include a 'h' 'F' |
|
253 entry, which is "flush now" hint, and/or a 'h' 'N' entry, which |
|
254 is a "generate a native backtrace and add it to the buffer right |
|
255 now" hint. Hand them off to the helper thread, together with |
|
256 stack and register context needed to do a native unwind, if that |
|
257 is currently enabled. */ |
|
258 |
|
259 /* If a native unwind has been requested, we'll start it off using |
|
260 the context obtained from the signal handler, to avoid the |
|
261 problem of having to unwind through the signal frame itself. */ |
|
262 |
|
263 /* On Linux and Android, the initial register state is in the |
|
264 supplied sample->context. But on MacOS it's not, so we have to |
|
265 fake it up here (sigh). */ |
|
266 if (sUnwindMode == UnwNATIVE || sUnwindMode == UnwCOMBINED) { |
|
267 # if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_arm_android) \ |
|
268 || defined(SPS_PLAT_x86_linux) || defined(SPS_PLAT_x86_android) |
|
269 void* ucV = (void*)sample->context; |
|
270 # elif defined(SPS_PLAT_amd64_darwin) |
|
271 struct __darwin_mcontext64 mc; |
|
272 memset(&mc, 0, sizeof(mc)); |
|
273 ucontext_t uc; |
|
274 memset(&uc, 0, sizeof(uc)); |
|
275 uc.uc_mcontext = &mc; |
|
276 mc.__ss.__rip = (uint64_t)sample->pc; |
|
277 mc.__ss.__rsp = (uint64_t)sample->sp; |
|
278 mc.__ss.__rbp = (uint64_t)sample->fp; |
|
279 void* ucV = (void*)&uc; |
|
280 # elif defined(SPS_PLAT_x86_darwin) |
|
281 struct __darwin_mcontext32 mc; |
|
282 memset(&mc, 0, sizeof(mc)); |
|
283 ucontext_t uc; |
|
284 memset(&uc, 0, sizeof(uc)); |
|
285 uc.uc_mcontext = &mc; |
|
286 mc.__ss.__eip = (uint32_t)sample->pc; |
|
287 mc.__ss.__esp = (uint32_t)sample->sp; |
|
288 mc.__ss.__ebp = (uint32_t)sample->fp; |
|
289 void* ucV = (void*)&uc; |
|
290 # elif defined(SPS_OS_windows) |
|
291 /* Totally fake this up so it at least builds. No idea if we can |
|
292 even ever get here on Windows. */ |
|
293 void* ucV = nullptr; |
|
294 # else |
|
295 # error "Unsupported platform" |
|
296 # endif |
|
297 releaseFunction(&sampledThreadProfile, utb, ucV); |
|
298 } else { |
|
299 releaseFunction(&sampledThreadProfile, utb, nullptr); |
|
300 } |
|
301 } |
|
302 |
|
303 static |
|
304 void sampleCurrent(TickSample* sample) |
|
305 { |
|
306 // This variant requires sample->threadProfile to be set |
|
307 MOZ_ASSERT(sample->threadProfile); |
|
308 LinkedUWTBuffer* syncBuf = utb__acquire_sync_buffer(tlsStackTop.get()); |
|
309 if (!syncBuf) { |
|
310 return; |
|
311 } |
|
312 SyncProfile* syncProfile = sample->threadProfile->AsSyncProfile(); |
|
313 MOZ_ASSERT(syncProfile); |
|
314 if (!syncProfile->SetUWTBuffer(syncBuf)) { |
|
315 utb__release_sync_buffer(syncBuf); |
|
316 return; |
|
317 } |
|
318 UnwinderThreadBuffer* utb = syncBuf->GetBuffer(); |
|
319 populateBuffer(utb, sample, &utb__finish_sync_buffer, false); |
|
320 } |
|
321 |
|
322 // RUNS IN SIGHANDLER CONTEXT |
|
323 void TableTicker::UnwinderTick(TickSample* sample) |
|
324 { |
|
325 if (sample->isSamplingCurrentThread) { |
|
326 sampleCurrent(sample); |
|
327 return; |
|
328 } |
|
329 |
|
330 if (!sample->threadProfile) { |
|
331 // Platform doesn't support multithread, so use the main thread profile we created |
|
332 sample->threadProfile = GetPrimaryThreadProfile(); |
|
333 } |
|
334 |
|
335 /* Get hold of an empty inter-thread buffer into which to park |
|
336 the ProfileEntries for this sample. */ |
|
337 UnwinderThreadBuffer* utb = uwt__acquire_empty_buffer(); |
|
338 |
|
339 /* This could fail, if no buffers are currently available, in which |
|
340 case we must give up right away. We cannot wait for a buffer to |
|
341 become available, as that risks deadlock. */ |
|
342 if (!utb) |
|
343 return; |
|
344 |
|
345 populateBuffer(utb, sample, &uwt__release_full_buffer, mJankOnly); |
|
346 } |
|
347 |
|
348 // END take samples |
|
349 //////////////////////////////////////////////////////////////////////// |
|
350 |