summaryrefslogtreecommitdiffstats
path: root/tools/profiler/core/ProfileBuffer.h
blob: 5da34909cce1498231d2df29becbf5032345bf7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef MOZ_PROFILE_BUFFER_H
#define MOZ_PROFILE_BUFFER_H

#include "GeckoProfiler.h"
#include "ProfileBufferEntry.h"

#include "mozilla/Maybe.h"
#include "mozilla/PowerOfTwo.h"
#include "mozilla/ProfileBufferChunkManagerSingle.h"
#include "mozilla/ProfileChunkedBuffer.h"

class ProcessStreamingContext;
class RunningTimes;

// Class storing most profiling data in a ProfileChunkedBuffer.
//
// This class is used as a queue of entries which, after construction, never
// allocates. This makes it safe to use in the profiler's "critical section".
class ProfileBuffer final {
 public:
  // ProfileBuffer constructor
  // @param aBuffer The in-session ProfileChunkedBuffer to use as buffer
  // manager.
  explicit ProfileBuffer(mozilla::ProfileChunkedBuffer& aBuffer);

  mozilla::ProfileChunkedBuffer& UnderlyingChunkedBuffer() const {
    return mEntries;
  }

  bool IsThreadSafe() const { return mEntries.IsThreadSafe(); }

  // Add |aEntry| to the buffer, ignoring what kind of entry it is.
  uint64_t AddEntry(const ProfileBufferEntry& aEntry);

  // Add to the buffer a sample start (ThreadId) entry for aThreadId.
  // Returns the position of the entry.
  uint64_t AddThreadIdEntry(ProfilerThreadId aThreadId);

  void CollectCodeLocation(
      const char* aLabel, const char* aStr, uint32_t aFrameFlags,
      uint64_t aInnerWindowID, const mozilla::Maybe<uint32_t>& aLineNumber,
      const mozilla::Maybe<uint32_t>& aColumnNumber,
      const mozilla::Maybe<JS::ProfilingCategoryPair>& aCategoryPair);

  // Maximum size of a frameKey string that we'll handle.
  static const size_t kMaxFrameKeyLength = 512;

  // Add JIT frame information to aJITFrameInfo for any JitReturnAddr entries
  // that are currently in the buffer at or after aRangeStart, in samples
  // for the given thread.
  void AddJITInfoForRange(uint64_t aRangeStart, ProfilerThreadId aThreadId,
                          JSContext* aContext, JITFrameInfo& aJITFrameInfo,
                          mozilla::ProgressLogger aProgressLogger) const;

  // Stream JSON for samples in the buffer to aWriter, using the supplied
  // UniqueStacks object.
  // Only streams samples for the given thread ID and which were taken at or
  // after aSinceTime. If ID is 0, ignore the stored thread ID; this should only
  // be used when the buffer contains only one sample.
  // aUniqueStacks needs to contain information about any JIT frames that we
  // might encounter in the buffer, before this method is called. In other
  // words, you need to have called AddJITInfoForRange for every range that
  // might contain JIT frame information before calling this method.
  // Return the thread ID of the streamed sample(s), or 0.
  ProfilerThreadId StreamSamplesToJSON(
      SpliceableJSONWriter& aWriter, ProfilerThreadId aThreadId,
      double aSinceTime, UniqueStacks& aUniqueStacks,
      mozilla::ProgressLogger aProgressLogger) const;

  void StreamMarkersToJSON(SpliceableJSONWriter& aWriter,
                           ProfilerThreadId aThreadId,
                           const mozilla::TimeStamp& aProcessStartTime,
                           double aSinceTime, UniqueStacks& aUniqueStacks,
                           mozilla::ProgressLogger aProgressLogger) const;

  // Stream samples and markers from all threads that `aProcessStreamingContext`
  // accepts.
  void StreamSamplesAndMarkersToJSON(
      ProcessStreamingContext& aProcessStreamingContext,
      mozilla::ProgressLogger aProgressLogger) const;

