Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #ifndef ThreadInfo_h
8 : #define ThreadInfo_h
9 :
10 : #include "mozilla/NotNull.h"
11 : #include "mozilla/UniquePtrExtensions.h"
12 :
13 : #include "platform.h"
14 : #include "ProfileBuffer.h"
15 : #include "js/ProfilingStack.h"
16 :
17 : // This class contains the info for a single thread that is accessible without
18 : // protection from gPSMutex in platform.cpp. Because there is no external
19 : // protection against data races, it must provide internal protection. Hence
20 : // the "Racy" prefix.
21 : //
22 : class RacyThreadInfo final : public PseudoStack
23 : {
24 : public:
25 75 : RacyThreadInfo()
26 75 : : PseudoStack()
27 75 : , mSleep(AWAKE)
28 : {
29 75 : MOZ_COUNT_CTOR(RacyThreadInfo);
30 75 : }
31 :
32 1 : ~RacyThreadInfo()
33 1 : {
34 1 : MOZ_COUNT_DTOR(RacyThreadInfo);
35 1 : }
36 :
37 0 : size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
38 : {
39 0 : size_t n = aMallocSizeOf(this);
40 :
41 : // Measurement of the following members may be added later if DMD finds it
42 : // is worthwhile:
43 : // - things in the PseudoStack
44 : // - mPendingMarkers
45 : //
46 : // If these measurements are added, the code must be careful to avoid data
47 : // races. (The current code doesn't have any race issues because the
48 : // contents of the PseudoStack object aren't accessed; |this| is used only
49 : // as an address for lookup by aMallocSizeof).
50 :
51 0 : return n;
52 : }
53 :
54 0 : void AddPendingMarker(const char* aMarkerName,
55 : mozilla::UniquePtr<ProfilerMarkerPayload> aPayload,
56 : double aTime)
57 : {
58 : ProfilerMarker* marker =
59 0 : new ProfilerMarker(aMarkerName, Move(aPayload), aTime);
60 0 : mPendingMarkers.insert(marker);
61 0 : }
62 :
63 : // Called within signal. Function must be reentrant.
64 0 : ProfilerMarkerLinkedList* GetPendingMarkers()
65 : {
66 : // The profiled thread is interrupted, so we can access the list safely.
67 : // Unless the profiled thread was in the middle of changing the list when
68 : // we interrupted it - in that case, accessList() will return null.
69 0 : return mPendingMarkers.accessList();
70 : }
71 :
72 : // This is called on every profiler restart. Put things that should happen at
73 : // that time here.
74 0 : void ReinitializeOnResume()
75 : {
76 : // This is needed to cause an initial sample to be taken from sleeping
77 : // threads that had been observed prior to the profiler stopping and
78 : // restarting. Otherwise sleeping threads would not have any samples to
79 : // copy forward while sleeping.
80 0 : (void)mSleep.compareExchange(SLEEPING_OBSERVED, SLEEPING_NOT_OBSERVED);
81 0 : }
82 :
83 : // This returns true for the second and subsequent calls in each sleep cycle.
84 0 : bool CanDuplicateLastSampleDueToSleep()
85 : {
86 0 : if (mSleep == AWAKE) {
87 0 : return false;
88 : }
89 :
90 0 : if (mSleep.compareExchange(SLEEPING_NOT_OBSERVED, SLEEPING_OBSERVED)) {
91 0 : return false;
92 : }
93 :
94 0 : return true;
95 : }
96 :
97 : // Call this whenever the current thread sleeps. Calling it twice in a row
98 : // without an intervening setAwake() call is an error.
99 2657 : void SetSleeping()
100 : {
101 2657 : MOZ_ASSERT(mSleep == AWAKE);
102 2656 : mSleep = SLEEPING_NOT_OBSERVED;
103 2656 : }
104 :
105 : // Call this whenever the current thread wakes. Calling it twice in a row
106 : // without an intervening setSleeping() call is an error.
107 2652 : void SetAwake()
108 : {
109 2652 : MOZ_ASSERT(mSleep != AWAKE);
110 2652 : mSleep = AWAKE;
111 2652 : }
112 :
113 0 : bool IsSleeping() { return mSleep != AWAKE; }
114 :
115 : private:
116 : // A list of pending markers that must be moved to the circular buffer.
117 : ProfilerSignalSafeLinkedList<ProfilerMarker> mPendingMarkers;
118 :
119 : // mSleep tracks whether the thread is sleeping, and if so, whether it has
120 : // been previously observed. This is used for an optimization: in some cases,
121 : // when a thread is asleep, we duplicate the previous sample, which is
122 : // cheaper than taking a new sample.
123 : //
124 : // mSleep is atomic because it is accessed from multiple threads.
125 : //
126 : // - It is written only by this thread, via setSleeping() and setAwake().
127 : //
128 : // - It is read by SamplerThread::Run().
129 : //
130 : // There are two cases where racing between threads can cause an issue.
131 : //
132 : // - If CanDuplicateLastSampleDueToSleep() returns false but that result is
133 : // invalidated before being acted upon, we will take a full sample
134 : // unnecessarily. This is additional work but won't cause any correctness
135 : // issues. (In actual fact, this case is impossible. In order to go from
136 : // CanDuplicateLastSampleDueToSleep() returning false to it returning true
137 : // requires an intermediate call to it in order for mSleep to go from
138 : // SLEEPING_NOT_OBSERVED to SLEEPING_OBSERVED.)
139 : //
140 : // - If CanDuplicateLastSampleDueToSleep() returns true but that result is
141 : // invalidated before being acted upon -- i.e. the thread wakes up before
142 : // DuplicateLastSample() is called -- we will duplicate the previous
143 : // sample. This is inaccurate, but only slightly... we will effectively
144 : // treat the thread as having slept a tiny bit longer than it really did.
145 : //
146 : // This latter inaccuracy could be avoided by moving the
147 : // CanDuplicateLastSampleDueToSleep() check within the thread-freezing code,
148 : // e.g. the section where Tick() is called. But that would reduce the
149 : // effectiveness of the optimization because more code would have to be run
150 : // before we can tell that duplication is allowed.
151 : //
152 : static const int AWAKE = 0;
153 : static const int SLEEPING_NOT_OBSERVED = 1;
154 : static const int SLEEPING_OBSERVED = 2;
155 : mozilla::Atomic<int> mSleep;
156 : };
157 :
158 : // This class contains the info for a single thread.
159 : //
160 : // Note: A thread's ThreadInfo can be held onto after the thread itself exits,
161 : // because we may need to output profiling information about that thread. But
162 : // some of the fields in this class are only relevant while the thread is
163 : // alive. It's possible that this class could be refactored so there is a
164 : // clearer split between those fields and the fields that are still relevant
165 : // after the thread exists.
166 : class ThreadInfo final
167 : {
168 : public:
169 : ThreadInfo(const char* aName, int aThreadId, bool aIsMainThread,
170 : void* aStackTop);
171 :
172 : ~ThreadInfo();
173 :
174 0 : const char* Name() const { return mName.get(); }
175 1040 : int ThreadId() const { return mThreadId; }
176 :
177 0 : bool IsMainThread() const { return mIsMainThread; }
178 :
179 5388 : mozilla::NotNull<RacyThreadInfo*> RacyInfo() const { return mRacyInfo; }
180 :
181 : void StartProfiling();
182 : void StopProfiling();
183 0 : bool IsBeingProfiled() { return mIsBeingProfiled; }
184 :
185 : PlatformData* GetPlatformData() const { return mPlatformData.get(); }
186 0 : void* StackTop() const { return mStackTop; }
187 :
188 : size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
189 :
190 0 : ProfileBuffer::LastSample& LastSample() { return mLastSample; }
191 :
192 : private:
193 : mozilla::UniqueFreePtr<char> mName;
194 : int mThreadId;
195 : const bool mIsMainThread;
196 :
197 : // The thread's RacyThreadInfo. This is an owning pointer. It could be an
198 : // inline member, but we don't do that because RacyThreadInfo is quite large
199 : // (due to the PseudoStack within it), and we have ThreadInfo vectors and so
200 : // we'd end up wasting a lot of space in those vectors for excess elements.
201 : mozilla::NotNull<RacyThreadInfo*> mRacyInfo;
202 :
203 : UniquePlatformData mPlatformData;
204 : void* mStackTop;
205 :
206 : //
207 : // The following code is only used for threads that are being profiled, i.e.
208 : // for which IsBeingProfiled() returns true.
209 : //
210 :
211 : public:
212 : void StreamJSON(const ProfileBuffer& aBuffer, SpliceableJSONWriter& aWriter,
213 : const mozilla::TimeStamp& aProcessStartTime,
214 : double aSinceTime);
215 :
216 : // Call this method when the JS entries inside the buffer are about to
217 : // become invalid, i.e., just before JS shutdown.
218 : void FlushSamplesAndMarkers(const mozilla::TimeStamp& aProcessStartTime,
219 : ProfileBuffer& aBuffer);
220 :
221 : // Returns nullptr if this is not the main thread or if this thread is not
222 : // being profiled.
223 0 : ThreadResponsiveness* GetThreadResponsiveness()
224 : {
225 0 : ThreadResponsiveness* responsiveness = mResponsiveness.ptrOr(nullptr);
226 0 : MOZ_ASSERT(!!responsiveness == (mIsMainThread && mIsBeingProfiled));
227 0 : return responsiveness;
228 : }
229 :
230 : // Set the JSContext of the thread to be sampled. Sampling cannot begin until
231 : // this has been set.
232 4 : void SetJSContext(JSContext* aContext)
233 : {
234 : // This function runs on-thread.
235 :
236 4 : MOZ_ASSERT(aContext && !mContext);
237 :
238 4 : mContext = aContext;
239 :
240 : // We give the JS engine a non-owning reference to the RacyInfo (just the
241 : // PseudoStack, really). It's important that the JS engine doesn't touch
242 : // this once the thread dies.
243 4 : js::SetContextProfilingStack(aContext, RacyInfo());
244 :
245 4 : PollJSSampling();
246 4 : }
247 :
248 : // Request that this thread start JS sampling. JS sampling won't actually
249 : // start until a subsequent PollJSSampling() call occurs *and* mContext has
250 : // been set.
251 0 : void StartJSSampling()
252 : {
253 : // This function runs on-thread or off-thread.
254 :
255 0 : MOZ_RELEASE_ASSERT(mJSSampling == INACTIVE ||
256 : mJSSampling == INACTIVE_REQUESTED);
257 0 : mJSSampling = ACTIVE_REQUESTED;
258 0 : }
259 :
260 : // Request that this thread stop JS sampling. JS sampling won't actually stop
261 : // until a subsequent PollJSSampling() call occurs.
262 0 : void StopJSSampling()
263 : {
264 : // This function runs on-thread or off-thread.
265 :
266 0 : MOZ_RELEASE_ASSERT(mJSSampling == ACTIVE ||
267 : mJSSampling == ACTIVE_REQUESTED);
268 0 : mJSSampling = INACTIVE_REQUESTED;
269 0 : }
270 :
271 : // Poll to see if JS sampling should be started/stopped.
272 31 : void PollJSSampling()
273 : {
274 : // This function runs on-thread.
275 :
276 : // We can't start/stop profiling until we have the thread's JSContext.
277 31 : if (mContext) {
278 : // It is possible for mJSSampling to go through the following sequences.
279 : //
280 : // - INACTIVE, ACTIVE_REQUESTED, INACTIVE_REQUESTED, INACTIVE
281 : //
282 : // - ACTIVE, INACTIVE_REQUESTED, ACTIVE_REQUESTED, ACTIVE
283 : //
284 : // Therefore, the if and else branches here aren't always interleaved.
285 : // This is ok because the JS engine can handle that.
286 : //
287 31 : if (mJSSampling == ACTIVE_REQUESTED) {
288 0 : mJSSampling = ACTIVE;
289 0 : js::EnableContextProfilingStack(mContext, true);
290 0 : js::RegisterContextProfilingEventMarker(mContext, profiler_add_marker);
291 :
292 31 : } else if (mJSSampling == INACTIVE_REQUESTED) {
293 0 : mJSSampling = INACTIVE;
294 0 : js::EnableContextProfilingStack(mContext, false);
295 : }
296 : }
297 31 : }
298 :
299 : private:
300 : bool mIsBeingProfiled;
301 :
302 : // JS frames in the buffer may require a live JSRuntime to stream (e.g.,
303 : // stringifying JIT frames). In the case of JSRuntime destruction,
304 : // FlushSamplesAndMarkers should be called to save them. These are spliced
305 : // into the final stream.
306 : mozilla::UniquePtr<char[]> mSavedStreamedSamples;
307 : mozilla::UniquePtr<char[]> mSavedStreamedMarkers;
308 : mozilla::Maybe<UniqueStacks> mUniqueStacks;
309 :
310 : // This is only used for the main thread.
311 : mozilla::Maybe<ThreadResponsiveness> mResponsiveness;
312 :
313 : public:
314 : // If this is a JS thread, this is its JSContext, which is required for any
315 : // JS sampling.
316 : JSContext* mContext;
317 :
318 : private:
319 : // The profiler needs to start and stop JS sampling of JS threads at various
320 : // times. However, the JS engine can only do the required actions on the
321 : // JS thread itself ("on-thread"), not from another thread ("off-thread").
322 : // Therefore, we have the following two-step process.
323 : //
324 : // - The profiler requests (on-thread or off-thread) that the JS sampling be
325 : // started/stopped, by changing mJSSampling to the appropriate REQUESTED
326 : // state.
327 : //
328 : // - The relevant JS thread polls (on-thread) for changes to mJSSampling.
329 : // When it sees a REQUESTED state, it performs the appropriate actions to
330 : // actually start/stop JS sampling, and changes mJSSampling out of the
331 : // REQUESTED state.
332 : //
333 : // The state machine is as follows.
334 : //
335 : // INACTIVE --> ACTIVE_REQUESTED
336 : // ^ ^ |
337 : // | _/ |
338 : // | _/ |
339 : // | / |
340 : // | v v
341 : // INACTIVE_REQUESTED <-- ACTIVE
342 : //
343 : // The polling is done in the following two ways.
344 : //
345 : // - Via the interrupt callback mechanism; the JS thread must call
346 : // profiler_js_interrupt_callback() from its own interrupt callback.
347 : // This is how sampling must be started/stopped for threads where the
348 : // request was made off-thread.
349 : //
350 : // - When {Start,Stop}JSSampling() is called on-thread, we can immediately
351 : // follow it with a PollJSSampling() call to avoid the delay between the
352 : // two steps. Likewise, setJSContext() calls PollJSSampling().
353 : //
354 : // One non-obvious thing about all this: these JS sampling requests are made
355 : // on all threads, even non-JS threads. mContext needs to also be set (via
356 : // setJSContext(), which can only happen for JS threads) for any JS sampling
357 : // to actually happen.
358 : //
359 : enum {
360 : INACTIVE = 0,
361 : ACTIVE_REQUESTED = 1,
362 : ACTIVE = 2,
363 : INACTIVE_REQUESTED = 3,
364 : } mJSSampling;
365 :
366 : // When sampling, this holds the generation number and offset in
367 : // ActivePS::mBuffer of the most recent sample for this thread.
368 : ProfileBuffer::LastSample mLastSample;
369 : };
370 :
371 : void
372 : StreamSamplesAndMarkers(const char* aName, int aThreadId,
373 : const ProfileBuffer& aBuffer,
374 : SpliceableJSONWriter& aWriter,
375 : const mozilla::TimeStamp& aProcessStartTime,
376 : double aSinceTime,
377 : JSContext* aContext,
378 : char* aSavedStreamedSamples,
379 : char* aSavedStreamedMarkers,
380 : UniqueStacks& aUniqueStacks);
381 :
382 : #endif // ThreadInfo_h
|