Bug 1646266 - ProfilerString{,8,16}View - r=gregtatum

These string views are similar to `std::string_view`, but they are optimized to be serialized in the profiler buffer, and later deserialized and streamed to JSON.
They accept literal strings, and keep them as unowned raw pointers and sizes.
They also accept any substring reference, assuming that they will only be used as parameters during function calls, and therefore the dependent string will live during that call where these `StringView`'s are used.

Internally, they also allow optional string ownership, which is only used during deserialization and streaming.
This is hidden, so that users are not tempted to use potentially expensive string allocations during profiling; it's only used *after* profiling, so it's less of an impact to allocate strings then. (But it could still be optimized later on, as part of bug 1577656.)

Differential Revision: https://phabricator.services.mozilla.com/D87242
This commit is contained in:
Gerald Squelart 2020-08-31 23:28:22 +00:00
Родитель 3cb4ee0995
Коммит 4e9b0b4d1f
4 изменённых файлов: 488 добавлений и 0 удалений

Просмотреть файл

@ -15,6 +15,107 @@
#ifdef MOZ_GECKO_PROFILER
// ~~ HERE BE DRAGONS ~~
//
// Everything below is internal implementation detail, you shouldn't need to
// look at it unless working on the profiler code.
# include <limits>
namespace mozilla {
// ----------------------------------------------------------------------------
// Serializer, Deserializer: ProfilerStringView<CHAR>
// The serialization starts with a ULEB128 number that encodes both whether the
// ProfilerStringView is literal (Least Significant Bit = 0) or not (LSB = 1),
// plus the string length (excluding null terminator) in bytes, shifted left by
// 1 bit. Following that number:
// - If literal, the string pointer value.
// - If non-literal, the contents as bytes (excluding null terminator if any).
template <typename CHAR>
struct ProfileBufferEntryWriter::Serializer<ProfilerStringView<CHAR>> {
static Length Bytes(const ProfilerStringView<CHAR>& aString) {
MOZ_RELEASE_ASSERT(
aString.Length() < std::numeric_limits<Length>::max() / 2,
"Double the string length doesn't fit in Length type");
const Length stringLength = static_cast<Length>(aString.Length());
if (aString.IsLiteral()) {
// Literal -> Length shifted left and LSB=0, then pointer.
return ULEB128Size(stringLength << 1 | 0u) +
static_cast<ProfileChunkedBuffer::Length>(sizeof(const CHAR*));
}
// Non-literal -> Length shifted left and LSB=1, then string size in bytes.
return ULEB128Size((stringLength << 1) | 1u) + stringLength * sizeof(CHAR);
}
static void Write(ProfileBufferEntryWriter& aEW,
const ProfilerStringView<CHAR>& aString) {
MOZ_RELEASE_ASSERT(
aString.Length() < std::numeric_limits<Length>::max() / 2,
"Double the string length doesn't fit in Length type");
const Length stringLength = static_cast<Length>(aString.Length());
if (aString.IsLiteral()) {
// Literal -> Length shifted left and LSB=0, then pointer.
aEW.WriteULEB128(stringLength << 1 | 0u);
aEW.WriteObject(WrapProfileBufferRawPointer(aString.Data()));
return;
}
// Non-literal -> Length shifted left and LSB=1, then string size in bytes.
aEW.WriteULEB128(stringLength << 1 | 1u);
aEW.WriteBytes(aString.Data(), stringLength * sizeof(CHAR));
}
};
template <typename CHAR>
struct ProfileBufferEntryReader::Deserializer<ProfilerStringView<CHAR>> {
static void ReadInto(ProfileBufferEntryReader& aER,
ProfilerStringView<CHAR>& aString) {
const Length lengthAndIsLiteral = aER.ReadULEB128<Length>();
const Length stringLength = lengthAndIsLiteral >> 1;
if ((lengthAndIsLiteral & 1u) == 0u) {
// LSB==0 -> Literal string, read the string pointer.
aString.mStringView = std::basic_string_view<CHAR>(
aER.ReadObject<const CHAR*>(), stringLength);
aString.mOwnership = ProfilerStringView<CHAR>::Ownership::Literal;
return;
}
// LSB==1 -> Not a literal string, allocate a buffer to store the string
// (plus terminal, for safety), and give it to the ProfilerStringView; Note
// that this is a secret use of ProfilerStringView, which is intended to
// only be used between deserialization and JSON streaming.
CHAR* buffer = new CHAR[stringLength + 1];
aER.ReadBytes(buffer, stringLength * sizeof(CHAR));
buffer[stringLength] = CHAR(0);
aString.mStringView = std::basic_string_view<CHAR>(buffer, stringLength);
aString.mOwnership =
ProfilerStringView<CHAR>::Ownership::OwnedThroughStringView;
}
static ProfilerStringView<CHAR> Read(ProfileBufferEntryReader& aER) {
const Length lengthAndIsLiteral = aER.ReadULEB128<Length>();
const Length stringLength = lengthAndIsLiteral >> 1;
if ((lengthAndIsLiteral & 1u) == 0u) {
// LSB==0 -> Literal string, read the string pointer.
return ProfilerStringView<CHAR>(
aER.ReadObject<const CHAR*>(), stringLength,
ProfilerStringView<CHAR>::Ownership::Literal);
}
// LSB==1 -> Not a literal string, allocate a buffer to store the string
// (plus terminal, for safety), and give it to the ProfilerStringView; Note
// that this is a secret use of ProfilerStringView, which is intended to
// only be used between deserialization and JSON streaming.
CHAR* buffer = new CHAR[stringLength + 1];
aER.ReadBytes(buffer, stringLength * sizeof(CHAR));
buffer[stringLength] = CHAR(0);
return ProfilerStringView<CHAR>(
buffer, stringLength,
ProfilerStringView<CHAR>::Ownership::OwnedThroughStringView);
}
};
} // namespace mozilla
#endif // MOZ_GECKO_PROFILER
#endif // BaseProfilerMarkersDetail_h