  void StreamPausedRangesToJSON(SpliceableJSONWriter& aWriter,
                                double aSinceTime,
                                mozilla::ProgressLogger aProgressLogger) const;
  void StreamProfilerOverheadToJSON(
      SpliceableJSONWriter& aWriter,
      const mozilla::TimeStamp& aProcessStartTime, double aSinceTime,
      mozilla::ProgressLogger aProgressLogger) const;
  void StreamCountersToJSON(SpliceableJSONWriter& aWriter,
                            const mozilla::TimeStamp& aProcessStartTime,
                            double aSinceTime,
                            mozilla::ProgressLogger aProgressLogger) const;

  // Find (via |aLastSample|) the most recent sample for the thread denoted by
  // |aThreadId| and clone it, patching in the current time as appropriate.
  // Mutate |aLastSample| to point to the newly inserted sample.
  // Returns whether duplication was successful.
  bool DuplicateLastSample(ProfilerThreadId aThreadId, double aSampleTimeMs,
                           mozilla::Maybe<uint64_t>& aLastSample,
                           const RunningTimes& aRunningTimes);

  void DiscardSamplesBeforeTime(double aTime);

  // Read an entry in the buffer.
  ProfileBufferEntry GetEntry(uint64_t aPosition) const {
    return mEntries.ReadAt(
        mozilla::ProfileBufferBlockIndex::CreateFromProfileBufferIndex(
            aPosition),
        [&](mozilla::Maybe<mozilla::ProfileBufferEntryReader>&& aMER) {
          ProfileBufferEntry entry;
          if (aMER.isSome()) {
            if (aMER->CurrentBlockIndex().ConvertToProfileBufferIndex() ==
                aPosition) {
              // If we're here, it means `aPosition` pointed at a valid block.
              MOZ_RELEASE_ASSERT(aMER->RemainingBytes() <= sizeof(entry));
              aMER->ReadBytes(&entry, aMER->RemainingBytes());
            } else {
              // EntryReader at the wrong position, pretend to have read
              // everything.
              aMER->SetRemainingBytes(0);
            }
          }
          return entry;
        });
  }

  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;

  void CollectOverheadStats(double aSamplingTimeMs,
                            mozilla::TimeDuration aLocking,
                            mozilla::TimeDuration aCleaning,
                            mozilla::TimeDuration aCounters,
                            mozilla::TimeDuration aThreads);

  ProfilerBufferInfo GetProfilerBufferInfo() const;

 private:
  // Add |aEntry| to the provided ProfileChunkedBuffer.
  // `static` because it may be used to add an entry to a `ProfileChunkedBuffer`
  // that is not attached to a `ProfileBuffer`.
  static mozilla::ProfileBufferBlockIndex AddEntry(
      mozilla::ProfileChunkedBuffer& aProfileChunkedBuffer,
      const ProfileBufferEntry& aEntry);

  // Add a sample start (ThreadId) entry for aThreadId to the provided
  // ProfileChunkedBuffer. Returns the position of the entry.
  // `static` because it may be used to add an entry to a `ProfileChunkedBuffer`
  // that is not attached to a `ProfileBuffer`.
  static mozilla::ProfileBufferBlockIndex AddThreadIdEntry(
      mozilla::ProfileChunkedBuffer& aProfileChunkedBuffer,
      ProfilerThreadId aThreadId);

  // The storage in which this ProfileBuffer stores its entries.
  mozilla::ProfileChunkedBuffer& mEntries;

 public:
  // `BufferRangeStart()` and `BufferRangeEnd()` return `uint64_t` values
  // corresponding to the first entry and past the last entry stored in
  // `mEntries`.
  //
  // The returned values are not guaranteed to be stable, because other threads
  // may also be accessing the buffer concurrently. But they will always
  // increase, and can therefore give an indication of how far these values have
  // *at least* reached. In particular:
  // - Entries whose index is strictly less that `BufferRangeStart()` have been
  //   discarded by now, so any related data may also be safely discarded.
  // - It is safe to try and read entries at any index strictly less than
  //   `BufferRangeEnd()` -- but note that these reads may fail by the time you
  //   request them, as old entries get overwritten by new ones.
  uint64_t BufferRangeStart() const { return mEntries.GetState().mRangeStart; }
  uint64_t BufferRangeEnd() const { return mEntries.GetState().mRangeEnd; }

