[profiler] add Serialize to v8::CpuProfile

Support JSON serialization in `v8::CpuProfile`

Bug: v8:13291
Change-Id: I638cf2c1f7acba9c5b8a2932d84c9882d968c90d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3905128
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83901}
This commit is contained in:
Juan José Arboleda 2022-10-13 18:16:03 -05:00 committed by V8 LUCI CQ
parent 0486ef3727
commit 69ad9552b2
13 changed files with 505 additions and 185 deletions

View File

@ -151,6 +151,7 @@ Jiaxun Yang <jiaxun.yang@flygoat.com>
Joel Stanley <joel@jms.id.au>
Johan Bergström <johan@bergstroem.nu>
Jonathan Liu <net147@gmail.com>
Juan Arboleda <soyjuanarbol@gmail.com>
Julien Brianceau <jbriance@cisco.com>
JunHo Seo <sejunho@gmail.com>
Junha Park <jpark3@scu.edu>

View File

@ -1966,6 +1966,7 @@ filegroup(
"src/profiler/heap-snapshot-generator-inl.h",
"src/profiler/heap-snapshot-generator.cc",
"src/profiler/heap-snapshot-generator.h",
"src/profiler/output-stream-writer.h",
"src/profiler/profile-generator-inl.h",
"src/profiler/profile-generator.cc",
"src/profiler/profile-generator.h",

View File

@ -3436,6 +3436,7 @@ v8_header_set("v8_internal_headers") {
"src/profiler/heap-profiler.h",
"src/profiler/heap-snapshot-generator-inl.h",
"src/profiler/heap-snapshot-generator.h",
"src/profiler/output-stream-writer.h",
"src/profiler/profile-generator-inl.h",
"src/profiler/profile-generator.h",
"src/profiler/profiler-listener.h",

View File

@ -175,6 +175,32 @@ class V8_EXPORT CpuProfileNode {
static const int kNoColumnNumberInfo = Message::kNoColumnInfo;
};
/**
* An interface for exporting data from V8, using "push" model.
*/
class V8_EXPORT OutputStream {
public:
enum WriteResult { kContinue = 0, kAbort = 1 };
virtual ~OutputStream() = default;
/** Notify about the end of stream. */
virtual void EndOfStream() = 0;
/** Get preferred output chunk size. Called only once. */
virtual int GetChunkSize() { return 1024; }
/**
* Writes the next chunk of snapshot data into the stream. Writing
* can be stopped by returning kAbort as function result. EndOfStream
* will not be called in case writing was aborted.
*/
virtual WriteResult WriteAsciiChunk(char* data, int size) = 0;
/**
* Writes the next chunk of heap stats data into the stream. Writing
* can be stopped by returning kAbort as function result. EndOfStream
* will not be called in case writing was aborted.
*/
virtual WriteResult WriteHeapStatsChunk(HeapStatsUpdate* data, int count) {
return kAbort;
}
};
/**
* CpuProfile contains a CPU profile in a form of top-down call tree
@ -182,6 +208,9 @@ class V8_EXPORT CpuProfileNode {
*/
class V8_EXPORT CpuProfile {
public:
enum SerializationFormat {
kJSON = 0 // See format description near 'Serialize' method.
};
/** Returns CPU profile title. */
Local<String> GetTitle() const;
@ -235,6 +264,25 @@ class V8_EXPORT CpuProfile {
* All pointers to nodes previously returned become invalid.
*/
void Delete();
/**
* Prepare a serialized representation of the profile. The result
* is written into the stream provided in chunks of specified size.
*
* For the JSON format, heap contents are represented as an object
* with the following structure:
*
* {
* nodes: [nodes array],
* startTime: number,
* endTime: number
* samples: [strings array]
* timeDeltas: [numbers array]
* }
*
*/
void Serialize(OutputStream* stream,
SerializationFormat format = kJSON) const;
};
enum CpuProfilingMode {
@ -576,37 +624,6 @@ class V8_EXPORT HeapGraphNode {
const HeapGraphEdge* GetChild(int index) const;
};
/**
* An interface for exporting data from V8, using "push" model.
*/
class V8_EXPORT OutputStream {
public:
enum WriteResult {
kContinue = 0,
kAbort = 1
};
virtual ~OutputStream() = default;
/** Notify about the end of stream. */
virtual void EndOfStream() = 0;
/** Get preferred output chunk size. Called only once. */
virtual int GetChunkSize() { return 1024; }
/**
* Writes the next chunk of snapshot data into the stream. Writing
* can be stopped by returning kAbort as function result. EndOfStream
* will not be called in case writing was aborted.
*/
virtual WriteResult WriteAsciiChunk(char* data, int size) = 0;
/**
* Writes the next chunk of heap stats data into the stream. Writing
* can be stopped by returning kAbort as function result. EndOfStream
* will not be called in case writing was aborted.
*/
virtual WriteResult WriteHeapStatsChunk(HeapStatsUpdate* data, int count) {
return kAbort;
}
};
/**
* HeapSnapshots record the state of the JS heap at some moment.
*/

View File

@ -10067,6 +10067,21 @@ int64_t CpuProfile::GetEndTime() const {
return profile->end_time().since_origin().InMicroseconds();
}
static i::CpuProfile* ToInternal(const CpuProfile* profile) {
return const_cast<i::CpuProfile*>(
reinterpret_cast<const i::CpuProfile*>(profile));
}
void CpuProfile::Serialize(OutputStream* stream,
CpuProfile::SerializationFormat format) const {
Utils::ApiCheck(format == kJSON, "v8::CpuProfile::Serialize",
"Unknown serialization format");
Utils::ApiCheck(stream->GetChunkSize() > 0, "v8::CpuProfile::Serialize",
"Invalid stream chunk size");
i::CpuProfileJSONSerializer serializer(ToInternal(this));
serializer.Serialize(stream);
}
int CpuProfile::GetSamplesCount() const {
return reinterpret_cast<const i::CpuProfile*>(this)->samples_count();
}

View File

@ -38,6 +38,7 @@
#include "src/profiler/allocation-tracker.h"
#include "src/profiler/heap-profiler.h"
#include "src/profiler/heap-snapshot-generator-inl.h"
#include "src/profiler/output-stream-writer.h"
namespace v8 {
namespace internal {
@ -2788,111 +2789,6 @@ bool HeapSnapshotGenerator::FillReferences() {
dom_explorer_.IterateAndExtractReferences(this);
}
template<int bytes> struct MaxDecimalDigitsIn;
template <>
struct MaxDecimalDigitsIn<1> {
static const int kSigned = 3;
static const int kUnsigned = 3;
};
template<> struct MaxDecimalDigitsIn<4> {
static const int kSigned = 11;
static const int kUnsigned = 10;
};
template<> struct MaxDecimalDigitsIn<8> {
static const int kSigned = 20;
static const int kUnsigned = 20;
};
class OutputStreamWriter {
public:
explicit OutputStreamWriter(v8::OutputStream* stream)
: stream_(stream),
chunk_size_(stream->GetChunkSize()),
chunk_(chunk_size_),
chunk_pos_(0),
aborted_(false) {
DCHECK_GT(chunk_size_, 0);
}
bool aborted() { return aborted_; }
void AddCharacter(char c) {
DCHECK_NE(c, '\0');
DCHECK(chunk_pos_ < chunk_size_);
chunk_[chunk_pos_++] = c;
MaybeWriteChunk();
}
void AddString(const char* s) {
size_t len = strlen(s);
DCHECK_GE(kMaxInt, len);
AddSubstring(s, static_cast<int>(len));
}
void AddSubstring(const char* s, int n) {
if (n <= 0) return;
DCHECK_LE(n, strlen(s));
const char* s_end = s + n;
while (s < s_end) {
int s_chunk_size =
std::min(chunk_size_ - chunk_pos_, static_cast<int>(s_end - s));
DCHECK_GT(s_chunk_size, 0);
MemCopy(chunk_.begin() + chunk_pos_, s, s_chunk_size);
s += s_chunk_size;
chunk_pos_ += s_chunk_size;
MaybeWriteChunk();
}
}
void AddNumber(unsigned n) { AddNumberImpl<unsigned>(n, "%u"); }
void Finalize() {
if (aborted_) return;
DCHECK(chunk_pos_ < chunk_size_);
if (chunk_pos_ != 0) {
WriteChunk();
}
stream_->EndOfStream();
}
private:
template<typename T>
void AddNumberImpl(T n, const char* format) {
// Buffer for the longest value plus trailing \0
static const int kMaxNumberSize =
MaxDecimalDigitsIn<sizeof(T)>::kUnsigned + 1;
if (chunk_size_ - chunk_pos_ >= kMaxNumberSize) {
int result = SNPrintF(
chunk_.SubVector(chunk_pos_, chunk_size_), format, n);
DCHECK_NE(result, -1);
chunk_pos_ += result;
MaybeWriteChunk();
} else {
base::EmbeddedVector<char, kMaxNumberSize> buffer;
int result = SNPrintF(buffer, format, n);
USE(result);
DCHECK_NE(result, -1);
AddString(buffer.begin());
}
}
void MaybeWriteChunk() {
DCHECK(chunk_pos_ <= chunk_size_);
if (chunk_pos_ == chunk_size_) {
WriteChunk();
}
}
void WriteChunk() {
if (aborted_) return;
if (stream_->WriteAsciiChunk(chunk_.begin(), chunk_pos_) ==
v8::OutputStream::kAbort)
aborted_ = true;
chunk_pos_ = 0;
}
v8::OutputStream* stream_;
int chunk_size_;
base::ScopedVector<char> chunk_;
int chunk_pos_;
bool aborted_;
};
// type, name|index, to_node.
const int HeapSnapshotJSONSerializer::kEdgeFieldsCount = 3;
// type, name, id, self_size, edge_count, trace_node_id, detachedness.
const int HeapSnapshotJSONSerializer::kNodeFieldsCount = 7;

View File

@ -0,0 +1,129 @@
// Copyright 2022 the V8 project 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 V8_PROFILER_OUTPUT_STREAM_WRITER_H_
#define V8_PROFILER_OUTPUT_STREAM_WRITER_H_
#include <algorithm>
#include <string>
#include "include/v8-profiler.h"
#include "src/base/logging.h"
#include "src/base/strings.h"
#include "src/base/vector.h"
#include "src/common/globals.h"
#include "src/utils/memcopy.h"
namespace v8 {
namespace internal {
template <int bytes>
struct MaxDecimalDigitsIn;
template <>
struct MaxDecimalDigitsIn<1> {
static const int kSigned = 3;
static const int kUnsigned = 3;
};
template <>
struct MaxDecimalDigitsIn<4> {
static const int kSigned = 11;
static const int kUnsigned = 10;
};
template <>
struct MaxDecimalDigitsIn<8> {
static const int kSigned = 20;
static const int kUnsigned = 20;
};
class OutputStreamWriter {
public:
explicit OutputStreamWriter(v8::OutputStream* stream)
: stream_(stream),
chunk_size_(stream->GetChunkSize()),
chunk_(chunk_size_),
chunk_pos_(0),
aborted_(false) {
DCHECK_GT(chunk_size_, 0);
}
bool aborted() { return aborted_; }
void AddCharacter(char c) {
DCHECK_NE(c, '\0');
DCHECK(chunk_pos_ < chunk_size_);
chunk_[chunk_pos_++] = c;
MaybeWriteChunk();
}
void AddString(const char* s) {
size_t len = strlen(s);
DCHECK_GE(kMaxInt, len);
AddSubstring(s, static_cast<int>(len));
}
void AddSubstring(const char* s, int n) {
if (n <= 0) return;
DCHECK_LE(n, strlen(s));
const char* s_end = s + n;
while (s < s_end) {
int s_chunk_size =
std::min(chunk_size_ - chunk_pos_, static_cast<int>(s_end - s));
DCHECK_GT(s_chunk_size, 0);
MemCopy(chunk_.begin() + chunk_pos_, s, s_chunk_size);
s += s_chunk_size;
chunk_pos_ += s_chunk_size;
MaybeWriteChunk();
}
}
void AddNumber(unsigned n) { AddNumberImpl<unsigned>(n, "%u"); }
void Finalize() {
if (aborted_) return;
DCHECK(chunk_pos_ < chunk_size_);
if (chunk_pos_ != 0) {
WriteChunk();
}
stream_->EndOfStream();
}
private:
template <typename T>
void AddNumberImpl(T n, const char* format) {
// Buffer for the longest value plus trailing \0
static const int kMaxNumberSize =
MaxDecimalDigitsIn<sizeof(T)>::kUnsigned + 1;
if (chunk_size_ - chunk_pos_ >= kMaxNumberSize) {
int result =
SNPrintF(chunk_.SubVector(chunk_pos_, chunk_size_), format, n);
DCHECK_NE(result, -1);
chunk_pos_ += result;
MaybeWriteChunk();
} else {
base::EmbeddedVector<char, kMaxNumberSize> buffer;
int result = SNPrintF(buffer, format, n);
USE(result);
DCHECK_NE(result, -1);
AddString(buffer.begin());
}
}
void MaybeWriteChunk() {
DCHECK(chunk_pos_ <= chunk_size_);
if (chunk_pos_ == chunk_size_) {
WriteChunk();
}
}
void WriteChunk() {
if (aborted_) return;
if (stream_->WriteAsciiChunk(chunk_.begin(), chunk_pos_) ==
v8::OutputStream::kAbort)
aborted_ = true;
chunk_pos_ = 0;
}
v8::OutputStream* stream_;
int chunk_size_;
base::ScopedVector<char> chunk_;
int chunk_pos_;
bool aborted_;
};
} // namespace internal
} // namespace v8
#endif // V8_PROFILER_OUTPUT_STREAM_WRITER_H_

View File

@ -5,12 +5,14 @@
#include "src/profiler/profile-generator.h"
#include <algorithm>
#include <vector>
#include "include/v8-profiler.h"
#include "src/base/lazy-instance.h"
#include "src/codegen/source-position.h"
#include "src/objects/shared-function-info-inl.h"
#include "src/profiler/cpu-profiler.h"
#include "src/profiler/output-stream-writer.h"
#include "src/profiler/profile-generator-inl.h"
#include "src/profiler/profiler-stats.h"
#include "src/tracing/trace-event.h"
@ -762,6 +764,160 @@ void CpuProfile::FinishProfile() {
"ProfileChunk", id_, "data", std::move(value));
}
namespace {
void FlattenNodesTree(const v8::CpuProfileNode* node,
std::vector<const v8::CpuProfileNode*>* nodes) {
nodes->emplace_back(node);
const int childrenCount = node->GetChildrenCount();
for (int i = 0; i < childrenCount; i++)
FlattenNodesTree(node->GetChild(i), nodes);
}
} // namespace
void CpuProfileJSONSerializer::Serialize(v8::OutputStream* stream) {
DCHECK_NULL(writer_);
writer_ = new OutputStreamWriter(stream);
SerializeImpl();
delete writer_;
writer_ = nullptr;
}
void CpuProfileJSONSerializer::SerializePositionTicks(
const v8::CpuProfileNode* node, int lineCount) {
std::vector<v8::CpuProfileNode::LineTick> entries(lineCount);
if (node->GetLineTicks(&entries[0], lineCount)) {
for (int i = 0; i < lineCount; i++) {
writer_->AddCharacter('{');
writer_->AddString("\"line\":");
writer_->AddNumber(entries[i].line);
writer_->AddString(",\"ticks\":");
writer_->AddNumber(entries[i].hit_count);
writer_->AddCharacter('}');
if (i != (lineCount - 1)) writer_->AddCharacter(',');
}
}
}
void CpuProfileJSONSerializer::SerializeCallFrame(
const v8::CpuProfileNode* node) {
writer_->AddString("\"functionName\":\"");
writer_->AddString(node->GetFunctionNameStr());
writer_->AddString("\",\"lineNumber\":");
writer_->AddNumber(node->GetLineNumber() - 1);
writer_->AddString(",\"columnNumber\":");
writer_->AddNumber(node->GetColumnNumber() - 1);
writer_->AddString(",\"scriptId\":");
writer_->AddNumber(node->GetScriptId());
writer_->AddString(",\"url\":\"");
writer_->AddString(node->GetScriptResourceNameStr());
writer_->AddCharacter('"');
}
void CpuProfileJSONSerializer::SerializeChildren(const v8::CpuProfileNode* node,
int childrenCount) {
for (int i = 0; i < childrenCount; i++) {
writer_->AddNumber(node->GetChild(i)->GetNodeId());
if (i != (childrenCount - 1)) writer_->AddCharacter(',');
}
}
void CpuProfileJSONSerializer::SerializeNode(const v8::CpuProfileNode* node) {
writer_->AddCharacter('{');
writer_->AddString("\"id\":");
writer_->AddNumber(node->GetNodeId());
writer_->AddString(",\"hitCount\":");
writer_->AddNumber(node->GetHitCount());
writer_->AddString(",\"callFrame\":{");
SerializeCallFrame(node);
writer_->AddCharacter('}');
const int childrenCount = node->GetChildrenCount();
if (childrenCount) {
writer_->AddString(",\"children\":[");
SerializeChildren(node, childrenCount);
writer_->AddCharacter(']');
}
const char* deoptReason = node->GetBailoutReason();
if (deoptReason && deoptReason[0] && strcmp(deoptReason, "no reason")) {
writer_->AddString(",\"deoptReason\":\"");
writer_->AddString(deoptReason);
writer_->AddCharacter('"');
}
unsigned lineCount = node->GetHitLineCount();
if (lineCount) {
writer_->AddString(",\"positionTicks\":[");
SerializePositionTicks(node, lineCount);
writer_->AddCharacter(']');
}
writer_->AddCharacter('}');
}
void CpuProfileJSONSerializer::SerializeNodes() {
std::vector<const v8::CpuProfileNode*> nodes;
FlattenNodesTree(
reinterpret_cast<const v8::CpuProfileNode*>(profile_->top_down()->root()),
&nodes);
for (size_t i = 0; i < nodes.size(); i++) {
SerializeNode(nodes.at(i));
if (writer_->aborted()) return;
if (i != (nodes.size() - 1)) writer_->AddCharacter(',');
}
}
void CpuProfileJSONSerializer::SerializeTimeDeltas() {
int count = profile_->samples_count();
uint64_t lastTime = profile_->start_time().since_origin().InMicroseconds();
for (int i = 0; i < count; i++) {
uint64_t ts = profile_->sample(i).timestamp.since_origin().InMicroseconds();
writer_->AddNumber(static_cast<int>(ts - lastTime));
if (i != (count - 1)) writer_->AddString(",");
lastTime = ts;
}
}
void CpuProfileJSONSerializer::SerializeSamples() {
int count = profile_->samples_count();
for (int i = 0; i < count; i++) {
writer_->AddNumber(profile_->sample(i).node->id());
if (i != (count - 1)) writer_->AddString(",");
}
}
void CpuProfileJSONSerializer::SerializeImpl() {
writer_->AddCharacter('{');
writer_->AddString("\"nodes\":[");
SerializeNodes();
writer_->AddString("]");
writer_->AddString(",\"startTime\":");
writer_->AddNumber(static_cast<unsigned>(
profile_->start_time().since_origin().InMicroseconds()));
writer_->AddString(",\"endTime\":");
writer_->AddNumber(static_cast<unsigned>(
profile_->end_time().since_origin().InMicroseconds()));
writer_->AddString(",\"samples\":[");
SerializeSamples();
if (writer_->aborted()) return;
writer_->AddCharacter(']');
writer_->AddString(",\"timeDeltas\":[");
SerializeTimeDeltas();
if (writer_->aborted()) return;
writer_->AddString("]");
writer_->AddCharacter('}');
writer_->Finalize();
}
void CpuProfile::Print() const {
base::OS::Print("[Top down]:\n");
top_down_.Print();

View File

@ -19,6 +19,7 @@
#include "src/builtins/builtins.h"
#include "src/execution/vm-state.h"
#include "src/logging/code-events.h"
#include "src/profiler/output-stream-writer.h"
#include "src/profiler/strings-storage.h"
#include "src/utils/allocation.h"
@ -596,6 +597,31 @@ class V8_EXPORT_PRIVATE CpuProfilesCollection {
Isolate* isolate_;
};
class CpuProfileJSONSerializer {
public:
explicit CpuProfileJSONSerializer(CpuProfile* profile)
: profile_(profile), writer_(nullptr) {}
CpuProfileJSONSerializer(const CpuProfileJSONSerializer&) = delete;
CpuProfileJSONSerializer& operator=(const CpuProfileJSONSerializer&) = delete;
void Serialize(v8::OutputStream* stream);
private:
void SerializePositionTicks(const v8::CpuProfileNode* node, int lineCount);
void SerializeCallFrame(const v8::CpuProfileNode* node);
void SerializeChildren(const v8::CpuProfileNode* node, int childrenCount);
void SerializeNode(const v8::CpuProfileNode* node);
void SerializeNodes();
void SerializeSamples();
void SerializeTimeDeltas();
void SerializeImpl();
static const int kEdgeFieldsCount;
static const int kNodeFieldsCount;
CpuProfile* profile_;
OutputStreamWriter* writer_;
};
} // namespace internal
} // namespace v8

View File

@ -144,6 +144,7 @@ v8_source_set("cctest_sources") {
"heap/test-unmapper.cc",
"heap/test-weak-references.cc",
"heap/test-write-barrier.cc",
"jsonstream-helper.h",
"manually-externalized-buffer.h",
"print-extension.cc",
"print-extension.h",

View File

@ -0,0 +1,61 @@
// Copyright 2022 the V8 project 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 V8_CCTEST_JSONTREAM_HELPER_H_
#define V8_CCTEST_JSONTREAM_HELPER_H_
#include "include/v8-profiler.h"
#include "test/cctest/collector.h"
namespace v8 {
namespace internal {
class TestJSONStream : public v8::OutputStream {
public:
TestJSONStream() : eos_signaled_(0), abort_countdown_(-1) {}
explicit TestJSONStream(int abort_countdown)
: eos_signaled_(0), abort_countdown_(abort_countdown) {}
~TestJSONStream() override = default;
void EndOfStream() override { ++eos_signaled_; }
OutputStream::WriteResult WriteAsciiChunk(char* buffer,
int chars_written) override {
if (abort_countdown_ > 0) --abort_countdown_;
if (abort_countdown_ == 0) return OutputStream::kAbort;
CHECK_GT(chars_written, 0);
v8::base::Vector<char> chunk = buffer_.AddBlock(chars_written, '\0');
i::MemCopy(chunk.begin(), buffer, chars_written);
return OutputStream::kContinue;
}
virtual WriteResult WriteUint32Chunk(uint32_t* buffer, int chars_written) {
UNREACHABLE();
}
void WriteTo(v8::base::Vector<char> dest) { buffer_.WriteTo(dest); }
int eos_signaled() { return eos_signaled_; }
int size() { return buffer_.size(); }
private:
i::Collector<char> buffer_;
int eos_signaled_;
int abort_countdown_;
};
class OneByteResource : public v8::String::ExternalOneByteStringResource {
public:
explicit OneByteResource(v8::base::Vector<char> string)
: data_(string.begin()) {
length_ = string.length();
}
const char* data() const override { return data_; }
size_t length() const override { return length_; }
private:
const char* data_;
size_t length_;
};
} // namespace internal
} // namespace v8
#endif // V8_CCTEST_JSONTREAM_HELPER_H_

View File

@ -33,6 +33,7 @@
#include "include/libplatform/v8-tracing.h"
#include "include/v8-fast-api-calls.h"
#include "include/v8-function.h"
#include "include/v8-json.h"
#include "include/v8-locker.h"
#include "include/v8-profiler.h"
#include "src/api/api-inl.h"
@ -53,6 +54,7 @@
#include "src/utils/utils.h"
#include "test/cctest/cctest.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/cctest/jsonstream-helper.h"
#include "test/cctest/profiler-extension.h"
#include "test/common/flag-utils.h"
@ -4705,6 +4707,64 @@ TEST(SkipEstimatedSizeWhenActiveProfiling) {
CHECK_GT(profiler.GetEstimatedMemoryUsage(), 0);
}
TEST(CpuProfileJSONSerialization) {
LocalContext env;
v8::HandleScope scope(env->GetIsolate());
v8::CpuProfiler* cpu_profiler = v8::CpuProfiler::New(env->GetIsolate());
v8::Local<v8::String> name = v8_str("1");
cpu_profiler->StartProfiling(name);
v8::CpuProfile* profile = cpu_profiler->StopProfiling(name);
CHECK(profile);
TestJSONStream stream;
profile->Serialize(&stream, v8::CpuProfile::kJSON);
profile->Delete();
cpu_profiler->Dispose();
CHECK_GT(stream.size(), 0);
CHECK_EQ(1, stream.eos_signaled());
base::ScopedVector<char> json(stream.size());
stream.WriteTo(json);
// Verify that snapshot string is valid JSON.
OneByteResource* json_res = new OneByteResource(json);
v8::Local<v8::String> json_string =
v8::String::NewExternalOneByte(env->GetIsolate(), json_res)
.ToLocalChecked();
v8::Local<v8::Context> context = v8::Context::New(env->GetIsolate());
v8::Local<v8::Value> profile_parse_result =
v8::JSON::Parse(context, json_string).ToLocalChecked();
CHECK(!profile_parse_result.IsEmpty());
CHECK(profile_parse_result->IsObject());
v8::Local<v8::Object> profile_obj = profile_parse_result.As<v8::Object>();
CHECK(profile_obj->Get(env.local(), v8_str("nodes"))
.ToLocalChecked()
->IsArray());
CHECK(profile_obj->Get(env.local(), v8_str("startTime"))
.ToLocalChecked()
->IsNumber());
CHECK(profile_obj->Get(env.local(), v8_str("endTime"))
.ToLocalChecked()
->IsNumber());
CHECK(profile_obj->Get(env.local(), v8_str("samples"))
.ToLocalChecked()
->IsArray());
CHECK(profile_obj->Get(env.local(), v8_str("timeDeltas"))
.ToLocalChecked()
->IsArray());
CHECK(profile_obj->Get(env.local(), v8_str("startTime"))
.ToLocalChecked()
.As<v8::Number>()
->Value() > 0);
CHECK(profile_obj->Get(env.local(), v8_str("endTime"))
.ToLocalChecked()
.As<v8::Number>()
->Value() > 0);
}
} // namespace test_cpu_profiler
} // namespace internal
} // namespace v8

View File

@ -48,6 +48,7 @@
#include "test/cctest/cctest.h"
#include "test/cctest/collector.h"
#include "test/cctest/heap/heap-utils.h"
#include "test/cctest/jsonstream-helper.h"
using i::AllocationTraceNode;
using i::AllocationTraceTree;
@ -1043,52 +1044,6 @@ TEST(HeapEntryIdsAndGC) {
CHECK_EQ(b1->GetId(), b2->GetId());
}
namespace {
class TestJSONStream : public v8::OutputStream {
public:
TestJSONStream() : eos_signaled_(0), abort_countdown_(-1) {}
explicit TestJSONStream(int abort_countdown)
: eos_signaled_(0), abort_countdown_(abort_countdown) {}
~TestJSONStream() override = default;
void EndOfStream() override { ++eos_signaled_; }
WriteResult WriteAsciiChunk(char* buffer, int chars_written) override {
if (abort_countdown_ > 0) --abort_countdown_;
if (abort_countdown_ == 0) return kAbort;
CHECK_GT(chars_written, 0);
v8::base::Vector<char> chunk = buffer_.AddBlock(chars_written, '\0');
i::MemCopy(chunk.begin(), buffer, chars_written);
return kContinue;
}
virtual WriteResult WriteUint32Chunk(uint32_t* buffer, int chars_written) {
UNREACHABLE();
}
void WriteTo(v8::base::Vector<char> dest) { buffer_.WriteTo(dest); }
int eos_signaled() { return eos_signaled_; }
int size() { return buffer_.size(); }
private:
i::Collector<char> buffer_;
int eos_signaled_;
int abort_countdown_;
};
class OneByteResource : public v8::String::ExternalOneByteStringResource {
public:
explicit OneByteResource(v8::base::Vector<char> string)
: data_(string.begin()) {
length_ = string.length();
}
const char* data() const override { return data_; }
size_t length() const override { return length_; }
private:
const char* data_;
size_t length_;
};
} // namespace
TEST(HeapSnapshotJSONSerialization) {
v8::Isolate* isolate = CcTest::isolate();
LocalContext env;
@ -1105,7 +1060,7 @@ TEST(HeapSnapshotJSONSerialization) {
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));
TestJSONStream stream;
v8::internal::TestJSONStream stream;
snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);
CHECK_GT(stream.size(), 0);
CHECK_EQ(1, stream.eos_signaled());
@ -1113,7 +1068,8 @@ TEST(HeapSnapshotJSONSerialization) {
stream.WriteTo(json);
// Verify that snapshot string is valid JSON.
OneByteResource* json_res = new OneByteResource(json);
v8::internal::OneByteResource* json_res =
new v8::internal::OneByteResource(json);
v8::Local<v8::String> json_string =
v8::String::NewExternalOneByte(env->GetIsolate(), json_res)
.ToLocalChecked();
@ -1224,7 +1180,7 @@ TEST(HeapSnapshotJSONSerializationAborting) {
v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler();
const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot();
CHECK(ValidateSnapshot(snapshot));
TestJSONStream stream(5);
v8::internal::TestJSONStream stream(5);
snapshot->Serialize(&stream, v8::HeapSnapshot::kJSON);
CHECK_GT(stream.size(), 0);
CHECK_EQ(0, stream.eos_signaled());