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/. */
6 #ifndef BASEPROFILEJSONWRITER_H
7 #define BASEPROFILEJSONWRITER_H
9 #include "mozilla/HashFunctions.h"
10 #include "mozilla/HashTable.h"
11 #include "mozilla/JSONWriter.h"
12 #include "mozilla/TimeStamp.h"
13 #include "mozilla/UniquePtr.h"
17 #include <string_view>
20 namespace baseprofiler
{
22 class SpliceableJSONWriter
;
24 // On average, profile JSONs are large enough such that we want to avoid
25 // reallocating its buffer when expanding. Additionally, the contents of the
26 // profile are not accessed until the profile is entirely written. For these
27 // reasons we use a chunked writer that keeps an array of chunks, which is
28 // concatenated together after writing is finished.
29 class ChunkedJSONWriteFunc final
: public JSONWriteFunc
{
31 friend class SpliceableJSONWriter
;
33 ChunkedJSONWriteFunc() : mChunkPtr
{nullptr}, mChunkEnd
{nullptr} {
34 AllocChunk(kChunkSize
);
37 bool IsEmpty() const {
38 MOZ_ASSERT_IF(!mChunkPtr
, !mChunkEnd
&& mChunkList
.length() == 0 &&
39 mChunkLengths
.length() == 0);
43 void Write(const Span
<const char>& aStr
) override
{
44 MOZ_ASSERT(mChunkPtr
>= mChunkList
.back().get() && mChunkPtr
<= mChunkEnd
);
45 MOZ_ASSERT(mChunkEnd
>= mChunkList
.back().get() + mChunkLengths
.back());
46 MOZ_ASSERT(*mChunkPtr
== '\0');
48 // Most strings to be written are small, but subprocess profiles (e.g.,
49 // from the content process in e10s) may be huge. If the string is larger
50 // than a chunk, allocate its own chunk.
52 if (aStr
.size() >= kChunkSize
) {
53 AllocChunk(aStr
.size() + 1);
54 newPtr
= mChunkPtr
+ aStr
.size();
56 newPtr
= mChunkPtr
+ aStr
.size();
57 if (newPtr
>= mChunkEnd
) {
58 AllocChunk(kChunkSize
);
59 newPtr
= mChunkPtr
+ aStr
.size();
63 memcpy(mChunkPtr
, aStr
.data(), aStr
.size());
66 mChunkLengths
.back() += aStr
.size();
68 void CopyDataIntoLazilyAllocatedBuffer(
69 const std::function
<char*(size_t)>& aAllocator
) const {
70 // Request a buffer for the full content plus a null terminator.
71 MOZ_ASSERT(mChunkLengths
.length() == mChunkList
.length());
73 for (size_t i
= 0; i
< mChunkLengths
.length(); i
++) {
74 MOZ_ASSERT(strlen(mChunkList
[i
].get()) == mChunkLengths
[i
]);
75 totalLen
+= mChunkLengths
[i
];
77 char* ptr
= aAllocator(totalLen
);
80 // Failed to allocate memory.
84 for (size_t i
= 0; i
< mChunkList
.length(); i
++) {
85 size_t len
= mChunkLengths
[i
];
86 memcpy(ptr
, mChunkList
[i
].get(), len
);
91 UniquePtr
<char[]> CopyData() const {
93 CopyDataIntoLazilyAllocatedBuffer([&](size_t allocationSize
) {
94 c
= MakeUnique
<char[]>(allocationSize
);
99 void Take(ChunkedJSONWriteFunc
&& aOther
) {
100 for (size_t i
= 0; i
< aOther
.mChunkList
.length(); i
++) {
101 MOZ_ALWAYS_TRUE(mChunkLengths
.append(aOther
.mChunkLengths
[i
]));
102 MOZ_ALWAYS_TRUE(mChunkList
.append(std::move(aOther
.mChunkList
[i
])));
104 mChunkPtr
= mChunkList
.back().get() + mChunkLengths
.back();
105 mChunkEnd
= mChunkPtr
;
106 aOther
.mChunkPtr
= nullptr;
107 aOther
.mChunkEnd
= nullptr;
108 aOther
.mChunkList
.clear();
109 aOther
.mChunkLengths
.clear();
113 void AllocChunk(size_t aChunkSize
) {
114 MOZ_ASSERT(mChunkLengths
.length() == mChunkList
.length());
115 UniquePtr
<char[]> newChunk
= MakeUnique
<char[]>(aChunkSize
);
116 mChunkPtr
= newChunk
.get();
117 mChunkEnd
= mChunkPtr
+ aChunkSize
;
119 MOZ_ALWAYS_TRUE(mChunkLengths
.append(0));
120 MOZ_ALWAYS_TRUE(mChunkList
.append(std::move(newChunk
)));
123 static const size_t kChunkSize
= 4096 * 512;
125 // Pointer for writing inside the current chunk.
127 // The current chunk is always at the back of mChunkList, i.e.,
128 // mChunkList.back() <= mChunkPtr <= mChunkEnd.
131 // Pointer to the end of the current chunk.
133 // The current chunk is always at the back of mChunkList, i.e.,
134 // mChunkEnd >= mChunkList.back() + mChunkLengths.back().
137 // List of chunks and their lengths.
139 // For all i, the length of the string in mChunkList[i] is
141 Vector
<UniquePtr
<char[]>> mChunkList
;
142 Vector
<size_t> mChunkLengths
;
145 struct OStreamJSONWriteFunc final
: public JSONWriteFunc
{
146 explicit OStreamJSONWriteFunc(std::ostream
& aStream
) : mStream(aStream
) {}
148 void Write(const Span
<const char>& aStr
) override
{
149 std::string_view
sv(aStr
.data(), aStr
.size());
153 std::ostream
& mStream
;
156 class UniqueJSONStrings
;
158 class SpliceableJSONWriter
: public JSONWriter
{
160 explicit SpliceableJSONWriter(UniquePtr
<JSONWriteFunc
> aWriter
)
161 : JSONWriter(std::move(aWriter
)) {}
163 void StartBareList(CollectionStyle aStyle
= MultiLineStyle
) {
164 StartCollection(scEmptyString
, scEmptyString
, aStyle
);
167 void EndBareList() { EndCollection(scEmptyString
); }
169 // This function must be used to correctly stream timestamps in profiles.
170 // Null timestamps don't output anything.
171 void TimeProperty(const Span
<const char>& aName
, const TimeStamp
& aTime
) {
172 if (!aTime
.IsNull()) {
173 DoubleProperty(aName
,
174 (aTime
- TimeStamp::ProcessCreation()).ToMilliseconds());
178 void NullElements(uint32_t aCount
) {
179 for (uint32_t i
= 0; i
< aCount
; i
++) {
184 void Splice(const Span
<const char>& aStr
) {
186 WriteFunc()->Write(aStr
);
187 mNeedComma
[mDepth
] = true;
190 void Splice(const char* aStr
, size_t aLen
) {
192 WriteFunc()->Write(Span
<const char>(aStr
, aLen
));
193 mNeedComma
[mDepth
] = true;
196 // Splice the given JSON directly in, without quoting.
197 void SplicedJSONProperty(const Span
<const char>& aMaybePropertyName
,
198 const Span
<const char>& aJsonValue
) {
199 Scalar(aMaybePropertyName
, aJsonValue
);
202 void CopyAndSplice(const ChunkedJSONWriteFunc
& aFunc
) {
204 for (size_t i
= 0; i
< aFunc
.mChunkList
.length(); i
++) {
206 Span
<const char>(aFunc
.mChunkList
[i
].get(), aFunc
.mChunkLengths
[i
]));
208 mNeedComma
[mDepth
] = true;
211 // Takes the chunks from aFunc and write them. If move is not possible
212 // (e.g., using OStreamJSONWriteFunc), aFunc's chunks are copied and its
214 virtual void TakeAndSplice(ChunkedJSONWriteFunc
&& aFunc
) {
216 for (size_t i
= 0; i
< aFunc
.mChunkList
.length(); i
++) {
218 Span
<const char>(aFunc
.mChunkList
[i
].get(), aFunc
.mChunkLengths
[i
]));
220 aFunc
.mChunkPtr
= nullptr;
221 aFunc
.mChunkEnd
= nullptr;
222 aFunc
.mChunkList
.clear();
223 aFunc
.mChunkLengths
.clear();
224 mNeedComma
[mDepth
] = true;
227 // Set (or reset) the pointer to a UniqueJSONStrings.
228 void SetUniqueStrings(UniqueJSONStrings
& aUniqueStrings
) {
229 MOZ_RELEASE_ASSERT(!mUniqueStrings
);
230 mUniqueStrings
= &aUniqueStrings
;
233 // Set (or reset) the pointer to a UniqueJSONStrings.
234 void ResetUniqueStrings() {
235 MOZ_RELEASE_ASSERT(mUniqueStrings
);
236 mUniqueStrings
= nullptr;
239 // Add `aStr` to the unique-strings list (if not already there), and write its
240 // index as a named object property.
241 inline void UniqueStringProperty(const Span
<const char>& aName
,
242 const Span
<const char>& aStr
);
244 // Add `aStr` to the unique-strings list (if not already there), and write its
245 // index as an array element.
246 inline void UniqueStringElement(const Span
<const char>& aStr
);
249 UniqueJSONStrings
* mUniqueStrings
= nullptr;
252 class SpliceableChunkedJSONWriter final
: public SpliceableJSONWriter
{
254 explicit SpliceableChunkedJSONWriter()
255 : SpliceableJSONWriter(MakeUnique
<ChunkedJSONWriteFunc
>()) {}
257 // Access the ChunkedJSONWriteFunc as reference-to-const, usually to copy data
259 const ChunkedJSONWriteFunc
& ChunkedWriteFunc() const {
261 // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the
262 // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc*.
263 return *static_cast<const ChunkedJSONWriteFunc
*>(WriteFunc());
266 // Access the ChunkedJSONWriteFunc as rvalue-reference, usually to take its
267 // data out. This writer shouldn't be used anymore after this.
268 ChunkedJSONWriteFunc
&& TakeChunkedWriteFunc() {
273 // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the
274 // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc*.
275 return std::move(*static_cast<ChunkedJSONWriteFunc
*>(WriteFunc()));
278 // Adopts the chunks from aFunc without copying.
279 void TakeAndSplice(ChunkedJSONWriteFunc
&& aFunc
) override
{
282 // The WriteFunc was non-fallibly allocated as a ChunkedJSONWriteFunc in the
283 // only constructor above, so it's safe to cast to ChunkedJSONWriteFunc*.
284 static_cast<ChunkedJSONWriteFunc
*>(WriteFunc())->Take(std::move(aFunc
));
285 mNeedComma
[mDepth
] = true;
294 class JSONSchemaWriter
{
299 explicit JSONSchemaWriter(JSONWriter
& aWriter
) : mWriter(aWriter
), mIndex(0) {
300 aWriter
.StartObjectProperty("schema",
301 SpliceableJSONWriter::SingleLineStyle
);
304 void WriteField(const Span
<const char>& aName
) {
305 mWriter
.IntProperty(aName
, mIndex
++);
308 template <size_t Np1
>
309 void WriteField(const char (&aName
)[Np1
]) {
310 WriteField(Span
<const char>(aName
, Np1
- 1));
313 ~JSONSchemaWriter() { mWriter
.EndObject(); }
316 // This class helps create an indexed list of unique strings, and inserts the
317 // index as a JSON value. The collected list of unique strings can later be
318 // inserted as a JSON array.
319 // This can be useful for elements/properties with many repeated strings.
321 // With only JSONWriter w,
322 // `w.WriteElement("a"); w.WriteElement("b"); w.WriteElement("a");`
323 // when done inside a JSON array, will generate:
326 // With UniqueStrings u,
327 // `u.WriteElement(w, "a"); u.WriteElement(w, "b"); u.WriteElement(w, "a");`
328 // when done inside a JSON array, will generate:
330 // and later, `u.SpliceStringTableElements(w)` (inside a JSON array), will
331 // output the corresponding indexed list of unique strings:
333 class UniqueJSONStrings
{
335 // Start an empty list of unique strings.
336 MFBT_API
explicit UniqueJSONStrings(
337 JSONWriter::CollectionStyle aStyle
= JSONWriter::MultiLineStyle
);
339 // Start with a copy of the strings from another list.
340 MFBT_API
explicit UniqueJSONStrings(
341 const UniqueJSONStrings
& aOther
,
342 JSONWriter::CollectionStyle aStyle
= JSONWriter::MultiLineStyle
);
344 MFBT_API
~UniqueJSONStrings();
346 // Add `aStr` to the list (if not already there), and write its index as a
347 // named object property.
348 void WriteProperty(JSONWriter
& aWriter
, const Span
<const char>& aName
,
349 const Span
<const char>& aStr
) {
350 aWriter
.IntProperty(aName
, GetOrAddIndex(aStr
));
353 // Add `aStr` to the list (if not already there), and write its index as an
355 void WriteElement(JSONWriter
& aWriter
, const Span
<const char>& aStr
) {
356 aWriter
.IntElement(GetOrAddIndex(aStr
));
359 // Splice all collected unique strings into an array. This should only be done
360 // once, and then this UniqueStrings shouldn't be used anymore.
361 MFBT_API
void SpliceStringTableElements(SpliceableJSONWriter
& aWriter
);
364 // If `aStr` is already listed, return its index.
365 // Otherwise add it to the list and return the new index.
366 MFBT_API
uint32_t GetOrAddIndex(const Span
<const char>& aStr
);
368 SpliceableChunkedJSONWriter mStringTableWriter
;
369 HashMap
<HashNumber
, uint32_t> mStringHashToIndexMap
;
372 void SpliceableJSONWriter::UniqueStringProperty(const Span
<const char>& aName
,
373 const Span
<const char>& aStr
) {
374 MOZ_RELEASE_ASSERT(mUniqueStrings
);
375 mUniqueStrings
->WriteProperty(*this, aName
, aStr
);
378 // Add `aStr` to the list (if not already there), and write its index as an
380 void SpliceableJSONWriter::UniqueStringElement(const Span
<const char>& aStr
) {
381 MOZ_RELEASE_ASSERT(mUniqueStrings
);
382 mUniqueStrings
->WriteElement(*this, aStr
);
385 } // namespace baseprofiler
386 } // namespace mozilla
388 #endif // BASEPROFILEJSONWRITER_H