mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1067699 (part 2) - Add mfbt/JSONWriter.h and use it for memory reporting. r=froydnj.
--HG-- extra : rebase_source : 7eb21c4c20c0434b3b236d1c53cbef9cb6a4759c
This commit is contained in:
parent
9a8bee0c06
commit
4f913989b7
49
mfbt/JSONWriter.cpp
Normal file
49
mfbt/JSONWriter.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "mozilla/JSONWriter.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace detail {
|
||||
|
||||
// The chars with non-'___' entries in this table are those that can be
|
||||
// represented with a two-char escape sequence. The value is the second char in
|
||||
// the sequence, that which follows the initial backslash.
|
||||
#define ___ 0
|
||||
const char gTwoCharEscapes[256] = {
|
||||
/* 0 1 2 3 4 5 6 7 8 9 */
|
||||
/* 0+ */ ___, ___, ___, ___, ___, ___, ___, ___, 'b', 't',
|
||||
/* 10+ */ 'n', ___, 'f', 'r', ___, ___, ___, ___, ___, ___,
|
||||
/* 20+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 30+ */ ___, ___, ___, ___, '"', ___, ___, ___, ___, ___,
|
||||
/* 40+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 50+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 60+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 70+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 80+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 90+ */ ___, ___,'\\', ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 100+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 110+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 120+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 130+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 140+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 150+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 160+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 170+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 180+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 190+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 200+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 210+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 220+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 230+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 240+ */ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___,
|
||||
/* 250+ */ ___, ___, ___, ___, ___, ___
|
||||
};
|
||||
#undef ___
|
||||
|
||||
} // namespace detail
|
||||
} // namespace mozilla
|
||||
|
420
mfbt/JSONWriter.h
Normal file
420
mfbt/JSONWriter.h
Normal file
@ -0,0 +1,420 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
/* A JSON pretty-printer class. */
|
||||
|
||||
// A typical JSON-writing library requires you to first build up a data
|
||||
// structure that represents a JSON object and then serialize it (to file, or
|
||||
// somewhere else). This approach makes for a clean API, but building the data
|
||||
// structure takes up memory. Sometimes that isn't desirable, such as when the
|
||||
// JSON data is produced for memory reporting.
|
||||
//
|
||||
// The JSONWriter class instead allows JSON data to be written out
|
||||
// incrementally without building up large data structures.
|
||||
//
|
||||
// The API is slightly uglier than you would see in a typical JSON-writing
|
||||
// library, but still fairly easy to use. It's possible to generate invalid
|
||||
// JSON with JSONWriter, but typically the most basic testing will identify any
|
||||
// such problems.
|
||||
//
|
||||
// Similarly, there are no RAII facilities for automatically closing objects
|
||||
// and arrays. These would be nice if you are generating all your code within
|
||||
// nested functions, but in other cases you'd have to maintain an explicit
|
||||
// stack of RAII objects and manually unwind it, which is no better than just
|
||||
// calling "end" functions. Furthermore, the consequences of forgetting to
|
||||
// close an object or array are obvious and, again, will be identified via
|
||||
// basic testing, unlike other cases where RAII is typically used (e.g. smart
|
||||
// pointers) and the consequences of defects are more subtle.
|
||||
//
|
||||
// Importantly, the class does solve the two hard problems of JSON
|
||||
// pretty-printing, which are (a) correctly escaping strings, and (b) adding
|
||||
// appropriate indentation and commas between items.
|
||||
//
|
||||
// Strings used (for property names and string property values) are |const
|
||||
// char*| throughout, and can be ASCII or UTF-8.
|
||||
//
|
||||
// EXAMPLE
|
||||
// -------
|
||||
// Assume that |MyWriteFunc| is a class that implements |JSONWriteFunc|. The
|
||||
// following code:
|
||||
//
|
||||
// JSONWriter w(MakeUnique<MyWriteFunc>());
|
||||
// w.Start();
|
||||
// {
|
||||
// w.NullProperty("null");
|
||||
// w.BoolProperty("bool", true);
|
||||
// w.IntProperty("int", 1);
|
||||
// w.StringProperty("string", "hello");
|
||||
// w.StartArrayProperty("array");
|
||||
// {
|
||||
// w.DoubleElement(3.4);
|
||||
// w.StartObjectElement();
|
||||
// {
|
||||
// w.PointerProperty("ptr", (void*)0x12345678);
|
||||
// }
|
||||
// w.EndObjectElement();
|
||||
// }
|
||||
// w.EndArrayProperty();
|
||||
// }
|
||||
// w.End();
|
||||
//
|
||||
// will produce pretty-printed output for the following JSON object:
|
||||
//
|
||||
// {
|
||||
// "null": null,
|
||||
// "bool": true,
|
||||
// "int": 1,
|
||||
// "string": "hello",
|
||||
// "array": [
|
||||
// 3.4,
|
||||
// {
|
||||
// "ptr": "0x12345678"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// The nesting in the example code is obviously optional, but can aid
|
||||
// readability.
|
||||
|
||||
#ifndef mozilla_JSONWriter_h
|
||||
#define mozilla_JSONWriter_h
|
||||
|
||||
#include "mozilla/double-conversion.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// A quasi-functor for JSONWriter. We don't use a true functor because that
|
||||
// requires templatizing JSONWriter, and the templatization seeps to lots of
|
||||
// places we don't want it to.
|
||||
class JSONWriteFunc
|
||||
{
|
||||
public:
|
||||
virtual void Write(const char* aStr) = 0;
|
||||
virtual ~JSONWriteFunc() {}
|
||||
};
|
||||
|
||||
// Ideally this would be within |EscapedString| but when compiling with GCC
|
||||
// on Linux that caused link errors, whereas this formulation didn't.
|
||||
namespace detail {
|
||||
extern MFBT_DATA const char gTwoCharEscapes[256];
|
||||
}
|
||||
|
||||
class JSONWriter
|
||||
{
|
||||
// From http://www.ietf.org/rfc/rfc4627.txt:
|
||||
//
|
||||
// "All Unicode characters may be placed within the quotation marks except
|
||||
// for the characters that must be escaped: quotation mark, reverse
|
||||
// solidus, and the control characters (U+0000 through U+001F)."
|
||||
//
|
||||
// This implementation uses two-char escape sequences where possible, namely:
|
||||
//
|
||||
// \", \\, \b, \f, \n, \r, \t
|
||||
//
|
||||
// All control characters not in the above list are represented with a
|
||||
// six-char escape sequence, e.g. '\u000b' (a.k.a. '\v').
|
||||
//
|
||||
class EscapedString
|
||||
{
|
||||
// Only one of |mUnownedStr| and |mOwnedStr| are ever non-null. |mIsOwned|
|
||||
// indicates which one is in use. They're not within a union because that
|
||||
// wouldn't work with UniquePtr.
|
||||
bool mIsOwned;
|
||||
const char* mUnownedStr;
|
||||
UniquePtr<char[]> mOwnedStr;
|
||||
|
||||
void SanityCheck() const
|
||||
{
|
||||
MOZ_ASSERT_IF( mIsOwned, mOwnedStr.get() && !mUnownedStr);
|
||||
MOZ_ASSERT_IF(!mIsOwned, !mOwnedStr.get() && mUnownedStr);
|
||||
}
|
||||
|
||||
static char hexDigitToAsciiChar(uint8_t u)
|
||||
{
|
||||
u = u & 0xf;
|
||||
return u < 10 ? '0' + u : 'a' + (u - 10);
|
||||
}
|
||||
|
||||
public:
|
||||
EscapedString(const char* aStr)
|
||||
: mUnownedStr(nullptr)
|
||||
, mOwnedStr(nullptr)
|
||||
{
|
||||
const char* p;
|
||||
|
||||
// First, see if we need to modify the string.
|
||||
size_t nExtra = 0;
|
||||
p = aStr;
|
||||
while (true) {
|
||||
uint8_t u = *p; // ensure it can't be interpreted as negative
|
||||
if (u == 0) {
|
||||
break;
|
||||
}
|
||||
if (detail::gTwoCharEscapes[u]) {
|
||||
nExtra += 1;
|
||||
} else if (u <= 0x1f) {
|
||||
nExtra += 5;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
if (nExtra == 0) {
|
||||
// No escapes needed. Easy.
|
||||
mIsOwned = false;
|
||||
mUnownedStr = aStr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Escapes are needed. We'll create a new string.
|
||||
mIsOwned = true;
|
||||
size_t len = (p - aStr) + nExtra;
|
||||
mOwnedStr = MakeUnique<char[]>(len + 1);
|
||||
|
||||
p = aStr;
|
||||
size_t i = 0;
|
||||
|
||||
while (true) {
|
||||
uint8_t u = *p; // ensure it can't be interpreted as negative
|
||||
if (u == 0) {
|
||||
mOwnedStr[i] = 0;
|
||||
break;
|
||||
}
|
||||
if (detail::gTwoCharEscapes[u]) {
|
||||
mOwnedStr[i++] = '\\';
|
||||
mOwnedStr[i++] = detail::gTwoCharEscapes[u];
|
||||
} else if (u <= 0x1f) {
|
||||
mOwnedStr[i++] = '\\';
|
||||
mOwnedStr[i++] = 'u';
|
||||
mOwnedStr[i++] = '0';
|
||||
mOwnedStr[i++] = '0';
|
||||
mOwnedStr[i++] = hexDigitToAsciiChar((u & 0x00f0) >> 4);
|
||||
mOwnedStr[i++] = hexDigitToAsciiChar(u & 0x000f);
|
||||
} else {
|
||||
mOwnedStr[i++] = u;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
~EscapedString()
|
||||
{
|
||||
SanityCheck();
|
||||
}
|
||||
|
||||
const char* get() const
|
||||
{
|
||||
SanityCheck();
|
||||
return mIsOwned ? mOwnedStr.get() : mUnownedStr;
|
||||
}
|
||||
};
|
||||
|
||||
const UniquePtr<JSONWriteFunc> mWriter;
|
||||
Vector<bool, 8> mNeedComma; // do we need a comma at depth N?
|
||||
size_t mDepth; // the current nesting depth
|
||||
|
||||
void Indent()
|
||||
{
|
||||
for (size_t i = 0; i < mDepth; i++) {
|
||||
mWriter->Write(" ");
|
||||
}
|
||||
}
|
||||
|
||||
// Adds whatever is necessary (maybe a comma, and then a newline and
|
||||
// whitespace) to separate an item (property or element) from what's come
|
||||
// before.
|
||||
void Separator()
|
||||
{
|
||||
if (mNeedComma[mDepth]) {
|
||||
mWriter->Write(",");
|
||||
}
|
||||
if (mDepth > 0) {
|
||||
mWriter->Write("\n");
|
||||
}
|
||||
Indent();
|
||||
}
|
||||
|
||||
void PropertyNameAndColon(const char* aName)
|
||||
{
|
||||
EscapedString escapedName(aName);
|
||||
mWriter->Write("\"");
|
||||
mWriter->Write(escapedName.get());
|
||||
mWriter->Write("\": ");
|
||||
}
|
||||
|
||||
void Scalar(const char* aMaybePropertyName, const char* aStringValue)
|
||||
{
|
||||
Separator();
|
||||
if (aMaybePropertyName) {
|
||||
PropertyNameAndColon(aMaybePropertyName);
|
||||
}
|
||||
mWriter->Write(aStringValue);
|
||||
mNeedComma[mDepth] = true;
|
||||
}
|
||||
|
||||
void QuotedScalar(const char* aMaybePropertyName, const char* aStringValue)
|
||||
{
|
||||
Separator();
|
||||
if (aMaybePropertyName) {
|
||||
PropertyNameAndColon(aMaybePropertyName);
|
||||
}
|
||||
mWriter->Write("\"");
|
||||
mWriter->Write(aStringValue);
|
||||
mWriter->Write("\"");
|
||||
mNeedComma[mDepth] = true;
|
||||
}
|
||||
|
||||
void NewCommaEntry()
|
||||
{
|
||||
// If this tiny allocation OOMs we might as well just crash because we must
|
||||
// be in serious memory trouble.
|
||||
MOZ_RELEASE_ASSERT(mNeedComma.growByUninitialized(1));
|
||||
mNeedComma[mDepth] = false;
|
||||
}
|
||||
|
||||
void StartCollection(const char* aMaybePropertyName, const char* aStartChar)
|
||||
{
|
||||
Separator();
|
||||
if (aMaybePropertyName) {
|
||||
mWriter->Write("\"");
|
||||
mWriter->Write(aMaybePropertyName);
|
||||
mWriter->Write("\": ");
|
||||
}
|
||||
mWriter->Write(aStartChar);
|
||||
mNeedComma[mDepth] = true;
|
||||
mDepth++;
|
||||
NewCommaEntry();
|
||||
}
|
||||
|
||||
// Adds the whitespace and closing char necessary to end a collection.
|
||||
void EndCollection(const char* aEndChar)
|
||||
{
|
||||
mDepth--;
|
||||
mWriter->Write("\n");
|
||||
Indent();
|
||||
mWriter->Write(aEndChar);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit JSONWriter(UniquePtr<JSONWriteFunc> aWriter)
|
||||
: mWriter(Move(aWriter))
|
||||
, mNeedComma()
|
||||
, mDepth(0)
|
||||
{
|
||||
NewCommaEntry();
|
||||
}
|
||||
|
||||
// Returns the JSONWriteFunc passed in at creation, for temporary use. The
|
||||
// JSONWriter object still owns the JSONWriteFunc.
|
||||
JSONWriteFunc* WriteFunc() const { return mWriter.get(); }
|
||||
|
||||
// For all the following functions, the "Prints:" comment indicates what the
|
||||
// basic output looks like. However, it doesn't indicate the indentation and
|
||||
// trailing commas, which are automatically added as required.
|
||||
//
|
||||
// All property names and string properties are escaped as necessary.
|
||||
|
||||
// Prints: {
|
||||
void Start() { StartCollection(nullptr, "{"); }
|
||||
|
||||
// Prints: }\n
|
||||
void End() { EndCollection("}\n"); }
|
||||
|
||||
// Prints: "<aName>": null
|
||||
void NullProperty(const char* aName)
|
||||
{
|
||||
Scalar(aName, "null");
|
||||
}
|
||||
|
||||
// Prints: null
|
||||
void NullElement() { NullProperty(nullptr); }
|
||||
|
||||
// Prints: "<aName>": <aBool>
|
||||
void BoolProperty(const char* aName, bool aBool)
|
||||
{
|
||||
Scalar(aName, aBool ? "true" : "false");
|
||||
}
|
||||
|
||||
// Prints: <aBool>
|
||||
void BoolElement(bool aBool) { BoolProperty(nullptr, aBool); }
|
||||
|
||||
// Prints: "<aName>": <aInt>
|
||||
void IntProperty(const char* aName, int64_t aInt)
|
||||
{
|
||||
char buf[64];
|
||||
sprintf(buf, "%" PRId64, aInt);
|
||||
Scalar(aName, buf);
|
||||
}
|
||||
|
||||
// Prints: <aInt>
|
||||
void IntElement(int64_t aInt) { IntProperty(nullptr, aInt); }
|
||||
|
||||
// Prints: "<aName>": <aDouble>
|
||||
void DoubleProperty(const char* aName, double aDouble)
|
||||
{
|
||||
static const size_t buflen = 64;
|
||||
char buf[buflen];
|
||||
const double_conversion::DoubleToStringConverter &converter =
|
||||
double_conversion::DoubleToStringConverter::EcmaScriptConverter();
|
||||
double_conversion::StringBuilder builder(buf, buflen);
|
||||
converter.ToShortest(aDouble, &builder);
|
||||
Scalar(aName, builder.Finalize());
|
||||
}
|
||||
|
||||
// Prints: <aDouble>
|
||||
void DoubleElement(double aDouble) { DoubleProperty(nullptr, aDouble); }
|
||||
|
||||
// Prints: "<aName>": "<aStr>"
|
||||
void StringProperty(const char* aName, const char* aStr)
|
||||
{
|
||||
EscapedString escapedStr(aStr);
|
||||
QuotedScalar(aName, escapedStr.get());
|
||||
}
|
||||
|
||||
// Prints: "<aStr>"
|
||||
void StringElement(const char* aStr) { StringProperty(nullptr, aStr); }
|
||||
|
||||
// Prints: "<aName>": "<aPtr>"
|
||||
// The pointer is printed as a hexadecimal integer with a leading '0x'.
|
||||
void PointerProperty(const char* aName, const void* aPtr)
|
||||
{
|
||||
char buf[32];
|
||||
sprintf(buf, "0x%" PRIxPTR, uintptr_t(aPtr));
|
||||
QuotedScalar(aName, buf);
|
||||
}
|
||||
|
||||
// Prints: "<aPtr>"
|
||||
// The pointer is printed as a hexadecimal integer with a leading '0x'.
|
||||
void PointerElement(const void* aPtr) { PointerProperty(nullptr, aPtr); }
|
||||
|
||||
// Prints: "<aName>": [
|
||||
void StartArrayProperty(const char* aName) { StartCollection(aName, "["); }
|
||||
|
||||
// Prints: [
|
||||
void StartArrayElement() { StartArrayProperty(nullptr); }
|
||||
|
||||
// Prints: ]
|
||||
void EndArray() { EndCollection("]"); }
|
||||
|
||||
// Prints: "<aName>": {
|
||||
void StartObjectProperty(const char* aName) { StartCollection(aName, "{"); }
|
||||
|
||||
// Prints: {
|
||||
void StartObjectElement() { StartObjectProperty(nullptr); }
|
||||
|
||||
// Prints: }
|
||||
void EndObject() { EndCollection("}"); }
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* mozilla_JSONWriter_h */
|
||||
|
@ -37,6 +37,7 @@ EXPORTS.mozilla = [
|
||||
'HashFunctions.h',
|
||||
'IntegerPrintfMacros.h',
|
||||
'IntegerTypeTraits.h',
|
||||
'JSONWriter.h',
|
||||
'Likely.h',
|
||||
'LinkedList.h',
|
||||
'MacroArgs.h',
|
||||
@ -97,6 +98,7 @@ UNIFIED_SOURCES = [
|
||||
'double-conversion/strtod.cc',
|
||||
'FloatingPoint.cpp',
|
||||
'HashFunctions.cpp',
|
||||
'JSONWriter.cpp',
|
||||
'Poison.cpp',
|
||||
'SHA1.cpp',
|
||||
'TaggedAnonymousMemory.cpp',
|
||||
|
417
mfbt/tests/TestJSONWriter.cpp
Normal file
417
mfbt/tests/TestJSONWriter.cpp
Normal file
@ -0,0 +1,417 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/JSONWriter.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
using mozilla::JSONWriteFunc;
|
||||
using mozilla::JSONWriter;
|
||||
using mozilla::MakeUnique;
|
||||
|
||||
// This writes all the output into a big buffer.
|
||||
struct StringWriteFunc : public JSONWriteFunc
|
||||
{
|
||||
const static size_t kLen = 100000;
|
||||
char mBuf[kLen];
|
||||
char* mPtr;
|
||||
|
||||
StringWriteFunc() : mPtr(mBuf) {}
|
||||
|
||||
void Write(const char* aStr)
|
||||
{
|
||||
char* last = mPtr + strlen(aStr); // where the nul will be added
|
||||
|
||||
// If you change this test and this assertion fails, just make kLen bigger.
|
||||
MOZ_RELEASE_ASSERT(last < mBuf + kLen);
|
||||
sprintf(mPtr, "%s", aStr);
|
||||
mPtr = last;
|
||||
}
|
||||
};
|
||||
|
||||
void Check(JSONWriteFunc* aFunc, const char* aExpected)
|
||||
{
|
||||
const char* actual = static_cast<StringWriteFunc*>(aFunc)->mBuf;
|
||||
if (strcmp(aExpected, actual) != 0) {
|
||||
fprintf(stderr,
|
||||
"---- EXPECTED ----\n<<<%s>>>\n"
|
||||
"---- ACTUAL ----\n<<<%s>>>\n",
|
||||
aExpected, actual);
|
||||
MOZ_RELEASE_ASSERT(false, "expected and actual output don't match");
|
||||
}
|
||||
}
|
||||
|
||||
// Note: to convert actual output into |expected| strings that C++ can handle,
|
||||
// apply the following substitutions, in order, to each line.
|
||||
// - s/\\/\\\\/g # escapes backslashes
|
||||
// - s/"/\\"/g # escapes quotes
|
||||
// - s/$/\\n\\/ # adds a newline and string continuation char to each line
|
||||
|
||||
void TestBasicProperties()
|
||||
{
|
||||
const char* expected = "\
|
||||
{\n\
|
||||
\"null\": null,\n\
|
||||
\"bool1\": true,\n\
|
||||
\"bool2\": false,\n\
|
||||
\"int1\": 123,\n\
|
||||
\"int2\": -123,\n\
|
||||
\"int3\": -123456789000,\n\
|
||||
\"double1\": 1.2345,\n\
|
||||
\"double2\": -3,\n\
|
||||
\"double3\": 1e-7,\n\
|
||||
\"double4\": 1.1111111111111111e+21,\n\
|
||||
\"string1\": \"\",\n\
|
||||
\"string2\": \"1234\",\n\
|
||||
\"string3\": \"hello\",\n\
|
||||
\"string4\": \"\\\" \\\\ \\u0007 \\b \\t \\n \\u000b \\f \\r\",\n\
|
||||
\"ptr1\": \"0x0\",\n\
|
||||
\"ptr2\": \"0xdeadbeef\",\n\
|
||||
\"ptr3\": \"0xfacade\",\n\
|
||||
\"len 0 array\": [\n\
|
||||
],\n\
|
||||
\"len 1 array\": [\n\
|
||||
1\n\
|
||||
],\n\
|
||||
\"len 5 array\": [\n\
|
||||
1,\n\
|
||||
2,\n\
|
||||
3,\n\
|
||||
4,\n\
|
||||
5\n\
|
||||
],\n\
|
||||
\"len 0 object\": {\n\
|
||||
},\n\
|
||||
\"len 1 object\": {\n\
|
||||
\"one\": 1\n\
|
||||
},\n\
|
||||
\"len 5 object\": {\n\
|
||||
\"one\": 1,\n\
|
||||
\"two\": 2,\n\
|
||||
\"three\": 3,\n\
|
||||
\"four\": 4,\n\
|
||||
\"five\": 5\n\
|
||||
}\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
JSONWriter w(MakeUnique<StringWriteFunc>());
|
||||
|
||||
w.Start();
|
||||
{
|
||||
w.NullProperty("null");
|
||||
|
||||
w.BoolProperty("bool1", true);
|
||||
w.BoolProperty("bool2", false);
|
||||
|
||||
w.IntProperty("int1", 123);
|
||||
w.IntProperty("int2", -0x7b);
|
||||
w.IntProperty("int3", -123456789000ll);
|
||||
|
||||
w.DoubleProperty("double1", 1.2345);
|
||||
w.DoubleProperty("double2", -3);
|
||||
w.DoubleProperty("double3", 1e-7);
|
||||
w.DoubleProperty("double4", 1.1111111111111111e+21);
|
||||
|
||||
w.StringProperty("string1", "");
|
||||
w.StringProperty("string2", "1234");
|
||||
w.StringProperty("string3", "hello");
|
||||
w.StringProperty("string4", "\" \\ \a \b \t \n \v \f \r");
|
||||
|
||||
w.PointerProperty("ptr1", (void*)0x0);
|
||||
w.PointerProperty("ptr2", (void*)0xdeadbeef);
|
||||
w.PointerProperty("ptr3", (void*)0xFaCaDe);
|
||||
|
||||
w.StartArrayProperty("len 0 array");
|
||||
w.EndArray();
|
||||
|
||||
w.StartArrayProperty("len 1 array");
|
||||
{
|
||||
w.IntElement(1);
|
||||
}
|
||||
w.EndArray();
|
||||
|
||||
w.StartArrayProperty("len 5 array");
|
||||
{
|
||||
w.IntElement(1);
|
||||
w.IntElement(2);
|
||||
w.IntElement(3);
|
||||
w.IntElement(4);
|
||||
w.IntElement(5);
|
||||
}
|
||||
w.EndArray();
|
||||
|
||||
w.StartObjectProperty("len 0 object");
|
||||
w.EndObject();
|
||||
|
||||
w.StartObjectProperty("len 1 object");
|
||||
{
|
||||
w.IntProperty("one", 1);
|
||||
}
|
||||
w.EndObject();
|
||||
|
||||
w.StartObjectProperty("len 5 object");
|
||||
{
|
||||
w.IntProperty("one", 1);
|
||||
w.IntProperty("two", 2);
|
||||
w.IntProperty("three", 3);
|
||||
w.IntProperty("four", 4);
|
||||
w.IntProperty("five", 5);
|
||||
}
|
||||
w.EndObject();
|
||||
}
|
||||
w.End();
|
||||
|
||||
Check(w.WriteFunc(), expected);
|
||||
}
|
||||
|
||||
void TestBasicElements()
|
||||
{
|
||||
const char* expected = "\
|
||||
{\n\
|
||||
\"array\": [\n\
|
||||
null,\n\
|
||||
true,\n\
|
||||
false,\n\
|
||||
123,\n\
|
||||
-123,\n\
|
||||
-123456789000,\n\
|
||||
1.2345,\n\
|
||||
-3,\n\
|
||||
1e-7,\n\
|
||||
1.1111111111111111e+21,\n\
|
||||
\"\",\n\
|
||||
\"1234\",\n\
|
||||
\"hello\",\n\
|
||||
\"\\\" \\\\ \\u0007 \\b \\t \\n \\u000b \\f \\r\",\n\
|
||||
\"0x0\",\n\
|
||||
\"0xdeadbeef\",\n\
|
||||
\"0xfacade\",\n\
|
||||
[\n\
|
||||
],\n\
|
||||
[\n\
|
||||
1\n\
|
||||
],\n\
|
||||
[\n\
|
||||
1,\n\
|
||||
2,\n\
|
||||
3,\n\
|
||||
4,\n\
|
||||
5\n\
|
||||
],\n\
|
||||
{\n\
|
||||
},\n\
|
||||
{\n\
|
||||
\"one\": 1\n\
|
||||
},\n\
|
||||
{\n\
|
||||
\"one\": 1,\n\
|
||||
\"two\": 2,\n\
|
||||
\"three\": 3,\n\
|
||||
\"four\": 4,\n\
|
||||
\"five\": 5\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
JSONWriter w(MakeUnique<StringWriteFunc>());
|
||||
|
||||
w.Start();
|
||||
w.StartArrayProperty("array");
|
||||
{
|
||||
w.NullElement();
|
||||
|
||||
w.BoolElement(true);
|
||||
w.BoolElement(false);
|
||||
|
||||
w.IntElement(123);
|
||||
w.IntElement(-0x7b);
|
||||
w.IntElement(-123456789000ll);
|
||||
|
||||
w.DoubleElement(1.2345);
|
||||
w.DoubleElement(-3);
|
||||
w.DoubleElement(1e-7);
|
||||
w.DoubleElement(1.1111111111111111e+21);
|
||||
|
||||
w.StringElement("");
|
||||
w.StringElement("1234");
|
||||
w.StringElement("hello");
|
||||
w.StringElement("\" \\ \a \b \t \n \v \f \r");
|
||||
|
||||
w.PointerElement((void*)0x0);
|
||||
w.PointerElement((void*)0xdeadbeef);
|
||||
w.PointerElement((void*)0xFaCaDe);
|
||||
|
||||
w.StartArrayElement();
|
||||
w.EndArray();
|
||||
|
||||
w.StartArrayElement();
|
||||
{
|
||||
w.IntElement(1);
|
||||
}
|
||||
w.EndArray();
|
||||
|
||||
w.StartArrayElement();
|
||||
{
|
||||
w.IntElement(1);
|
||||
w.IntElement(2);
|
||||
w.IntElement(3);
|
||||
w.IntElement(4);
|
||||
w.IntElement(5);
|
||||
}
|
||||
w.EndArray();
|
||||
|
||||
w.StartObjectElement();
|
||||
w.EndObject();
|
||||
|
||||
w.StartObjectElement();
|
||||
{
|
||||
w.IntProperty("one", 1);
|
||||
}
|
||||
w.EndObject();
|
||||
|
||||
w.StartObjectElement();
|
||||
{
|
||||
w.IntProperty("one", 1);
|
||||
w.IntProperty("two", 2);
|
||||
w.IntProperty("three", 3);
|
||||
w.IntProperty("four", 4);
|
||||
w.IntProperty("five", 5);
|
||||
}
|
||||
w.EndObject();
|
||||
}
|
||||
w.EndArray();
|
||||
w.End();
|
||||
|
||||
Check(w.WriteFunc(), expected);
|
||||
}
|
||||
|
||||
void TestStringEscaping()
|
||||
{
|
||||
const char* expected = "\
|
||||
{\n\
|
||||
\"ascii\": \"~}|{zyxwvutsrqponmlkjihgfedcba`_^]\\\\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#\\\"! \\u001f\\u001e\\u001d\\u001c\\u001b\\u001a\\u0019\\u0018\\u0017\\u0016\\u0015\\u0014\\u0013\\u0012\\u0011\\u0010\\u000f\\u000e\\r\\f\\u000b\\n\\t\\b\\u0007\\u0006\\u0005\\u0004\\u0003\\u0002\\u0001\",\n\
|
||||
\"مرحبا هناك\": true,\n\
|
||||
\"բարեւ չկա\": -123,\n\
|
||||
\"你好\": 1.234,\n\
|
||||
\"γεια εκεί\": \"سلام\",\n\
|
||||
\"halló þarna\": \"0x1234\",\n\
|
||||
\"こんにちは\": {\n\
|
||||
\"привет\": [\n\
|
||||
]\n\
|
||||
}\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
JSONWriter w(MakeUnique<StringWriteFunc>());
|
||||
|
||||
// Test the string escaping behaviour.
|
||||
w.Start();
|
||||
{
|
||||
// Test all 127 ascii values. Do it in reverse order so that the 0
|
||||
// at the end serves as the null char.
|
||||
char buf[128];
|
||||
for (int i = 0; i < 128; i++) {
|
||||
buf[i] = 127 - i;
|
||||
}
|
||||
w.StringProperty("ascii", buf);
|
||||
|
||||
// Test lots of unicode stuff. Note that this file is encoded as UTF-8.
|
||||
w.BoolProperty("مرحبا هناك", true);
|
||||
w.IntProperty("բարեւ չկա", -123);
|
||||
w.DoubleProperty("你好", 1.234);
|
||||
w.StringProperty("γεια εκεί", "سلام");
|
||||
w.PointerProperty("halló þarna", (void*)0x1234);
|
||||
w.StartObjectProperty("こんにちは");
|
||||
{
|
||||
w.StartArrayProperty("привет");
|
||||
w.EndArray();
|
||||
}
|
||||
w.EndObject();
|
||||
}
|
||||
w.End();
|
||||
|
||||
Check(w.WriteFunc(), expected);
|
||||
}
|
||||
|
||||
void TestDeepNesting()
|
||||
{
|
||||
const char* expected = "\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
\"a\": [\n\
|
||||
{\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
]\n\
|
||||
}\n\
|
||||
";
|
||||
|
||||
JSONWriter w(MakeUnique<StringWriteFunc>());
|
||||
|
||||
w.Start();
|
||||
{
|
||||
static const int n = 10;
|
||||
for (int i = 0; i < n; i++) {
|
||||
w.StartArrayProperty("a");
|
||||
w.StartObjectElement();
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
w.EndObject();
|
||||
w.EndArray();
|
||||
}
|
||||
}
|
||||
w.End();
|
||||
|
||||
Check(w.WriteFunc(), expected);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
TestBasicProperties();
|
||||
TestBasicElements();
|
||||
TestStringEscaping();
|
||||
TestDeepNesting();
|
||||
|
||||
return 0;
|
||||
}
|
@ -18,6 +18,7 @@ CppUnitTests([
|
||||
'TestEnumSet',
|
||||
'TestFloatingPoint',
|
||||
'TestIntegerPrintfMacros',
|
||||
'TestJSONWriter',
|
||||
'TestMacroArgs',
|
||||
'TestMacroForEach',
|
||||
'TestMaybe',
|
||||
|
@ -62,7 +62,8 @@ interface nsIMemoryInfoDumper : nsISupports
|
||||
* Sample output:
|
||||
*
|
||||
* {
|
||||
* "hasMozMallocUsableSize":true,
|
||||
* "version": 1
|
||||
* "hasMozMallocUsableSize": true,
|
||||
* "reports": [
|
||||
* {"process":"Main Process (pid 12345)", "path":"explicit/foo/bar",
|
||||
* "kind":1, "units":0, "amount":2000000, "description":"Foo bar."},
|
||||
@ -77,6 +78,11 @@ interface nsIMemoryInfoDumper : nsISupports
|
||||
*
|
||||
* {
|
||||
* "properties": {
|
||||
* "version": {
|
||||
* "type": "integer",
|
||||
* "description": "Version number of this schema.",
|
||||
* "required": true
|
||||
* },
|
||||
* "hasMozMallocUsableSize": {
|
||||
* "type": "boolean",
|
||||
* "description": "nsIMemoryReporterManager::hasMozMallocUsableSize",
|
||||
|
@ -4,6 +4,8 @@
|
||||
* 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/. */
|
||||
|
||||
#include "mozilla/JSONWriter.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/nsMemoryInfoDumper.h"
|
||||
#include "nsDumpUtils.h"
|
||||
|
||||
@ -418,100 +420,6 @@ nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#define DUMP(o, s) \
|
||||
do { \
|
||||
nsresult rv = (o)->Write(s); \
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) \
|
||||
return rv; \
|
||||
} while (0)
|
||||
|
||||
class DumpReportCallback MOZ_FINAL : public nsIHandleReportCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
explicit DumpReportCallback(nsGZFileWriter* aWriter)
|
||||
: mIsFirst(true)
|
||||
, mWriter(aWriter)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
|
||||
int32_t aKind, int32_t aUnits, int64_t aAmount,
|
||||
const nsACString& aDescription,
|
||||
nsISupports* aData)
|
||||
{
|
||||
if (mIsFirst) {
|
||||
DUMP(mWriter, "[");
|
||||
mIsFirst = false;
|
||||
} else {
|
||||
DUMP(mWriter, ",");
|
||||
}
|
||||
|
||||
nsAutoCString process;
|
||||
if (aProcess.IsEmpty()) {
|
||||
// If the process is empty, the report originated with the process doing
|
||||
// the dumping. In that case, generate the process identifier, which is of
|
||||
// the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we don't
|
||||
// have a process name. If we're the main process, we let $PROCESS_NAME be
|
||||
// "Main Process".
|
||||
if (XRE_GetProcessType() == GeckoProcessType_Default) {
|
||||
// We're the main process.
|
||||
process.AssignLiteral("Main Process");
|
||||
} else if (ContentChild* cc = ContentChild::GetSingleton()) {
|
||||
// Try to get the process name from ContentChild.
|
||||
cc->GetProcessName(process);
|
||||
}
|
||||
ContentChild::AppendProcessId(process);
|
||||
|
||||
} else {
|
||||
// Otherwise, the report originated with another process and already has a
|
||||
// process name. Just use that.
|
||||
process = aProcess;
|
||||
}
|
||||
|
||||
DUMP(mWriter, "\n {\"process\": \"");
|
||||
DUMP(mWriter, process);
|
||||
|
||||
DUMP(mWriter, "\", \"path\": \"");
|
||||
nsCString path(aPath);
|
||||
path.ReplaceSubstring("\\", "\\\\"); /* <backslash> --> \\ */
|
||||
path.ReplaceSubstring("\"", "\\\""); // " --> \"
|
||||
DUMP(mWriter, path);
|
||||
|
||||
DUMP(mWriter, "\", \"kind\": ");
|
||||
DUMP(mWriter, nsPrintfCString("%d", aKind));
|
||||
|
||||
DUMP(mWriter, ", \"units\": ");
|
||||
DUMP(mWriter, nsPrintfCString("%d", aUnits));
|
||||
|
||||
DUMP(mWriter, ", \"amount\": ");
|
||||
DUMP(mWriter, nsPrintfCString("%lld", aAmount));
|
||||
|
||||
nsCString description(aDescription);
|
||||
description.ReplaceSubstring("\\", "\\\\"); /* <backslash> --> \\ */
|
||||
description.ReplaceSubstring("\"", "\\\""); // " --> \"
|
||||
description.ReplaceSubstring("\n", "\\n"); // <newline> --> \n
|
||||
DUMP(mWriter, ", \"description\": \"");
|
||||
DUMP(mWriter, description);
|
||||
DUMP(mWriter, "\"}");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~DumpReportCallback() {}
|
||||
|
||||
bool mIsFirst;
|
||||
nsRefPtr<nsGZFileWriter> mWriter;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(DumpReportCallback, nsIHandleReportCallback)
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
static void
|
||||
MakeFilename(const char* aPrefix, const nsAString& aIdentifier,
|
||||
int aPid, const char* aSuffix, nsACString& aResult)
|
||||
@ -544,66 +452,101 @@ DMDWrite(void* aState, const char* aFmt, va_list ap)
|
||||
}
|
||||
#endif
|
||||
|
||||
static nsresult
|
||||
DumpHeader(nsIGZFileWriter* aWriter)
|
||||
// This class wraps GZFileWriter so it can be used with JSONWriter, overcoming
|
||||
// the following two problems:
|
||||
// - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write().
|
||||
// - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted.
|
||||
class GZWriterWrapper : public JSONWriteFunc
|
||||
{
|
||||
// Increment this number if the format changes.
|
||||
//
|
||||
// This is the first write to the file, and it causes |aWriter| to allocate
|
||||
// over 200 KiB of memory.
|
||||
//
|
||||
DUMP(aWriter, "{\n \"version\": 1,\n");
|
||||
public:
|
||||
explicit GZWriterWrapper(nsGZFileWriter* aGZWriter)
|
||||
: mGZWriter(aGZWriter)
|
||||
{}
|
||||
|
||||
DUMP(aWriter, " \"hasMozMallocUsableSize\": ");
|
||||
|
||||
nsCOMPtr<nsIMemoryReporterManager> mgr =
|
||||
do_GetService("@mozilla.org/memory-reporter-manager;1");
|
||||
if (NS_WARN_IF(!mgr)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
void Write(const char* aStr)
|
||||
{
|
||||
(void)mGZWriter->Write(aStr);
|
||||
}
|
||||
|
||||
DUMP(aWriter, mgr->GetHasMozMallocUsableSize() ? "true" : "false");
|
||||
DUMP(aWriter, ",\n");
|
||||
DUMP(aWriter, " \"reports\": ");
|
||||
nsresult Finish() { return mGZWriter->Finish(); }
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
nsRefPtr<nsGZFileWriter> mGZWriter;
|
||||
};
|
||||
|
||||
static nsresult
|
||||
DumpFooter(nsIGZFileWriter* aWriter)
|
||||
{
|
||||
DUMP(aWriter, "\n ]\n}\n");
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// This dumps the JSON footer and closes the file, and then calls the given
|
||||
// nsIFinishDumpingCallback.
|
||||
class FinishReportingCallback MOZ_FINAL : public nsIFinishReportingCallback
|
||||
// We need two callbacks: one that handles reports, and one that is called at
|
||||
// the end of reporting. Both the callbacks need access to the same JSONWriter,
|
||||
// so we implement both of them in this one class.
|
||||
class HandleReportAndFinishReportingCallbacks MOZ_FINAL
|
||||
: public nsIHandleReportCallback, public nsIFinishReportingCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
FinishReportingCallback(nsGZFileWriter* aReportsWriter,
|
||||
nsIFinishDumpingCallback* aFinishDumping,
|
||||
nsISupports* aFinishDumpingData)
|
||||
: mReportsWriter(aReportsWriter)
|
||||
HandleReportAndFinishReportingCallbacks(UniquePtr<JSONWriter> aWriter,
|
||||
nsIFinishDumpingCallback* aFinishDumping,
|
||||
nsISupports* aFinishDumpingData)
|
||||
: mWriter(Move(aWriter))
|
||||
, mFinishDumping(aFinishDumping)
|
||||
, mFinishDumpingData(aFinishDumpingData)
|
||||
{
|
||||
}
|
||||
|
||||
// This is the callback for nsIHandleReportCallback.
|
||||
NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
|
||||
int32_t aKind, int32_t aUnits, int64_t aAmount,
|
||||
const nsACString& aDescription,
|
||||
nsISupports* aData)
|
||||
{
|
||||
nsAutoCString process;
|
||||
if (aProcess.IsEmpty()) {
|
||||
// If the process is empty, the report originated with the process doing
|
||||
// the dumping. In that case, generate the process identifier, which is
|
||||
// of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we
|
||||
// don't have a process name. If we're the main process, we let
|
||||
// $PROCESS_NAME be "Main Process".
|
||||
if (XRE_GetProcessType() == GeckoProcessType_Default) {
|
||||
// We're the main process.
|
||||
process.AssignLiteral("Main Process");
|
||||
} else if (ContentChild* cc = ContentChild::GetSingleton()) {
|
||||
// Try to get the process name from ContentChild.
|
||||
cc->GetProcessName(process);
|
||||
}
|
||||
ContentChild::AppendProcessId(process);
|
||||
|
||||
} else {
|
||||
// Otherwise, the report originated with another process and already has a
|
||||
// process name. Just use that.
|
||||
process = aProcess;
|
||||
}
|
||||
|
||||
mWriter->StartObjectElement();
|
||||
{
|
||||
mWriter->StringProperty("process", process.get());
|
||||
mWriter->StringProperty("path", PromiseFlatCString(aPath).get());
|
||||
mWriter->IntProperty("kind", aKind);
|
||||
mWriter->IntProperty("units", aUnits);
|
||||
mWriter->IntProperty("amount", aAmount);
|
||||
mWriter->StringProperty("description",
|
||||
PromiseFlatCString(aDescription).get());
|
||||
}
|
||||
mWriter->EndObject();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// This is the callback for nsIFinishReportingCallback.
|
||||
NS_IMETHOD Callback(nsISupports* aData)
|
||||
{
|
||||
nsresult rv = DumpFooter(mReportsWriter);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mWriter->EndArray(); // end of "reports" array
|
||||
mWriter->End();
|
||||
|
||||
// The call to Finish() deallocates the memory allocated by the first DUMP()
|
||||
// The call to Finish() deallocates the memory allocated by the first Write
|
||||
// call. Because that memory was live while the memory reporters ran and
|
||||
// thus measured by them -- by "heap-allocated" if nothing else -- we want
|
||||
// DMD to see it as well. So we deliberately don't call Finish() until
|
||||
// was measured by them -- by "heap-allocated" if nothing else -- we want
|
||||
// DMD to see it as well. So we deliberately don't call Finish() until
|
||||
// after DMD finishes.
|
||||
rv = mReportsWriter->Finish();
|
||||
nsresult rv = static_cast<GZWriterWrapper*>(mWriter->WriteFunc())->Finish();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!mFinishDumping) {
|
||||
@ -614,14 +557,15 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
~FinishReportingCallback() {}
|
||||
~HandleReportAndFinishReportingCallbacks() {}
|
||||
|
||||
nsRefPtr<nsGZFileWriter> mReportsWriter;
|
||||
UniquePtr<JSONWriter> mWriter;
|
||||
nsCOMPtr<nsIFinishDumpingCallback> mFinishDumping;
|
||||
nsCOMPtr<nsISupports> mFinishDumpingData;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(FinishReportingCallback, nsIFinishReportingCallback)
|
||||
NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks,
|
||||
nsIHandleReportCallback, nsIFinishReportingCallback)
|
||||
|
||||
class TempDirFinishCallback MOZ_FINAL : public nsIFinishDumpingCallback
|
||||
{
|
||||
@ -713,28 +657,35 @@ DumpMemoryInfoToFile(
|
||||
bool aMinimizeMemoryUsage,
|
||||
nsAString& aDMDIdentifier)
|
||||
{
|
||||
nsRefPtr<nsGZFileWriter> reportsWriter = new nsGZFileWriter();
|
||||
nsresult rv = reportsWriter->Init(aReportsFile);
|
||||
nsRefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
|
||||
nsresult rv = gzWriter->Init(aReportsFile);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
auto jsonWriter =
|
||||
MakeUnique<JSONWriter>(MakeUnique<GZWriterWrapper>(gzWriter));
|
||||
|
||||
// Dump the memory reports to the file.
|
||||
rv = DumpHeader(reportsWriter);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Process reporters.
|
||||
nsCOMPtr<nsIMemoryReporterManager> mgr =
|
||||
do_GetService("@mozilla.org/memory-reporter-manager;1");
|
||||
nsRefPtr<DumpReportCallback> dumpReport =
|
||||
new DumpReportCallback(reportsWriter);
|
||||
nsRefPtr<FinishReportingCallback> finishReporting =
|
||||
new FinishReportingCallback(reportsWriter, aFinishDumping,
|
||||
aFinishDumpingData);
|
||||
rv = mgr->GetReportsExtended(dumpReport, nullptr,
|
||||
finishReporting, nullptr,
|
||||
|
||||
// This is the first write to the file, and it causes |aWriter| to allocate
|
||||
// over 200 KiB of memory.
|
||||
jsonWriter->Start();
|
||||
{
|
||||
// Increment this number if the format changes.
|
||||
jsonWriter->IntProperty("version", 1);
|
||||
jsonWriter->BoolProperty("hasMozMallocUsableSize",
|
||||
mgr->GetHasMozMallocUsableSize());
|
||||
jsonWriter->StartArrayProperty("reports");
|
||||
}
|
||||
|
||||
nsRefPtr<HandleReportAndFinishReportingCallbacks>
|
||||
handleReportAndFinishReporting =
|
||||
new HandleReportAndFinishReportingCallbacks(Move(jsonWriter),
|
||||
aFinishDumping,
|
||||
aFinishDumpingData);
|
||||
rv = mgr->GetReportsExtended(handleReportAndFinishReporting, nullptr,
|
||||
handleReportAndFinishReporting, nullptr,
|
||||
aAnonymize,
|
||||
aMinimizeMemoryUsage,
|
||||
aDMDIdentifier);
|
||||
@ -801,12 +752,12 @@ nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier,
|
||||
// looking for memory report dumps to grab a file before we're finished
|
||||
// writing to it.
|
||||
|
||||
nsCString reportsFinalFilename;
|
||||
// The "unified" indicates that we merge the memory reports from all
|
||||
// processes and write out one file, rather than a separate file for
|
||||
// each process as was the case before bug 946407. This is so that
|
||||
// the get_about_memory.py script in the B2G repository can
|
||||
// determine when it's done waiting for files to appear.
|
||||
nsCString reportsFinalFilename;
|
||||
MakeFilename("unified-memory-report", identifier, getpid(), "json.gz",
|
||||
reportsFinalFilename);
|
||||
|
||||
@ -892,4 +843,3 @@ nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile)
|
||||
}
|
||||
#endif // MOZ_DMD
|
||||
|
||||
#undef DUMP
|
||||
|
Loading…
Reference in New Issue
Block a user