From 69ad9552b28299558b52eb002daf949b8f9e8bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Arboleda?= Date: Thu, 13 Oct 2022 18:16:03 -0500 Subject: [PATCH] [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 Reviewed-by: Camillo Bruni Cr-Commit-Position: refs/heads/main@{#83901} --- AUTHORS | 1 + BUILD.bazel | 1 + BUILD.gn | 1 + include/v8-profiler.h | 79 +++++++----- src/api/api.cc | 15 +++ src/profiler/heap-snapshot-generator.cc | 106 +--------------- src/profiler/output-stream-writer.h | 129 ++++++++++++++++++++ src/profiler/profile-generator.cc | 156 ++++++++++++++++++++++++ src/profiler/profile-generator.h | 26 ++++ test/cctest/BUILD.gn | 1 + test/cctest/jsonstream-helper.h | 61 +++++++++ test/cctest/test-cpu-profiler.cc | 60 +++++++++ test/cctest/test-heap-profiler.cc | 54 +------- 13 files changed, 505 insertions(+), 185 deletions(-) create mode 100644 src/profiler/output-stream-writer.h create mode 100644 test/cctest/jsonstream-helper.h diff --git a/AUTHORS b/AUTHORS index 21bd1dd57d..ef50bba1ef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -151,6 +151,7 @@ Jiaxun Yang Joel Stanley Johan Bergström Jonathan Liu +Juan Arboleda Julien Brianceau JunHo Seo Junha Park diff --git a/BUILD.bazel b/BUILD.bazel index 1b7e7e1576..1a4a4e69ce 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -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", diff --git a/BUILD.gn b/BUILD.gn index 0e04ad7b94..cd2a20bce1 100644 --- a/BUILD.gn +++ b/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", diff --git a/include/v8-profiler.h b/include/v8-profiler.h index 6145a2257a..6b73fc60bf 100644 --- a/include/v8-profiler.h +++ b/include/v8-profiler.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 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. */ diff --git a/src/api/api.cc b/src/api/api.cc index f231865f80..fa6ac0acf0 100644 --- a/src/api/api.cc +++ b/src/api/api.cc @@ -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( + reinterpret_cast(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(this)->samples_count(); } diff --git a/src/profiler/heap-snapshot-generator.cc b/src/profiler/heap-snapshot-generator.cc index d542a174b1..6e9ace40be 100644 --- a/src/profiler/heap-snapshot-generator.cc +++ b/src/profiler/heap-snapshot-generator.cc @@ -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 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(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(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(n, "%u"); } - void Finalize() { - if (aborted_) return; - DCHECK(chunk_pos_ < chunk_size_); - if (chunk_pos_ != 0) { - WriteChunk(); - } - stream_->EndOfStream(); - } - - private: - template - void AddNumberImpl(T n, const char* format) { - // Buffer for the longest value plus trailing \0 - static const int kMaxNumberSize = - MaxDecimalDigitsIn::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 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 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; diff --git a/src/profiler/output-stream-writer.h b/src/profiler/output-stream-writer.h new file mode 100644 index 0000000000..c6d144d8a7 --- /dev/null +++ b/src/profiler/output-stream-writer.h @@ -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 +#include + +#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 +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(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(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(n, "%u"); } + void Finalize() { + if (aborted_) return; + DCHECK(chunk_pos_ < chunk_size_); + if (chunk_pos_ != 0) { + WriteChunk(); + } + stream_->EndOfStream(); + } + + private: + template + void AddNumberImpl(T n, const char* format) { + // Buffer for the longest value plus trailing \0 + static const int kMaxNumberSize = + MaxDecimalDigitsIn::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 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 chunk_; + int chunk_pos_; + bool aborted_; +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_PROFILER_OUTPUT_STREAM_WRITER_H_ diff --git a/src/profiler/profile-generator.cc b/src/profiler/profile-generator.cc index 1f39ec26d1..06b8a8e1e7 100644 --- a/src/profiler/profile-generator.cc +++ b/src/profiler/profile-generator.cc @@ -5,12 +5,14 @@ #include "src/profiler/profile-generator.h" #include +#include #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* 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 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 nodes; + FlattenNodesTree( + reinterpret_cast(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(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( + profile_->start_time().since_origin().InMicroseconds())); + + writer_->AddString(",\"endTime\":"); + writer_->AddNumber(static_cast( + 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(); diff --git a/src/profiler/profile-generator.h b/src/profiler/profile-generator.h index 6228f2bba4..3a0e3a8f2f 100644 --- a/src/profiler/profile-generator.h +++ b/src/profiler/profile-generator.h @@ -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 diff --git a/test/cctest/BUILD.gn b/test/cctest/BUILD.gn index 22d660104b..59965d6e0c 100644 --- a/test/cctest/BUILD.gn +++ b/test/cctest/BUILD.gn @@ -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", diff --git a/test/cctest/jsonstream-helper.h b/test/cctest/jsonstream-helper.h new file mode 100644 index 0000000000..d887605609 --- /dev/null +++ b/test/cctest/jsonstream-helper.h @@ -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 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 dest) { buffer_.WriteTo(dest); } + int eos_signaled() { return eos_signaled_; } + int size() { return buffer_.size(); } + + private: + i::Collector buffer_; + int eos_signaled_; + int abort_countdown_; +}; + +class OneByteResource : public v8::String::ExternalOneByteStringResource { + public: + explicit OneByteResource(v8::base::Vector 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_ diff --git a/test/cctest/test-cpu-profiler.cc b/test/cctest/test-cpu-profiler.cc index b1e7fb830e..e6991ba142 100644 --- a/test/cctest/test-cpu-profiler.cc +++ b/test/cctest/test-cpu-profiler.cc @@ -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 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 json(stream.size()); + stream.WriteTo(json); + + // Verify that snapshot string is valid JSON. + OneByteResource* json_res = new OneByteResource(json); + v8::Local json_string = + v8::String::NewExternalOneByte(env->GetIsolate(), json_res) + .ToLocalChecked(); + v8::Local context = v8::Context::New(env->GetIsolate()); + v8::Local profile_parse_result = + v8::JSON::Parse(context, json_string).ToLocalChecked(); + + CHECK(!profile_parse_result.IsEmpty()); + CHECK(profile_parse_result->IsObject()); + + v8::Local profile_obj = profile_parse_result.As(); + 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() + ->Value() > 0); + CHECK(profile_obj->Get(env.local(), v8_str("endTime")) + .ToLocalChecked() + .As() + ->Value() > 0); +} + } // namespace test_cpu_profiler } // namespace internal } // namespace v8 diff --git a/test/cctest/test-heap-profiler.cc b/test/cctest/test-heap-profiler.cc index 188c9ef867..f8bf86bbe3 100644 --- a/test/cctest/test-heap-profiler.cc +++ b/test/cctest/test-heap-profiler.cc @@ -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 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 dest) { buffer_.WriteTo(dest); } - int eos_signaled() { return eos_signaled_; } - int size() { return buffer_.size(); } - - private: - i::Collector buffer_; - int eos_signaled_; - int abort_countdown_; -}; - -class OneByteResource : public v8::String::ExternalOneByteStringResource { - public: - explicit OneByteResource(v8::base::Vector 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 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());