Commit b30f46fd authored by George Zhang's avatar George Zhang Committed by Commit Bot

Fill in the first pass of the parser to analyze hprof files.

The main purpose of the java heap profiler is to obtain a heap dump in
the form of an hprof file and then parse the hprof file. Currently,
there exists an empty parser that skips over everything in the hprof
file.

With CL, we first parse each instance for given fields (object_id,
names, etc..) and then store these values in different instance
objects. We will be using these instance objects in the next CL which
is the second pass over.

Bug: 1012072
Change-Id: Ibe249056c180dd07b907ada129898248c91cbaec
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1898481
Commit-Queue: ssid <ssid@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarssid <ssid@chromium.org>
Cr-Commit-Position: refs/heads/master@{#716986}
parent a6d4a1dc
...@@ -36,6 +36,9 @@ if (!is_nacl && !is_ios) { ...@@ -36,6 +36,9 @@ if (!is_nacl && !is_ios) {
"perfetto/interning_index.h", "perfetto/interning_index.h",
"perfetto/java_heap_profiler/hprof_buffer_android.cc", "perfetto/java_heap_profiler/hprof_buffer_android.cc",
"perfetto/java_heap_profiler/hprof_buffer_android.h", "perfetto/java_heap_profiler/hprof_buffer_android.h",
"perfetto/java_heap_profiler/hprof_data_type_android.h",
"perfetto/java_heap_profiler/hprof_instances_android.cc",
"perfetto/java_heap_profiler/hprof_instances_android.h",
"perfetto/java_heap_profiler/hprof_parser_android.cc", "perfetto/java_heap_profiler/hprof_parser_android.cc",
"perfetto/java_heap_profiler/hprof_parser_android.h", "perfetto/java_heap_profiler/hprof_parser_android.h",
"perfetto/java_heap_profiler/java_heap_profiler_android.cc", "perfetto/java_heap_profiler/java_heap_profiler_android.cc",
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_buffer_android.h" #include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_buffer_android.h"
#include "base/logging.h" #include "base/logging.h"
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_data_type_android.h"
namespace tracing { namespace tracing {
...@@ -28,7 +29,7 @@ uint64_t HprofBuffer::GetId() { ...@@ -28,7 +29,7 @@ uint64_t HprofBuffer::GetId() {
} }
bool HprofBuffer::HasRemaining() { bool HprofBuffer::HasRemaining() {
return data_position_ < size_; return offset_ < size_;
} }
void HprofBuffer::set_id_size(unsigned id_size) { void HprofBuffer::set_id_size(unsigned id_size) {
...@@ -38,18 +39,37 @@ void HprofBuffer::set_id_size(unsigned id_size) { ...@@ -38,18 +39,37 @@ void HprofBuffer::set_id_size(unsigned id_size) {
void HprofBuffer::set_position(size_t new_position) { void HprofBuffer::set_position(size_t new_position) {
DCHECK(new_position <= size_ && new_position >= 0); DCHECK(new_position <= size_ && new_position >= 0);
data_position_ = new_position; offset_ = new_position;
} }
// Skips |delta| bytes in the buffer. // Skips |delta| bytes in the buffer.
void HprofBuffer::Skip(uint32_t delta) { void HprofBuffer::Skip(uint32_t delta) {
set_position(data_position_ + delta); set_position(offset_ + delta);
}
void HprofBuffer::SkipBytesByType(DataType type) {
Skip(SizeOfType(type));
}
void HprofBuffer::SkipId() {
Skip(object_id_size_in_bytes_);
}
const char* HprofBuffer::DataPosition() {
return reinterpret_cast<const char*>(data_ + offset_);
}
uint32_t HprofBuffer::SizeOfType(uint32_t index) {
uint32_t object_size = kTypeSizes[index];
// If type is object, return id_size.
return object_size == 0 ? object_id_size_in_bytes_ : object_size;
} }
unsigned char HprofBuffer::GetByte() { unsigned char HprofBuffer::GetByte() {
DCHECK(HasRemaining()); DCHECK(HasRemaining());
unsigned char byte = data_[data_position_]; unsigned char byte = data_[offset_];
++data_position_; ++offset_;
return byte; return byte;
} }
...@@ -70,4 +90,5 @@ uint64_t HprofBuffer::GetUInt64FromBytes(size_t num_bytes) { ...@@ -70,4 +90,5 @@ uint64_t HprofBuffer::GetUInt64FromBytes(size_t num_bytes) {
} }
return val; return val;
} }
} // namespace tracing } // namespace tracing
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
#ifndef SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_BUFFER_ANDROID_H_ #ifndef SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_BUFFER_ANDROID_H_
#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_BUFFER_ANDROID_H_ #define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_BUFFER_ANDROID_H_
#include <stddef.h> #include <string>
#include <cstdint>
#include "base/component_export.h" #include "base/component_export.h"
#include "base/macros.h" #include "base/macros.h"
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_data_type_android.h"
namespace tracing { namespace tracing {
...@@ -33,6 +33,15 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofBuffer { ...@@ -33,6 +33,15 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofBuffer {
// Skips |delta| bytes in the buffer. // Skips |delta| bytes in the buffer.
void Skip(uint32_t delta); void Skip(uint32_t delta);
void SkipBytesByType(DataType type);
void SkipId();
// Returns a pointer to the current position of |data_| with offset included.
const char* DataPosition();
uint32_t SizeOfType(uint32_t index);
size_t offset() const { return offset_; }
unsigned object_id_size_in_bytes() const { return object_id_size_in_bytes_; }
private: private:
unsigned char GetByte(); unsigned char GetByte();
...@@ -42,11 +51,12 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofBuffer { ...@@ -42,11 +51,12 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofBuffer {
// Read in the next |num_bytes| as an uint64_t. // Read in the next |num_bytes| as an uint64_t.
uint64_t GetUInt64FromBytes(size_t num_bytes); uint64_t GetUInt64FromBytes(size_t num_bytes);
const unsigned char* const data_;
const size_t size_;
size_t data_position_ = 0;
// The ID size in bytes of the objects in the hprof, valid values are 4 and 8. // The ID size in bytes of the objects in the hprof, valid values are 4 and 8.
unsigned object_id_size_in_bytes_ = 4; unsigned object_id_size_in_bytes_ = 4;
const unsigned char* const data_; // Pointer to buffer.
const size_t size_; // Total size of buffer.
size_t offset_ = 0; // Index into buffer as we parse through contents.
}; };
} // namespace tracing } // namespace tracing
......
...@@ -6,13 +6,15 @@ ...@@ -6,13 +6,15 @@
#include <cstdint> #include <cstdint>
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_buffer_android.h" #include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_buffer_android.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace tracing { namespace tracing {
TEST(HprofBufferTest, VerifyBasicGetBytes) { TEST(HprofBufferTest, VerifyBasicGetBytes) {
unsigned char file_data[7]{1, 1, 1, 1, 1, 1, 1}; const int length = 7;
HprofBuffer hprof(file_data, 7); unsigned char file_data[length]{1, 1, 1, 1, 1, 1, 1};
HprofBuffer hprof(file_data, length);
EXPECT_EQ(hprof.GetOneByte(), 1u); EXPECT_EQ(hprof.GetOneByte(), 1u);
EXPECT_EQ(hprof.GetTwoBytes(), 257u); EXPECT_EQ(hprof.GetTwoBytes(), 257u);
EXPECT_EQ(hprof.GetFourBytes(), 16843009u); EXPECT_EQ(hprof.GetFourBytes(), 16843009u);
...@@ -20,8 +22,9 @@ TEST(HprofBufferTest, VerifyBasicGetBytes) { ...@@ -20,8 +22,9 @@ TEST(HprofBufferTest, VerifyBasicGetBytes) {
} }
TEST(HprofBufferTest, VerifyBasicGetId) { TEST(HprofBufferTest, VerifyBasicGetId) {
unsigned char file_data[12]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; const int length = 12;
HprofBuffer hprof(file_data, 12); unsigned char file_data[length]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
HprofBuffer hprof(file_data, length);
EXPECT_EQ(hprof.GetId(), 16843009u); EXPECT_EQ(hprof.GetId(), 16843009u);
hprof.set_id_size(8); hprof.set_id_size(8);
EXPECT_EQ(hprof.GetId(), 72340172838076673u); EXPECT_EQ(hprof.GetId(), 72340172838076673u);
...@@ -29,8 +32,9 @@ TEST(HprofBufferTest, VerifyBasicGetId) { ...@@ -29,8 +32,9 @@ TEST(HprofBufferTest, VerifyBasicGetId) {
} }
TEST(HprofBufferTest, VerifyBasicPositionalMethods) { TEST(HprofBufferTest, VerifyBasicPositionalMethods) {
unsigned char file_data[4]{1, 2, 3, 4}; const int length = 4;
HprofBuffer hprof(file_data, 4); unsigned char file_data[length]{1, 2, 3, 4};
HprofBuffer hprof(file_data, length);
EXPECT_EQ(hprof.GetOneByte(), 1u); EXPECT_EQ(hprof.GetOneByte(), 1u);
hprof.Skip(2); hprof.Skip(2);
EXPECT_EQ(hprof.GetOneByte(), 4u); EXPECT_EQ(hprof.GetOneByte(), 4u);
...@@ -39,4 +43,41 @@ TEST(HprofBufferTest, VerifyBasicPositionalMethods) { ...@@ -39,4 +43,41 @@ TEST(HprofBufferTest, VerifyBasicPositionalMethods) {
hprof.set_position(1); hprof.set_position(1);
EXPECT_EQ(hprof.GetOneByte(), 2u); EXPECT_EQ(hprof.GetOneByte(), 2u);
} }
TEST(HprofBufferTest, VerifySkipIdAndDataPositionMethods) {
const int length = 12;
unsigned char file_data[length]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
HprofBuffer hprof(file_data, length);
EXPECT_EQ(*hprof.DataPosition(), 1u);
hprof.SkipId();
EXPECT_EQ(*hprof.DataPosition(), 5u);
hprof.set_id_size(8);
hprof.SkipId();
EXPECT_EQ(hprof.HasRemaining(), false);
}
TEST(HprofBufferTest, VerifySizeOfTypeMethod) {
const int length = 34;
unsigned char file_data[length]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
HprofBuffer hprof(file_data, length);
unsigned correct_sizes[] = {4, 4, 4, 4, 1, 2, 4, 8, 1, 2, 4, 8};
for (uint32_t i = 0; i < 12; i++) {
EXPECT_EQ(hprof.SizeOfType(i), correct_sizes[i]);
}
DataType data_types[9]{DataType::OBJECT, DataType::BOOLEAN, DataType::CHAR,
DataType::FLOAT, DataType::DOUBLE, DataType::BYTE,
DataType::SHORT, DataType::INT, DataType::LONG};
size_t correct_offsets[] = {4, 5, 7, 11, 19, 20, 22, 26, 34};
for (uint32_t i = 0; i < 9; i++) {
hprof.SkipBytesByType(data_types[i]);
EXPECT_EQ(hprof.offset(), correct_offsets[i]);
}
EXPECT_EQ(hprof.HasRemaining(), false);
}
} // namespace tracing } // namespace tracing
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_DATA_TYPE_ANDROID_H_
#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_DATA_TYPE_ANDROID_H_
namespace tracing {
// These are the possible primitive types that can be read in by the parser.
// They are each associated with an id number that is read in by the parser.
// NOTE: The field values are used to index elements in the arrays below.
enum DataType {
OBJECT = 2,
BOOLEAN = 4,
CHAR = 5,
FLOAT = 6,
DOUBLE = 7,
BYTE = 8,
SHORT = 9,
INT = 10,
LONG = 11
};
// These are the sizes of the above primitive types indexed by their id.
// The OBJECT type has size based off the id size (4 or 8). This is determined
// only after the parser begins running. Thus, it's stored as 0 in this array.
constexpr unsigned kTypeSizes[] = {0, 0, 0, 0, 1, 2, 4, 8, 1, 2, 4, 8};
// These are the string representations of the above primitive types indexed by
// their id.
constexpr const char* kPrimitiveArrayStrings[] = {
"0", "0", "0", "0", "bool[]", "char[]",
"float[]", "double[]", "byte[]", "short[]", "int[]", "long[]"};
inline const char* GetTypeString(uint32_t index) {
return kPrimitiveArrayStrings[index];
}
} // namespace tracing
#endif // SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_DATA_TYPE_ANDROID_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_instances_android.h"
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_data_type_android.h"
namespace tracing {
Instance::Instance(uint64_t object_id, std::string type_name)
: type_name(type_name), object_id(object_id) {}
Instance::Instance(uint64_t object_id, uint32_t size)
: object_id(object_id), size(size) {}
Instance::Instance(uint64_t object_id, uint32_t size, std::string type_name)
: type_name(type_name), object_id(object_id), size(size) {}
Instance::~Instance() {}
Instance::Instance(const Instance& other) = default;
ClassInstance::ClassInstance(uint64_t object_id,
uint64_t class_id,
uint32_t temp_data_position,
uint32_t size)
: base_instance(object_id, size),
class_id(class_id),
temp_data_position(temp_data_position) {}
Field::Field(std::string name, DataType type, uint64_t object_id)
: name(name), type(type), object_id(object_id) {}
ClassObject::~ClassObject() {}
ClassObject::ClassObject(uint64_t object_id, std::string type_name)
: base_instance(object_id, type_name) {}
ObjectArrayInstance::ObjectArrayInstance(uint64_t object_id,
uint64_t class_id,
uint32_t temp_data_position,
uint32_t temp_data_length,
uint32_t size)
: base_instance(object_id, size),
class_id(class_id),
temp_data_position(temp_data_position),
temp_data_length(temp_data_length) {}
PrimitiveArrayInstance::PrimitiveArrayInstance(uint64_t object_id,
DataType type,
std::string type_name,
uint32_t size)
: base_instance(object_id, size, type_name), type(type) {}
} // namespace tracing
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_INSTANCES_ANDROID_H_
#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_INSTANCES_ANDROID_H_
#include <ostream>
#include <vector>
#include "base/component_export.h"
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_data_type_android.h"
namespace tracing {
// The base attributes across each type of Instance to that are needed to create
// heap_graph objects. Each of ClassInstance, ClassObject, ObjectArrayInstance,
// and PrimitiveArrayInstance all contain a |base_instance| of type Instance.
struct COMPONENT_EXPORT(TRACING_CPP) Instance {
// Reference from another instance where the current instance is defined.
struct Reference {
std::string referred_by_name;
uint64_t referred_from_object_id;
};
Instance(uint64_t object_id, std::string type_name);
Instance(uint64_t object_id, uint32_t size);
Instance(uint64_t object_id, uint32_t size, std::string type_name);
Instance(const Instance& other);
~Instance();
// Only set in first pass for ClassObject and PrimitiveArrayInstances.
std::string type_name;
const uint64_t object_id; // Always set in first pass.
uint32_t size; // Set in first pass except for ClassObject
std::vector<Reference> references; // Always set on second pass.
};
// A single instance of a particular class. There can be multiple class
// instances of a single class.
struct COMPONENT_EXPORT(TRACING_CPP) ClassInstance {
ClassInstance(uint64_t object_id,
uint64_t class_id,
uint32_t temp_data_position,
uint32_t size);
Instance base_instance;
const uint64_t class_id;
// When resolving references from one reference to another, need to reset
// the hprof offset to temp_data_position and read in the bytes located at
// this specific location within the buffer. The bytes here represent the
// instance fields of the current class instance. The parser will make a note
// of a reference from any instance fields that are objects to the current
// ClassInstance.
const uint32_t temp_data_position;
};
// Stores data about a static or instance field within a class object.
struct COMPONENT_EXPORT(TRACING_CPP) Field {
Field(std::string name, DataType type, uint64_t object_id);
const std::string name;
const DataType type;
const uint64_t object_id;
};
// An instance that contains the information associated with a given class's
// architecture. There can only be one class object per class.
struct COMPONENT_EXPORT(TRACING_CPP) ClassObject {
~ClassObject();
ClassObject(uint64_t object_id, std::string type_name);
Instance base_instance;
uint64_t instance_size;
std::vector<Field> instance_fields;
std::vector<Field> static_fields;
};
// An instance that is an array of object_ids of a specific class object
struct COMPONENT_EXPORT(TRACING_CPP) ObjectArrayInstance {
ObjectArrayInstance(uint64_t object_id,
uint64_t class_id,
uint32_t temp_data_position,
uint32_t temp_data_length,
uint32_t size);
Instance base_instance;
const uint64_t class_id;
// When resolving references from one reference to another, need to reset
// the hprof offset to temp_data_position and read in the object_ids located
// at this specific location within the buffer. The parser will make a note of
// a reference from each instance with given object_id to the current
// ObjectArrayInstance.
const uint32_t temp_data_position;
// The length of the array of object_ids at temp_data_position.
const uint32_t temp_data_length;
};
// An instance that is an array of primitive objects.
struct COMPONENT_EXPORT(TRACING_CPP) PrimitiveArrayInstance {
PrimitiveArrayInstance(uint64_t object_id,
DataType type,
std::string type_name,
uint32_t size);
Instance base_instance;
const DataType type;
};
} // namespace tracing
#endif // SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_INSTANCES_ANDROID_H_
...@@ -6,11 +6,18 @@ ...@@ -6,11 +6,18 @@
#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_PARSER_ANDROID_H_ #define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_JAVA_HEAP_PROFILER_HPROF_PARSER_ANDROID_H_
#include <string> #include <string>
#include <unordered_map>
#include "base/component_export.h" #include "base/component_export.h"
#include "base/gtest_prod_util.h"
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_buffer_android.h"
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_instances_android.h"
namespace tracing { namespace tracing {
using ObjectId = uint64_t;
const uint64_t kInvalidObjectId = std::numeric_limits<uint64_t>::max();
// This class takes in a temporary file_path where Java API endpoint // This class takes in a temporary file_path where Java API endpoint
// Debug.dumpHprofData() dumps hprof data to. This file is then parsed for // Debug.dumpHprofData() dumps hprof data to. This file is then parsed for
// references between different instances. The format is defined by said method. // references between different instances. The format is defined by said method.
...@@ -25,6 +32,7 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofParser { ...@@ -25,6 +32,7 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofParser {
PARSE_SUCCESS, PARSE_SUCCESS,
PARSE_FAILED, PARSE_FAILED,
FAILED_TO_OPEN_FILE, FAILED_TO_OPEN_FILE,
STRING_ID_NOT_FOUND,
}; };
struct ParseStats { struct ParseStats {
...@@ -32,16 +40,52 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofParser { ...@@ -32,16 +40,52 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofParser {
ParseStats(const ParseStats&) = delete; ParseStats(const ParseStats&) = delete;
ParseStats& operator=(const ParseStats&) = delete; ParseStats& operator=(const ParseStats&) = delete;
// Returns the result of the parser // Returns the result of the parser. Set to fail by default.
ParseResult result = PARSE_FAILED; ParseResult result = PARSE_FAILED;
// Number of strings found in heap dump.
uint64_t num_strings = 0;
// Number of class objects found in a class heap dump.
uint64_t num_class_objects = 0;
// Number of heap dump segments found in a heap dump.
uint64_t num_heap_dump_segments = 0;
// Number of class object dumps found within a heap dump segment.
uint64_t num_class_object_dumps = 0;
// Number of class instance dumps found within a heap dump segment.
uint64_t num_class_instance_dumps = 0;
// Number of object array dumps found within a heap dump segment.
uint64_t num_object_array_dumps = 0;
// Number of primitive array dumps found within a heap dump segment.
uint64_t num_primitive_array_dumps = 0;
};
// This stores a reference to the location of the string in the hprof buffer.
// The data will only be read once when calling the GetString() for the
// first time. This avoids paging in all strings in the hprof file.
struct StringReference {
StringReference(const char* string_position, size_t length);
~StringReference();
const std::string& GetString();
const char* string_position;
size_t length = 0;
std::unique_ptr<std::string> cached_copy;
}; };
HprofParser(const std::string& file_path); HprofParser(const std::string& file_path);
~HprofParser();
HprofParser(const HprofParser&) = delete; HprofParser(const HprofParser&) = delete;
HprofParser& operator=(const HprofParser&) = delete; HprofParser& operator=(const HprofParser&) = delete;
// This method should only be called after Parse() has been called. // This method should only be called after Parse() has been called.
const ParseStats& parse_stats() { return parse_stats_; } const ParseStats& parse_stats() const { return parse_stats_; }
// First opens the file at |file_path_| then passes the data to ParseFileData // First opens the file at |file_path_| then passes the data to ParseFileData
// to parse and record metrics. The hprof file is generated by // to parse and record metrics. The hprof file is generated by
...@@ -50,9 +94,35 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofParser { ...@@ -50,9 +94,35 @@ class COMPONENT_EXPORT(TRACING_CPP) HprofParser {
ParseResult Parse(); ParseResult Parse();
private: private:
FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseStringTag);
FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseClassTag);
FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseClassObjectDumpSubtag);
FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseClassInstanceDumpSubtag);
FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParseObjectArrayDumpSubtag);
FRIEND_TEST_ALL_PREFIXES(HprofParserTest, ParsePrimitiveArrayDumpSubtag);
// Parses hprof data file_data and records metrics in parse_stats_. // Parses hprof data file_data and records metrics in parse_stats_.
void ParseFileData(const unsigned char* file_data, size_t file_size); void ParseFileData(const unsigned char* file_data, size_t file_size);
ParseResult ParseStringTag(uint32_t record_length_);
ParseResult ParseClassTag();
ParseResult ParseClassObjectDumpSubtag();
ParseResult ParseClassInstanceDumpSubtag();
ParseResult ParseObjectArrayDumpSubtag();
ParseResult ParsePrimitiveArrayDumpSubtag();
ParseResult ParseHeapDumpTag(uint32_t record_length_);
std::unordered_map<ObjectId, std::unique_ptr<StringReference>> strings_;
std::unordered_map<ObjectId, std::unique_ptr<ClassObject>> class_objects_;
std::unordered_map<ObjectId, std::unique_ptr<ClassInstance>> class_instances_;
std::unordered_map<ObjectId, std::unique_ptr<ObjectArrayInstance>>
object_array_instances_;
std::unordered_map<ObjectId, std::unique_ptr<PrimitiveArrayInstance>>
primitive_array_instances_;
std::unique_ptr<HprofBuffer> hprof_;
const std::string file_path_; const std::string file_path_;
ParseStats parse_stats_; ParseStats parse_stats_;
}; };
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_parser_android.h" #include "services/tracing/public/cpp/perfetto/java_heap_profiler/hprof_parser_android.h"
#include "base/android/java_heap_dump_generator.h" #include "base/android/java_heap_dump_generator.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h" #include "base/files/scoped_temp_dir.h"
...@@ -11,7 +12,7 @@ ...@@ -11,7 +12,7 @@
namespace tracing { namespace tracing {
TEST(HprofParserTest, ValidateEmptyParser) { TEST(HprofParserTest, BasicParse) {
base::ScopedTempDir temp_dir; base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir()) { if (!temp_dir.CreateUniqueTempDir()) {
VLOG(0) << "Failed to create unique temporary directory."; VLOG(0) << "Failed to create unique temporary directory.";
...@@ -23,6 +24,18 @@ TEST(HprofParserTest, ValidateEmptyParser) { ...@@ -23,6 +24,18 @@ TEST(HprofParserTest, ValidateEmptyParser) {
base::android::WriteJavaHeapDumpToPath(file_path_str); base::android::WriteJavaHeapDumpToPath(file_path_str);
HprofParser parser(file_path_str); HprofParser parser(file_path_str);
parser.Parse(); parser.Parse();
static const uint64_t kObjectCountThreshold = 100;
EXPECT_GT(parser.parse_stats().num_strings, kObjectCountThreshold);
EXPECT_GT(parser.parse_stats().num_class_objects, kObjectCountThreshold);
EXPECT_GT(parser.parse_stats().num_heap_dump_segments, kObjectCountThreshold);
EXPECT_GT(parser.parse_stats().num_class_object_dumps, kObjectCountThreshold);
EXPECT_GT(parser.parse_stats().num_class_instance_dumps,
kObjectCountThreshold);
EXPECT_GT(parser.parse_stats().num_object_array_dumps, kObjectCountThreshold);
EXPECT_GT(parser.parse_stats().num_primitive_array_dumps,
kObjectCountThreshold);
EXPECT_EQ(parser.parse_stats().result, EXPECT_EQ(parser.parse_stats().result,
HprofParser::ParseResult::PARSE_SUCCESS); HprofParser::ParseResult::PARSE_SUCCESS);
} }
...@@ -34,4 +47,147 @@ TEST(HprofParserTest, InvalidPathWithNoDump) { ...@@ -34,4 +47,147 @@ TEST(HprofParserTest, InvalidPathWithNoDump) {
HprofParser::ParseResult::FAILED_TO_OPEN_FILE); HprofParser::ParseResult::FAILED_TO_OPEN_FILE);
} }
TEST(HprofParserTest, ParseStringTag) {
const int length = 8;
unsigned char file_data[length]{0, 0, 0, 1, // string_id
116, 101, 115, 116}; // "test"
HprofParser parser("dummy_file");
parser.hprof_ = std::make_unique<HprofBuffer>(
reinterpret_cast<const unsigned char*>(file_data), length);
parser.ParseStringTag(8);
EXPECT_NE(parser.strings_.find(1), parser.strings_.end());
EXPECT_EQ(parser.strings_[1]->GetString(), "test");
}
TEST(HprofParserTest, ParseClassTag) {
const int length = 20;
unsigned char file_data[length]{0, 0, 0, 0, 0, 0, 0, 2, // object_id
0, 0, 0, 0, 0, 0, 0, 1, // string_id
116, 101, 115, 116}; // "test"
HprofParser parser("dummy_file");
parser.hprof_ = std::make_unique<HprofBuffer>(
reinterpret_cast<const unsigned char*>(file_data), length);
parser.strings_.emplace(
1, std::make_unique<HprofParser::StringReference>(
reinterpret_cast<const char*>(file_data + 16), 4));
parser.ParseClassTag();
auto it = parser.class_objects_.find(2);
EXPECT_NE(it, parser.class_objects_.end());
EXPECT_EQ(it->second->base_instance.type_name, "test");
EXPECT_EQ(it->second->base_instance.object_id, 2u);
}
TEST(HprofParserTest, ParseClassObjectDumpSubtag) {
const int length = 64;
unsigned char file_data[length]{
0, 0, 0, 3, // object_id
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, // instance_size
0, 1, // constant_pool_size
0, 0,
4, // type_index
0, 0, 1, // num_static_fields
0, 0, 0, 1, // string_id
2, // type_index
0, 0, 0, 2, // object_id
0, 1, // num_instance_fields
0, 0, 0, 1, // string_id
4, // type_index
116, 101, 115, 116 // "test"
};
HprofParser parser("dummy_file");
parser.hprof_ = std::make_unique<HprofBuffer>(
reinterpret_cast<const unsigned char*>(file_data), length);
parser.strings_.emplace(
1, std::make_unique<HprofParser::StringReference>(
reinterpret_cast<const char*>(file_data + 60), 4));
parser.class_objects_.emplace(
3, std::make_unique<ClassObject>(3, "class_obj_dummy"));
parser.ParseClassObjectDumpSubtag();
auto it = parser.class_objects_.find(3);
EXPECT_NE(it, parser.class_objects_.end());
Field static_dummy_field = it->second->static_fields.back();
EXPECT_EQ(static_dummy_field.name, "test");
EXPECT_EQ(static_dummy_field.type, DataType::OBJECT);
EXPECT_EQ(static_dummy_field.object_id, 2u);
Field instance_dummy_field = it->second->instance_fields.back();
EXPECT_EQ(instance_dummy_field.name, "test");
EXPECT_EQ(instance_dummy_field.type, DataType::BOOLEAN);
EXPECT_EQ(instance_dummy_field.object_id, kInvalidObjectId);
EXPECT_EQ(it->second->base_instance.size, 4u);
EXPECT_EQ(it->second->instance_size, 8u);
}
TEST(HprofParserTest, ParseClassInstanceDumpSubtag) {
const int length = 20;
unsigned char file_data[length]{0, 0, 0, 2, // object_id
0, 0, 0, 0, 0, 0, 0, 7, // class_id
0, 0, 0, 4, // instance_size
0, 0, 0, 0};
HprofParser parser("dummy_file");
parser.hprof_ = std::make_unique<HprofBuffer>(
reinterpret_cast<const unsigned char*>(file_data), length);
parser.ParseClassInstanceDumpSubtag();
auto it = parser.class_instances_.find(2);
EXPECT_NE(it, parser.class_instances_.end());
EXPECT_EQ(it->second->class_id, 7u);
EXPECT_EQ(it->second->base_instance.object_id, 2u);
EXPECT_EQ(it->second->temp_data_position, 16u);
EXPECT_EQ(it->second->base_instance.size, 4u);
}
TEST(HprofParserTest, ParseObjectArrayDumpSubtag) {
const int length = 24;
unsigned char file_data[length]{0, 0, 0, 2, // object_id
0, 0, 0, 0, 0, 0, 0, 2, // length
0, 0, 0, 4, // class_id
0, 0, 0, 0, 0, 0, 0, 0};
HprofParser parser("dummy_file");
parser.hprof_ = std::make_unique<HprofBuffer>(
reinterpret_cast<const unsigned char*>(file_data), length);
parser.ParseObjectArrayDumpSubtag();
auto it = parser.object_array_instances_.find(2);
EXPECT_NE(it, parser.object_array_instances_.end());
EXPECT_EQ(it->second->class_id, 4u);
EXPECT_EQ(it->second->base_instance.object_id, 2u);
EXPECT_EQ(it->second->temp_data_position, 16u);
EXPECT_EQ(it->second->temp_data_length, 2u);
EXPECT_EQ(it->second->base_instance.size, 8u);
}
TEST(HprofParserTest, ParsePrimitiveArrayDumpSubtag) {
const int length = 17;
unsigned char file_data[length]{0, 0, 0, 2, // object_id
0, 0, 0, 0, 0, 0, 0, 4, // length
4, // type_index
0, 0, 0, 0};
HprofParser parser("dummy_file");
parser.hprof_ = std::make_unique<HprofBuffer>(
reinterpret_cast<const unsigned char*>(file_data), length);
parser.ParsePrimitiveArrayDumpSubtag();
auto it = parser.primitive_array_instances_.find(2);
EXPECT_NE(it, parser.primitive_array_instances_.end());
EXPECT_EQ(it->second->base_instance.object_id, 2u);
EXPECT_EQ(it->second->type, DataType::BOOLEAN);
EXPECT_EQ(it->second->base_instance.type_name, "bool[]");
EXPECT_EQ(it->second->base_instance.size, 4u);
}
} // namespace tracing } // namespace tracing
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment