[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:
parent
0486ef3727
commit
69ad9552b2
1
AUTHORS
1
AUTHORS
@ -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>
|
||||
|
@ -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",
|
||||
|
1
BUILD.gn
1
BUILD.gn
@ -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",
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
129
src/profiler/output-stream-writer.h
Normal file
129
src/profiler/output-stream-writer.h
Normal 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_
|
@ -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();
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
61
test/cctest/jsonstream-helper.h
Normal file
61
test/cctest/jsonstream-helper.h
Normal 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_
|
@ -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
|
||||
|
@ -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());
|
||||
|
Loading…
Reference in New Issue
Block a user