Просмотреть файл

@ -4,11 +4,203 @@
* 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/. */
// This header contains basic definitions required to create marker types, and
// to add markers to the profiler buffers.
//
// In most cases, #include "mozilla/BaseProfilerMarkers.h" instead, or
// #include "mozilla/BaseProfilerMarkerTypes.h" for common marker types.
#ifndef BaseProfilerMarkersPrerequisites_h
#define BaseProfilerMarkersPrerequisites_h
#ifdef MOZ_GECKO_PROFILER
# include "mozilla/ProfileChunkedBuffer.h"
# include <string_view>
# include <string>
# include <type_traits>
# include <utility>
namespace mozilla {
// Return a NotNull<const CHAR*> pointing at the literal empty string `""`.
template <typename CHAR>
constexpr const CHAR* LiteralEmptyStringPointer() {
static_assert(std::is_same_v<CHAR, char> || std::is_same_v<CHAR, char16_t>,
"Only char and char16_t are supported in Firefox");
if constexpr (std::is_same_v<CHAR, char>) {
return "";
}
if constexpr (std::is_same_v<CHAR, char16_t>) {
return u"";
}
}
// Return a string_view<CHAR> pointing at the literal empty string.
template <typename CHAR>
constexpr std::basic_string_view<CHAR> LiteralEmptyStringView() {
static_assert(std::is_same_v<CHAR, char> || std::is_same_v<CHAR, char16_t>,
"Only char and char16_t are supported in Firefox");
// Use `operator""sv()` from <string_view>.
using namespace std::literals::string_view_literals;
if constexpr (std::is_same_v<CHAR, char>) {
return ""sv;
}
if constexpr (std::is_same_v<CHAR, char16_t>) {
return u""sv;
}
}
// General string view, optimized for short on-stack life before serialization,
// and between deserialization and JSON-streaming.
template <typename CHAR>
class MOZ_STACK_CLASS ProfilerStringView {
public:
// Default constructor points at "" (literal empty string).
constexpr ProfilerStringView() = default;
// Don't allow copy.
ProfilerStringView(const ProfilerStringView&) = delete;
ProfilerStringView& operator=(const ProfilerStringView&) = delete;
// Allow move. For consistency the moved-from string is always reset to "".
constexpr ProfilerStringView(ProfilerStringView&& aOther)
: mStringView(std::move(aOther.mStringView)),
mOwnership(aOther.mOwnership) {
if (mOwnership == Ownership::OwnedThroughStringView) {
// We now own the buffer, make the other point at the literal "".
aOther.mStringView = LiteralEmptyStringView<CHAR>();
aOther.mOwnership = Ownership::Literal;
}
}
constexpr ProfilerStringView& operator=(ProfilerStringView&& aOther) {
mStringView = std::move(aOther.mStringView);
mOwnership = aOther.mOwnership;
if (mOwnership == Ownership::OwnedThroughStringView) {
// We now own the buffer, make the other point at the literal "".
aOther.mStringView = LiteralEmptyStringView<CHAR>();
aOther.mOwnership = Ownership::Literal;
}
return *this;
}
~ProfilerStringView() {
if (MOZ_UNLIKELY(mOwnership == Ownership::OwnedThroughStringView)) {
// We own the buffer pointed at by mStringView, destroy it.
// This is only used between deserialization and streaming.
delete mStringView.data();
}
}
// Implicit construction from nullptr, points at "" (literal empty string).
constexpr MOZ_IMPLICIT ProfilerStringView(decltype(nullptr)) {}
// Implicit constructor from a literal string.
template <size_t Np1>
constexpr MOZ_IMPLICIT ProfilerStringView(const CHAR (&aLiteralString)[Np1])
: ProfilerStringView(aLiteralString, Np1 - 1, Ownership::Literal) {}
// Constructor from a non-literal string.
constexpr ProfilerStringView(const CHAR* aString, size_t aLength)
: ProfilerStringView(aString, aLength, Ownership::Reference) {}
// Implicit constructor from a string_view.
constexpr MOZ_IMPLICIT ProfilerStringView(
const std::basic_string_view<CHAR>& aStringView)
: ProfilerStringView(aStringView.data(), aStringView.length(),
Ownership::Reference) {}
// Implicit constructor from an expiring string_view. We assume that the
// pointed-at string will outlive this ProfilerStringView.
constexpr MOZ_IMPLICIT ProfilerStringView(
std::basic_string_view<CHAR>&& aStringView)
: ProfilerStringView(aStringView.data(), aStringView.length(),
Ownership::Reference) {}
// Implicit constructor from std::string.
constexpr MOZ_IMPLICIT ProfilerStringView(const std::string& aString)
: ProfilerStringView(aString.data(), aString.length(),
Ownership::Reference) {}
// Construction from a raw pointer to a null-terminated string.
// This is a named class-static function to make it more obvious where work is
// being done (to determine the string length), and encourage users to instead
// provide a length, if already known.
// TODO: Find callers and convert them to constructor instead if possible.
static constexpr ProfilerStringView WrapNullTerminatedString(
const CHAR* aString) {
return ProfilerStringView(
aString, aString ? std::char_traits<char>::length(aString) : 0,
Ownership::Reference);
}
// Implicit constructor for an object with member functions `Data()`
// `Length()`, and `IsLiteral()`, common in xpcom strings.
template <
typename String,
typename DataReturnType = decltype(std::declval<const String>().Data()),
typename LengthReturnType =
decltype(std::declval<const String>().Length()),
typename IsLiteralReturnType =
decltype(std::declval<const String>().IsLiteral()),
typename =
std::enable_if_t<std::is_convertible_v<DataReturnType, const CHAR*> &&
std::is_integral_v<LengthReturnType> &&
std::is_same_v<IsLiteralReturnType, bool>>>
constexpr MOZ_IMPLICIT ProfilerStringView(const String& aString)
: ProfilerStringView(
static_cast<const CHAR*>(aString.Data()), aString.Length(),
aString.IsLiteral() ? Ownership::Literal : Ownership::Reference) {}
[[nodiscard]] constexpr const std::basic_string_view<CHAR>& StringView()
const {
return mStringView;
}
[[nodiscard]] constexpr const CHAR* Data() const {
return mStringView.data();
}
[[nodiscard]] constexpr size_t Length() const { return mStringView.length(); }
[[nodiscard]] constexpr bool IsLiteral() const {
return mOwnership == Ownership::Literal;
}
[[nodiscard]] constexpr bool IsReference() const {
return mOwnership == Ownership::Reference;
}
// No `IsOwned...()` because it's a secret, only used internally!
[[nodiscard]] std::basic_string<CHAR> String() const {
return std::basic_string<CHAR>(mStringView);
}
private:
enum class Ownership { Literal, Reference, OwnedThroughStringView };
// Allow deserializer to store anything here.
friend ProfileBufferEntryReader::Deserializer<ProfilerStringView>;
constexpr ProfilerStringView(const CHAR* aString, size_t aLength,
Ownership aOwnership)
: mStringView(aString ? std::basic_string_view<CHAR>(aString, aLength)
: LiteralEmptyStringView<CHAR>()),
mOwnership(aString ? aOwnership : Ownership::Literal) {}
// String view to an outside string (literal or reference).
// We may actually own the pointed-at buffer, but it is only used internally
// between deserialization and JSON streaming.
std::basic_string_view<CHAR> mStringView = LiteralEmptyStringView<CHAR>();
Ownership mOwnership = Ownership::Literal;
};
using ProfilerString8View = ProfilerStringView<char>;
using ProfilerString16View = ProfilerStringView<char16_t>;
} // namespace mozilla
#endif // MOZ_GECKO_PROFILER
#endif // BaseProfilerMarkersPrerequisites_h