 private:
  // Single pre-allocated chunk (to avoid spurious mallocs), used when:
  // - Duplicating sleeping stacks (hence scExpectedMaximumStackSize).
  // - Adding JIT info.
  // - Streaming stacks to JSON.
  // Mutable because it's accessed from non-multithreaded const methods.
  mutable mozilla::Maybe<mozilla::ProfileBufferChunkManagerSingle>
      mMaybeWorkerChunkManager;
  mozilla::ProfileBufferChunkManagerSingle& WorkerChunkManager() const {
    if (mMaybeWorkerChunkManager.isNothing()) {
      // Only actually allocate it on first use. (Some ProfileBuffers are
      // temporary and don't actually need this.)
      mMaybeWorkerChunkManager.emplace(
          mozilla::ProfileBufferChunk::SizeofChunkMetadata() +
          mozilla::ProfileBufferChunkManager::scExpectedMaximumStackSize);
    }
    return *mMaybeWorkerChunkManager;
  }

  // GetStreamingParametersForThreadCallback:
  //   (ProfilerThreadId) -> Maybe<StreamingParametersForThread>
  template <typename GetStreamingParametersForThreadCallback>
  ProfilerThreadId DoStreamSamplesAndMarkersToJSON(
      mozilla::FailureLatch& aFailureLatch,
      GetStreamingParametersForThreadCallback&&
          aGetStreamingParametersForThreadCallback,
      double aSinceTime, ProcessStreamingContext* aStreamingContextForMarkers,
      mozilla::ProgressLogger aProgressLogger) const;

  double mFirstSamplingTimeUs = 0.0;
  double mLastSamplingTimeUs = 0.0;
  ProfilerStats mIntervalsUs;
  ProfilerStats mOverheadsUs;
  ProfilerStats mLockingsUs;
  ProfilerStats mCleaningsUs;
  ProfilerStats mCountersUs;
  ProfilerStats mThreadsUs;
};

/**
 * Helper type used to implement ProfilerStackCollector. This type is used as
 * the collector for MergeStacks by ProfileBuffer. It holds a reference to the
 * buffer, as well as additional feature flags which are needed to control the
 * data collection strategy
 */
class ProfileBufferCollector final : public ProfilerStackCollector {
 public:
  ProfileBufferCollector(ProfileBuffer& aBuf, uint64_t aSamplePos,
                         uint64_t aBufferRangeStart)
      : mBuf(aBuf),
        mSamplePositionInBuffer(aSamplePos),
        mBufferRangeStart(aBufferRangeStart) {
    MOZ_ASSERT(
        mSamplePositionInBuffer >= mBufferRangeStart,
        "The sample position should always be after the buffer range start");
  }

  // Position at which the sample starts in the profiler buffer (which may be
  // different from the buffer in which the sample data is collected here).
  mozilla::Maybe<uint64_t> SamplePositionInBuffer() override {
    return mozilla::Some(mSamplePositionInBuffer);
  }

  // Profiler buffer's range start (which may be different from the buffer in
  // which the sample data is collected here).
  mozilla::Maybe<uint64_t> BufferRangeStart() override {
    return mozilla::Some(mBufferRangeStart);
  }

  virtual void CollectNativeLeafAddr(void* aAddr) override;
  virtual void CollectJitReturnAddr(void* aAddr) override;
  virtual void CollectWasmFrame(const char* aLabel) override;
  virtual void CollectProfilingStackFrame(
      const js::ProfilingStackFrame& aFrame) override;

 private:
  ProfileBuffer& mBuf;
  uint64_t mSamplePositionInBuffer;
  uint64_t mBufferRangeStart;
};

#endif