Просмотреть файл

@ -2983,6 +2983,193 @@ void TestBlocksRingBufferSerialization() {
printf("TestBlocksRingBufferSerialization done\n");
}
void TestLiteralEmptyStringView() {
printf("TestLiteralEmptyStringView...\n");
static_assert(mozilla::LiteralEmptyStringView<char>() ==
std::string_view(""));
static_assert(!!mozilla::LiteralEmptyStringView<char>().data());
static_assert(mozilla::LiteralEmptyStringView<char>().length() == 0);
static_assert(mozilla::LiteralEmptyStringView<char16_t>() ==
std::basic_string_view<char16_t>(u""));
static_assert(!!mozilla::LiteralEmptyStringView<char16_t>().data());
static_assert(mozilla::LiteralEmptyStringView<char16_t>().length() == 0);
printf("TestLiteralEmptyStringView done\n");
}
void TestProfilerStringView() {
printf("TestProfilerStringView...\n");
// Used to verify implicit constructions, as this will normally be used in
// function parameters.
auto BS8V = [](mozilla::ProfilerString8View&& aBS8V) {
return std::move(aBS8V);
};
// Literal empty string.
MOZ_RELEASE_ASSERT(BS8V("").Data() == "");
MOZ_RELEASE_ASSERT(BS8V("").Length() == 0);
MOZ_RELEASE_ASSERT(BS8V("").IsLiteral());
MOZ_RELEASE_ASSERT(!BS8V("").IsReference());
// Literal non-empty string.
MOZ_RELEASE_ASSERT(BS8V("hi").Data() == "hi");
MOZ_RELEASE_ASSERT(BS8V("hi").Length() == 2);
MOZ_RELEASE_ASSERT(BS8V("hi").IsLiteral());
MOZ_RELEASE_ASSERT(!BS8V("hi").IsReference());
// std::string_view to a literal empty string.
MOZ_RELEASE_ASSERT(BS8V(std::string_view("")).Data() == "");
MOZ_RELEASE_ASSERT(BS8V(std::string_view("")).Length() == 0);
MOZ_RELEASE_ASSERT(!BS8V(std::string_view("")).IsLiteral());
MOZ_RELEASE_ASSERT(BS8V(std::string_view("")).IsReference());
// std::string_view to a literal non-empty string.
MOZ_RELEASE_ASSERT(BS8V(std::string_view("hi")).Data() == "hi");
MOZ_RELEASE_ASSERT(BS8V(std::string_view("hi")).Length() == 2);
MOZ_RELEASE_ASSERT(!BS8V(std::string_view("hi")).IsLiteral());
MOZ_RELEASE_ASSERT(BS8V(std::string_view("hi")).IsReference());
// Default std::string_view points at nullptr, ProfilerStringView converts it
// to the literal empty string.
MOZ_RELEASE_ASSERT(!std::string_view().data());
MOZ_RELEASE_ASSERT(BS8V(std::string_view()).Data() == "");
MOZ_RELEASE_ASSERT(BS8V(std::string_view()).Length() == 0);
MOZ_RELEASE_ASSERT(BS8V(std::string_view()).IsLiteral());
MOZ_RELEASE_ASSERT(!BS8V(std::string_view()).IsReference());
// std::string to a literal empty string.
MOZ_RELEASE_ASSERT(BS8V(std::string("")).Data());
MOZ_RELEASE_ASSERT(*BS8V(std::string("")).Data() == '\0');
MOZ_RELEASE_ASSERT(BS8V(std::string("")).Length() == 0);
MOZ_RELEASE_ASSERT(!BS8V(std::string("")).IsLiteral());
MOZ_RELEASE_ASSERT(BS8V(std::string("")).IsReference());
// std::string to a literal non-empty string.
MOZ_RELEASE_ASSERT(BS8V(std::string("hi")).Data());
MOZ_RELEASE_ASSERT(BS8V(std::string("hi")).Data()[0] == 'h');
MOZ_RELEASE_ASSERT(BS8V(std::string("hi")).Data()[1] == 'i');
MOZ_RELEASE_ASSERT(BS8V(std::string("hi")).Data()[2] == '\0');
MOZ_RELEASE_ASSERT(BS8V(std::string("hi")).Length() == 2);
MOZ_RELEASE_ASSERT(!BS8V(std::string("hi")).IsLiteral());
MOZ_RELEASE_ASSERT(BS8V(std::string("hi")).IsReference());
// Default std::string_view contains an empty null-terminated string.
MOZ_RELEASE_ASSERT(std::string().data());
MOZ_RELEASE_ASSERT(BS8V(std::string()).Data());
MOZ_RELEASE_ASSERT(*BS8V(std::string()).Data() == '\0');
MOZ_RELEASE_ASSERT(BS8V(std::string()).Length() == 0);
MOZ_RELEASE_ASSERT(!BS8V(std::string()).IsLiteral());
MOZ_RELEASE_ASSERT(BS8V(std::string()).IsReference());
class FakeNsCString {
public:
FakeNsCString(const char* aData, size_t aLength, bool aIsLiteral)
: mData(aData), mLength(aLength), mIsLiteral(aIsLiteral) {}
const char* Data() const { return mData; }
size_t Length() const { return mLength; }
bool IsLiteral() const { return mIsLiteral; }
private:
const char* mData;
size_t mLength;
bool mIsLiteral;
};
// FakeNsCString to nullptr.
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString(nullptr, 0, true)).Data() == "");
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString(nullptr, 0, true)).Length() == 0);
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString(nullptr, 0, true)).IsLiteral());
MOZ_RELEASE_ASSERT(!BS8V(FakeNsCString(nullptr, 0, true)).IsReference());
// FakeNsCString to a literal empty string.
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("", 0, true)).Data());
MOZ_RELEASE_ASSERT(*BS8V(FakeNsCString("", 0, true)).Data() == '\0');
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("", 0, true)).Length() == 0);
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("", 0, true)).IsLiteral());
MOZ_RELEASE_ASSERT(!BS8V(FakeNsCString("", 0, true)).IsReference());
// FakeNsCString to a literal non-empty string.
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("hi", 2, true)).Data() == "hi");
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("hi", 2, true)).Length() == 2);
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("hi", 2, true)).IsLiteral());
MOZ_RELEASE_ASSERT(!BS8V(FakeNsCString("hi", 2, true)).IsReference());
// FakeNsCString to a non-literal non-empty string.
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("hi", 2, false)).Data() == "hi");
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("hi", 2, false)).Length() == 2);
MOZ_RELEASE_ASSERT(!BS8V(FakeNsCString("hi", 2, false)).IsLiteral());
MOZ_RELEASE_ASSERT(BS8V(FakeNsCString("hi", 2, false)).IsReference());
// Serialization and deserialization (with ownership).
constexpr size_t bufferMaxSize = 1024;
constexpr ProfileChunkedBuffer::Length chunkMinSize = 128;
ProfileBufferChunkManagerWithLocalLimit cm(bufferMaxSize, chunkMinSize);
ProfileChunkedBuffer cb(ProfileChunkedBuffer::ThreadSafety::WithMutex, cm);
// Literal string, serialized as raw pointer.
MOZ_RELEASE_ASSERT(cb.PutObject(BS8V("hi")));
{
unsigned read = 0;
ProfilerString8View outerBS8V;
cb.ReadEach([&](ProfileBufferEntryReader& aER) {
++read;
auto bs8v = aER.ReadObject<ProfilerString8View>();
MOZ_RELEASE_ASSERT(bs8v.Data() == "hi");
MOZ_RELEASE_ASSERT(bs8v.Length() == 2);
MOZ_RELEASE_ASSERT(bs8v.IsLiteral());
MOZ_RELEASE_ASSERT(!bs8v.IsReference());
outerBS8V = std::move(bs8v);
});
MOZ_RELEASE_ASSERT(read == 1);
MOZ_RELEASE_ASSERT(outerBS8V.Data() == "hi");
MOZ_RELEASE_ASSERT(outerBS8V.Length() == 2);
MOZ_RELEASE_ASSERT(outerBS8V.IsLiteral());
MOZ_RELEASE_ASSERT(!outerBS8V.IsReference());
}
cb.Clear();
// Non-literal string, content is serialized.
MOZ_RELEASE_ASSERT(cb.PutObject(BS8V(std::string("hi"))));
MOZ_RELEASE_ASSERT(std::string("hi").data() != "hi");
{
unsigned read = 0;
ProfilerString8View outerBS8V;
cb.ReadEach([&](ProfileBufferEntryReader& aER) {
++read;
auto bs8v = aER.ReadObject<ProfilerString8View>();
MOZ_RELEASE_ASSERT(bs8v.Data());
MOZ_RELEASE_ASSERT(bs8v.Data() != "hi");
MOZ_RELEASE_ASSERT(bs8v.Data()[0] == 'h');
MOZ_RELEASE_ASSERT(bs8v.Data()[1] == 'i');
MOZ_RELEASE_ASSERT(bs8v.Data()[2] == '\0');
MOZ_RELEASE_ASSERT(bs8v.Length() == 2);
// Special ownership case, neither a literal nor a reference!
MOZ_RELEASE_ASSERT(!bs8v.IsLiteral());
MOZ_RELEASE_ASSERT(!bs8v.IsReference());
// Test move of ownership.
outerBS8V = std::move(bs8v);
// NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move)
MOZ_RELEASE_ASSERT(bs8v.Length() == 0);
});
MOZ_RELEASE_ASSERT(read == 1);
MOZ_RELEASE_ASSERT(outerBS8V.Data());
MOZ_RELEASE_ASSERT(outerBS8V.Data() != "hi");
MOZ_RELEASE_ASSERT(outerBS8V.Data()[0] == 'h');
MOZ_RELEASE_ASSERT(outerBS8V.Data()[1] == 'i');
MOZ_RELEASE_ASSERT(outerBS8V.Data()[2] == '\0');
MOZ_RELEASE_ASSERT(outerBS8V.Length() == 2);
MOZ_RELEASE_ASSERT(!outerBS8V.IsLiteral());
MOZ_RELEASE_ASSERT(!outerBS8V.IsReference());
}
printf("TestProfilerStringView done\n");
}
void TestProfilerDependencies() {
TestPowerOfTwoMask();
TestPowerOfTwo();
@ -2999,6 +3186,8 @@ void TestProfilerDependencies() {
TestBlocksRingBufferUnderlyingBufferChanges();
TestBlocksRingBufferThreading();
TestBlocksRingBufferSerialization();
TestLiteralEmptyStringView();
TestProfilerStringView();
}
class BaseTestMarkerPayload : public baseprofiler::ProfilerMarkerPayload {

Просмотреть файл

@ -4,6 +4,12 @@
* 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/. */
// This header contains basic definitions required to create marker types, and
// to add markers to the profiler buffers.
//
// In most cases, #include "mozilla/ProfilerMarkers.h" instead, or
// #include "mozilla/ProfilerMarkerTypes.h" for common marker types.
#ifndef ProfilerMarkersPrerequisites_h
#define ProfilerMarkersPrerequisites_h