mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Refactor RuntimeSamplingProfileTraceEventSerializer: use smart pointers, split code, add tests (#50531)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50531 # Changelog: [Internal] This should not have any functional changes, mostly refactoring the previous code, splitting it into smaller methods, better documentation and added tests. Main changes are: 1. We are going to use smart pointers for `ProfileTreeNode` 2. Split single static method into multiple smaller methods of one class, less cognitive load and smaller context window Reviewed By: robhogan Differential Revision: D72401690 fbshipit-source-id: a1eed5501349dcd811661d91f3bf126a0daaacf2
This commit is contained in:
committed by
Facebook GitHub Bot
parent
6609ba98e5
commit
af5074509e
@@ -72,7 +72,9 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||||
}
|
||||
|
||||
instanceAgent_->stopTracing();
|
||||
bool correctlyStopped = PerformanceTracer::getInstance().stopTracing();
|
||||
|
||||
PerformanceTracer& performanceTracer = PerformanceTracer::getInstance();
|
||||
bool correctlyStopped = performanceTracer.stopTracing();
|
||||
if (!correctlyStopped) {
|
||||
frontendChannel_(cdp::jsonError(
|
||||
req.id,
|
||||
@@ -90,15 +92,16 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
|
||||
"Tracing.dataCollected",
|
||||
folly::dynamic::object("value", eventsChunk)));
|
||||
};
|
||||
PerformanceTracer::getInstance().collectEvents(
|
||||
performanceTracer.collectEvents(
|
||||
dataCollectedCallback, TRACE_EVENT_CHUNK_SIZE);
|
||||
|
||||
tracing::RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
|
||||
PerformanceTracer::getInstance(),
|
||||
instanceAgent_->collectTracingProfile().getRuntimeSamplingProfile(),
|
||||
instanceTracingStartTimestamp_,
|
||||
tracing::RuntimeSamplingProfileTraceEventSerializer serializer(
|
||||
performanceTracer,
|
||||
dataCollectedCallback,
|
||||
PROFILE_TRACE_EVENT_CHUNK_SIZE);
|
||||
serializer.serializeAndNotify(
|
||||
instanceAgent_->collectTracingProfile().getRuntimeSamplingProfile(),
|
||||
instanceTracingStartTimestamp_);
|
||||
|
||||
frontendChannel_(cdp::jsonNotification(
|
||||
"Tracing.tracingComplete",
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeSamplingProfile.h"
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <jsinspector-modern/tracing/RuntimeSamplingProfile.h>
|
||||
|
||||
namespace facebook::react::jsinspector_modern::tracing {
|
||||
|
||||
@@ -28,7 +31,7 @@ class ProfileTreeNode {
|
||||
ProfileTreeNode(
|
||||
uint32_t id,
|
||||
CodeType codeType,
|
||||
ProfileTreeNode* parent,
|
||||
std::shared_ptr<ProfileTreeNode> parent,
|
||||
RuntimeSamplingProfile::SampleCallStackFrame callFrame)
|
||||
: id_(id),
|
||||
codeType_(codeType),
|
||||
@@ -47,7 +50,7 @@ class ProfileTreeNode {
|
||||
* \return pointer to the parent node, nullptr if this is the root node.
|
||||
*/
|
||||
ProfileTreeNode* getParent() const {
|
||||
return parent_;
|
||||
return parent_.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,12 +61,14 @@ class ProfileTreeNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Will only add unique child node. Returns pointer to the already existing
|
||||
* child node, nullptr if the added child node is unique.
|
||||
* Will only add unique child node.
|
||||
* \return shared pointer to the already existing child node, nullptr if the
|
||||
* added child node is unique.
|
||||
*/
|
||||
ProfileTreeNode* addChild(ProfileTreeNode* child) {
|
||||
for (auto existingChild : children_) {
|
||||
if (*existingChild == child) {
|
||||
std::shared_ptr<ProfileTreeNode> addChild(
|
||||
std::shared_ptr<ProfileTreeNode> child) {
|
||||
for (const auto& existingChild : children_) {
|
||||
if (*existingChild == child.get()) {
|
||||
return existingChild;
|
||||
}
|
||||
}
|
||||
@@ -94,13 +99,13 @@ class ProfileTreeNode {
|
||||
*/
|
||||
CodeType codeType_;
|
||||
/**
|
||||
* Pointer to the parent node. Should be nullptr only for root node.
|
||||
* Shared pointer to the parent node. Can be nullptr only for root node.
|
||||
*/
|
||||
ProfileTreeNode* parent_{nullptr};
|
||||
std::shared_ptr<ProfileTreeNode> parent_;
|
||||
/**
|
||||
* Lst of pointers to children nodes.
|
||||
* Lst of shared pointers to children nodes.
|
||||
*/
|
||||
std::vector<ProfileTreeNode*> children_{};
|
||||
std::vector<std::shared_ptr<ProfileTreeNode>> children_;
|
||||
/**
|
||||
* Information about the corresponding call frame that is represented by this
|
||||
* node.
|
||||
|
||||
+186
-166
@@ -16,6 +16,11 @@ namespace {
|
||||
// future, once we support multiple VMs being sampled at the same time.
|
||||
const uint16_t PROFILE_ID = 1;
|
||||
|
||||
/// Fallback script ID for artificial call frames, such as (root), (idle) or
|
||||
/// (program). Required for emulating the payload in a format that is expected
|
||||
/// by Chrome DevTools.
|
||||
const uint32_t FALLBACK_SCRIPT_ID = 0;
|
||||
|
||||
uint64_t formatTimePointToUnixTimestamp(
|
||||
std::chrono::steady_clock::time_point timestamp) {
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
@@ -24,7 +29,7 @@ uint64_t formatTimePointToUnixTimestamp(
|
||||
}
|
||||
|
||||
TraceEventProfileChunk::CPUProfile::Node convertToTraceEventProfileNode(
|
||||
ProfileTreeNode* node) {
|
||||
std::shared_ptr<ProfileTreeNode> node) {
|
||||
ProfileTreeNode* nodeParent = node->getParent();
|
||||
const RuntimeSamplingProfile::SampleCallStackFrame& callFrame =
|
||||
node->getCallFrame();
|
||||
@@ -51,203 +56,218 @@ TraceEventProfileChunk::CPUProfile::Node convertToTraceEventProfileNode(
|
||||
: std::nullopt};
|
||||
}
|
||||
|
||||
void emitSingleProfileChunk(
|
||||
PerformanceTracer& performanceTracer,
|
||||
std::vector<folly::dynamic>& buffer,
|
||||
uint16_t profileId,
|
||||
uint64_t threadId,
|
||||
uint64_t chunkTimestamp,
|
||||
std::vector<ProfileTreeNode*>& nodes,
|
||||
std::vector<uint32_t>& samples,
|
||||
std::vector<long long>& timeDeltas) {
|
||||
std::vector<TraceEventProfileChunk::CPUProfile::Node> traceEventNodes;
|
||||
traceEventNodes.reserve(nodes.size());
|
||||
for (ProfileTreeNode* node : nodes) {
|
||||
traceEventNodes.push_back(convertToTraceEventProfileNode(node));
|
||||
}
|
||||
RuntimeSamplingProfile::SampleCallStackFrame createArtificialCallFrame(
|
||||
std::string callFrameName) {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
|
||||
FALLBACK_SCRIPT_ID,
|
||||
std::move(callFrameName)};
|
||||
};
|
||||
|
||||
buffer.push_back(performanceTracer.getSerializedRuntimeProfileChunkTraceEvent(
|
||||
profileId,
|
||||
threadId,
|
||||
chunkTimestamp,
|
||||
TraceEventProfileChunk{
|
||||
TraceEventProfileChunk::CPUProfile{traceEventNodes, samples},
|
||||
TraceEventProfileChunk::TimeDeltas{timeDeltas},
|
||||
}));
|
||||
RuntimeSamplingProfile::SampleCallStackFrame createGarbageCollectorCallFrame() {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector,
|
||||
FALLBACK_SCRIPT_ID,
|
||||
"(garbage collector)"};
|
||||
};
|
||||
|
||||
std::shared_ptr<ProfileTreeNode> createNode(
|
||||
NodeIdGenerator& nodeIdGenerator,
|
||||
ProfileTreeNode::CodeType codeType,
|
||||
std::shared_ptr<ProfileTreeNode> parent,
|
||||
const RuntimeSamplingProfile::SampleCallStackFrame& callFrame) {
|
||||
return std::make_shared<ProfileTreeNode>(
|
||||
nodeIdGenerator.getNext(), codeType, parent, callFrame);
|
||||
}
|
||||
|
||||
std::shared_ptr<ProfileTreeNode> createArtificialNode(
|
||||
NodeIdGenerator& nodeIdGenerator,
|
||||
std::shared_ptr<ProfileTreeNode> parent,
|
||||
std::string callFrameName) {
|
||||
RuntimeSamplingProfile::SampleCallStackFrame callFrame =
|
||||
createArtificialCallFrame(std::move(callFrameName));
|
||||
|
||||
return createNode(
|
||||
nodeIdGenerator, ProfileTreeNode::CodeType::Other, parent, callFrame);
|
||||
}
|
||||
|
||||
std::shared_ptr<ProfileTreeNode> createRootNode(
|
||||
NodeIdGenerator& nodeIdGenerator) {
|
||||
return createArtificialNode(nodeIdGenerator, nullptr, "(root)");
|
||||
}
|
||||
|
||||
std::shared_ptr<ProfileTreeNode> createProgramNode(
|
||||
NodeIdGenerator& nodeIdGenerator,
|
||||
std::shared_ptr<ProfileTreeNode> rootNode) {
|
||||
return createArtificialNode(nodeIdGenerator, rootNode, "(program)");
|
||||
}
|
||||
|
||||
std::shared_ptr<ProfileTreeNode> createIdleNode(
|
||||
NodeIdGenerator& nodeIdGenerator,
|
||||
std::shared_ptr<ProfileTreeNode> rootNode) {
|
||||
return createArtificialNode(nodeIdGenerator, rootNode, "(idle)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* static */ void
|
||||
RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
|
||||
PerformanceTracer& performanceTracer,
|
||||
void RuntimeSamplingProfileTraceEventSerializer::sendProfileTraceEvent(
|
||||
uint64_t threadId,
|
||||
uint16_t profileId,
|
||||
uint64_t profileStartUnixTimestamp) const {
|
||||
folly::dynamic serializedTraceEvent =
|
||||
performanceTracer_.getSerializedRuntimeProfileTraceEvent(
|
||||
threadId, profileId, profileStartUnixTimestamp);
|
||||
|
||||
notificationCallback_(folly::dynamic::array(serializedTraceEvent));
|
||||
}
|
||||
|
||||
void RuntimeSamplingProfileTraceEventSerializer::chunkEmptySample(
|
||||
ProfileChunk& chunk,
|
||||
uint32_t idleNodeId,
|
||||
long long samplesTimeDelta) {
|
||||
chunk.samples.push_back(idleNodeId);
|
||||
chunk.timeDeltas.push_back(samplesTimeDelta);
|
||||
}
|
||||
|
||||
void RuntimeSamplingProfileTraceEventSerializer::bufferProfileChunkTraceEvent(
|
||||
ProfileChunk& chunk,
|
||||
uint16_t profileId) {
|
||||
if (chunk.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<TraceEventProfileChunk::CPUProfile::Node> traceEventNodes;
|
||||
traceEventNodes.reserve(chunk.nodes.size());
|
||||
for (const auto& node : chunk.nodes) {
|
||||
traceEventNodes.push_back(convertToTraceEventProfileNode(node));
|
||||
}
|
||||
|
||||
traceEventBuffer_.push_back(
|
||||
performanceTracer_.getSerializedRuntimeProfileChunkTraceEvent(
|
||||
profileId,
|
||||
chunk.threadId,
|
||||
chunk.timestamp,
|
||||
TraceEventProfileChunk{
|
||||
TraceEventProfileChunk::CPUProfile{
|
||||
traceEventNodes, chunk.samples},
|
||||
TraceEventProfileChunk::TimeDeltas{chunk.timeDeltas},
|
||||
}));
|
||||
}
|
||||
|
||||
void RuntimeSamplingProfileTraceEventSerializer::processCallStack(
|
||||
const std::vector<RuntimeSamplingProfile::SampleCallStackFrame>& callStack,
|
||||
ProfileChunk& chunk,
|
||||
std::shared_ptr<ProfileTreeNode> rootNode,
|
||||
std::shared_ptr<ProfileTreeNode> idleNode,
|
||||
long long samplesTimeDelta,
|
||||
NodeIdGenerator& nodeIdGenerator) {
|
||||
if (callStack.empty()) {
|
||||
chunkEmptySample(chunk, idleNode->getId(), samplesTimeDelta);
|
||||
return;
|
||||
}
|
||||
|
||||
auto previousNode = rootNode;
|
||||
for (auto it = callStack.rbegin(); it != callStack.rend(); ++it) {
|
||||
const RuntimeSamplingProfile::SampleCallStackFrame& callFrame = *it;
|
||||
bool isGarbageCollectorFrame = callFrame.getKind() ==
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector;
|
||||
// We don't need real garbage collector call frame, we change it to
|
||||
// what Chrome DevTools expects.
|
||||
auto currentNode = createNode(
|
||||
nodeIdGenerator,
|
||||
isGarbageCollectorFrame ? ProfileTreeNode::CodeType::Other
|
||||
: ProfileTreeNode::CodeType::JavaScript,
|
||||
previousNode,
|
||||
isGarbageCollectorFrame ? createGarbageCollectorCallFrame()
|
||||
: callFrame);
|
||||
|
||||
auto alreadyExistingNode = previousNode->addChild(currentNode);
|
||||
if (alreadyExistingNode != nullptr) {
|
||||
previousNode = alreadyExistingNode;
|
||||
} else {
|
||||
chunk.nodes.push_back(currentNode);
|
||||
previousNode = currentNode;
|
||||
}
|
||||
}
|
||||
|
||||
chunk.samples.push_back(previousNode->getId());
|
||||
chunk.timeDeltas.push_back(samplesTimeDelta);
|
||||
}
|
||||
|
||||
void RuntimeSamplingProfileTraceEventSerializer::
|
||||
sendBufferedTraceEventsAndClear() {
|
||||
notificationCallback_(traceEventBuffer_);
|
||||
traceEventBuffer_ = folly::dynamic::array();
|
||||
}
|
||||
|
||||
void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify(
|
||||
const RuntimeSamplingProfile& profile,
|
||||
std::chrono::steady_clock::time_point tracingStartTime,
|
||||
const std::function<void(const folly::dynamic& traceEventsChunk)>&
|
||||
notificationCallback,
|
||||
uint16_t traceEventChunkSize,
|
||||
uint16_t profileChunkSize) {
|
||||
std::chrono::steady_clock::time_point tracingStartTime) {
|
||||
const std::vector<RuntimeSamplingProfile::Sample>& samples =
|
||||
profile.getSamples();
|
||||
if (samples.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<folly::dynamic> buffer;
|
||||
|
||||
uint64_t chunkThreadId = samples.front().getThreadId();
|
||||
uint64_t firstChunkThreadId = samples.front().getThreadId();
|
||||
uint64_t tracingStartUnixTimestamp =
|
||||
formatTimePointToUnixTimestamp(tracingStartTime);
|
||||
buffer.push_back(performanceTracer.getSerializedRuntimeProfileTraceEvent(
|
||||
chunkThreadId, PROFILE_ID, tracingStartUnixTimestamp));
|
||||
uint64_t previousSampleUnixTimestamp = tracingStartUnixTimestamp;
|
||||
uint64_t currentChunkUnixTimestamp = tracingStartUnixTimestamp;
|
||||
|
||||
uint32_t nodeCount = 0;
|
||||
auto* rootNode = new ProfileTreeNode(
|
||||
++nodeCount,
|
||||
ProfileTreeNode::CodeType::Other,
|
||||
nullptr,
|
||||
RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
|
||||
0,
|
||||
"(root)"});
|
||||
auto* programNode = new ProfileTreeNode(
|
||||
++nodeCount,
|
||||
ProfileTreeNode::CodeType::Other,
|
||||
rootNode,
|
||||
RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
|
||||
0,
|
||||
"(program)"});
|
||||
auto* idleNode = new ProfileTreeNode(
|
||||
++nodeCount,
|
||||
ProfileTreeNode::CodeType::Other,
|
||||
rootNode,
|
||||
RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
|
||||
0,
|
||||
"(idle)"});
|
||||
sendProfileTraceEvent(
|
||||
firstChunkThreadId, PROFILE_ID, tracingStartUnixTimestamp);
|
||||
|
||||
NodeIdGenerator nodeIdGenerator{};
|
||||
auto rootNode = createRootNode(nodeIdGenerator);
|
||||
auto programNode = createProgramNode(nodeIdGenerator, rootNode);
|
||||
auto idleNode = createIdleNode(nodeIdGenerator, rootNode);
|
||||
rootNode->addChild(programNode);
|
||||
rootNode->addChild(idleNode);
|
||||
|
||||
// Ideally, we should use a timestamp from Runtime Sampling Profiler.
|
||||
// We currently use tracingStartTime, which is defined in TracingAgent.
|
||||
uint64_t previousSampleTimestamp = tracingStartUnixTimestamp;
|
||||
// There could be any number of new nodes in this chunk. Empty if all nodes
|
||||
// are already emitted in previous chunks.
|
||||
std::vector<ProfileTreeNode*> nodesInThisChunk;
|
||||
nodesInThisChunk.push_back(rootNode);
|
||||
nodesInThisChunk.push_back(programNode);
|
||||
nodesInThisChunk.push_back(idleNode);
|
||||
ProfileChunk chunk{
|
||||
profileChunkSize_, firstChunkThreadId, currentChunkUnixTimestamp};
|
||||
chunk.nodes.push_back(rootNode);
|
||||
chunk.nodes.push_back(programNode);
|
||||
chunk.nodes.push_back(idleNode);
|
||||
|
||||
std::vector<uint32_t> samplesInThisChunk;
|
||||
samplesInThisChunk.reserve(profileChunkSize);
|
||||
std::vector<long long> timeDeltasInThisChunk;
|
||||
timeDeltasInThisChunk.reserve(profileChunkSize);
|
||||
for (const auto& sample : samples) {
|
||||
uint64_t currentSampleThreadId = sample.getThreadId();
|
||||
long long currentSampleUnixTimestamp = sample.getTimestamp();
|
||||
|
||||
RuntimeSamplingProfile::SampleCallStackFrame garbageCollectorCallFrame =
|
||||
RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector,
|
||||
0,
|
||||
"(garbage collector)"};
|
||||
uint64_t chunkTimestamp = tracingStartUnixTimestamp;
|
||||
for (const RuntimeSamplingProfile::Sample& sample : samples) {
|
||||
uint64_t sampleThreadId = sample.getThreadId();
|
||||
// If next sample was recorded on a different thread, emit the current chunk
|
||||
// and continue.
|
||||
if (chunkThreadId != sampleThreadId) {
|
||||
emitSingleProfileChunk(
|
||||
performanceTracer,
|
||||
buffer,
|
||||
PROFILE_ID,
|
||||
chunkThreadId,
|
||||
chunkTimestamp,
|
||||
nodesInThisChunk,
|
||||
samplesInThisChunk,
|
||||
timeDeltasInThisChunk);
|
||||
|
||||
nodesInThisChunk.clear();
|
||||
samplesInThisChunk.clear();
|
||||
timeDeltasInThisChunk.clear();
|
||||
// We should not attempt to merge samples from different threads.
|
||||
// From past observations, this only happens for GC nodes.
|
||||
// We should group samples by thread id once we support executing JavaScript
|
||||
// on different threads.
|
||||
if (currentSampleThreadId != chunk.threadId || chunk.isFull()) {
|
||||
bufferProfileChunkTraceEvent(chunk, PROFILE_ID);
|
||||
chunk = ProfileChunk{
|
||||
profileChunkSize_, currentSampleThreadId, currentChunkUnixTimestamp};
|
||||
}
|
||||
|
||||
chunkThreadId = sampleThreadId;
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack =
|
||||
sample.getCallStack();
|
||||
uint64_t sampleTimestamp = sample.getTimestamp();
|
||||
if (samplesInThisChunk.empty()) {
|
||||
// New chunk. Reset the timestamp.
|
||||
chunkTimestamp = sampleTimestamp;
|
||||
if (traceEventBuffer_.size() == traceEventChunkSize_) {
|
||||
sendBufferedTraceEventsAndClear();
|
||||
}
|
||||
|
||||
long long timeDelta = sampleTimestamp - previousSampleTimestamp;
|
||||
timeDeltasInThisChunk.push_back(timeDelta);
|
||||
previousSampleTimestamp = sampleTimestamp;
|
||||
processCallStack(
|
||||
sample.getCallStack(),
|
||||
chunk,
|
||||
rootNode,
|
||||
idleNode,
|
||||
currentSampleUnixTimestamp - previousSampleUnixTimestamp,
|
||||
nodeIdGenerator);
|
||||
|
||||
ProfileTreeNode* previousNode = callStack.empty() ? idleNode : rootNode;
|
||||
for (auto it = callStack.rbegin(); it != callStack.rend(); ++it) {
|
||||
auto callFrame = *it;
|
||||
bool isGarbageCollectorFrame = callFrame.getKind() ==
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector;
|
||||
// We don't need real garbage collector call frame, we change it to
|
||||
// what Chrome DevTools expects.
|
||||
auto* currentNode = new ProfileTreeNode(
|
||||
nodeCount + 1,
|
||||
isGarbageCollectorFrame ? ProfileTreeNode::CodeType::Other
|
||||
: ProfileTreeNode::CodeType::JavaScript,
|
||||
previousNode,
|
||||
isGarbageCollectorFrame ? garbageCollectorCallFrame : callFrame);
|
||||
|
||||
ProfileTreeNode* alreadyExistingNode =
|
||||
previousNode->addChild(currentNode);
|
||||
if (alreadyExistingNode != nullptr) {
|
||||
previousNode = alreadyExistingNode;
|
||||
} else {
|
||||
nodesInThisChunk.push_back(currentNode);
|
||||
++nodeCount;
|
||||
|
||||
previousNode = currentNode;
|
||||
}
|
||||
}
|
||||
samplesInThisChunk.push_back(previousNode->getId());
|
||||
|
||||
if (samplesInThisChunk.size() == profileChunkSize) {
|
||||
emitSingleProfileChunk(
|
||||
performanceTracer,
|
||||
buffer,
|
||||
PROFILE_ID,
|
||||
chunkThreadId,
|
||||
chunkTimestamp,
|
||||
nodesInThisChunk,
|
||||
samplesInThisChunk,
|
||||
timeDeltasInThisChunk);
|
||||
|
||||
nodesInThisChunk.clear();
|
||||
samplesInThisChunk.clear();
|
||||
timeDeltasInThisChunk.clear();
|
||||
}
|
||||
|
||||
if (buffer.size() == traceEventChunkSize) {
|
||||
notificationCallback(folly::dynamic::array(buffer.begin(), buffer.end()));
|
||||
buffer.clear();
|
||||
}
|
||||
previousSampleUnixTimestamp = currentSampleUnixTimestamp;
|
||||
}
|
||||
|
||||
if (!samplesInThisChunk.empty()) {
|
||||
emitSingleProfileChunk(
|
||||
performanceTracer,
|
||||
buffer,
|
||||
PROFILE_ID,
|
||||
chunkThreadId,
|
||||
chunkTimestamp,
|
||||
nodesInThisChunk,
|
||||
samplesInThisChunk,
|
||||
timeDeltasInThisChunk);
|
||||
if (!chunk.isEmpty()) {
|
||||
bufferProfileChunkTraceEvent(chunk, PROFILE_ID);
|
||||
}
|
||||
|
||||
if (!buffer.empty()) {
|
||||
notificationCallback(folly::dynamic::array(buffer.begin(), buffer.end()));
|
||||
buffer.clear();
|
||||
if (!traceEventBuffer_.empty()) {
|
||||
sendBufferedTraceEventsAndClear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+132
-14
@@ -8,39 +8,157 @@
|
||||
#pragma once
|
||||
|
||||
#include "PerformanceTracer.h"
|
||||
#include "ProfileTreeNode.h"
|
||||
#include "RuntimeSamplingProfile.h"
|
||||
|
||||
namespace facebook::react::jsinspector_modern::tracing {
|
||||
|
||||
namespace {
|
||||
|
||||
struct NodeIdGenerator {
|
||||
public:
|
||||
uint32_t getNext() {
|
||||
return ++counter_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t counter_ = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* Serializes RuntimeSamplingProfile into collection of specific Trace Events,
|
||||
* which represent Profile information on a timeline.
|
||||
*/
|
||||
class RuntimeSamplingProfileTraceEventSerializer {
|
||||
public:
|
||||
RuntimeSamplingProfileTraceEventSerializer() = delete;
|
||||
struct ProfileChunk {
|
||||
ProfileChunk(
|
||||
uint16_t chunkSize,
|
||||
uint64_t chunkThreadId,
|
||||
uint64_t chunkTimestamp)
|
||||
: size(chunkSize), threadId(chunkThreadId), timestamp(chunkTimestamp) {
|
||||
samples.reserve(size);
|
||||
timeDeltas.reserve(size);
|
||||
}
|
||||
|
||||
bool isFull() const {
|
||||
return samples.size() == size;
|
||||
}
|
||||
|
||||
bool isEmpty() const {
|
||||
return samples.empty();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ProfileTreeNode>> nodes;
|
||||
std::vector<uint32_t> samples;
|
||||
std::vector<long long> timeDeltas;
|
||||
uint16_t size;
|
||||
uint64_t threadId;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
public:
|
||||
/**
|
||||
* \param performanceTracer A reference to PerformanceTracer instance.
|
||||
* \param profile What we will be serializing.
|
||||
* \param tracingStartTime A timestamp of when tracing of an
|
||||
* Instance started, will be used as a starting reference point of JavaScript
|
||||
* samples recording.
|
||||
* \param notificationCallback A callback, which is called
|
||||
* \param notificationCallback A reference to a callback, which is called
|
||||
* when a chunk of trace events is ready to be sent.
|
||||
* \param traceEventChunkSize The maximum number of ProfileChunk trace events
|
||||
* that can be sent in a single CDP Tracing.dataCollected message.
|
||||
* \param traceEventChunkSize The maximum number of ProfileChunk trace
|
||||
* events that can be sent in a single CDP Tracing.dataCollected message.
|
||||
* \param profileChunkSize The maximum number of ProfileChunk trace events
|
||||
* that can be sent in a single ProfileChunk trace event.
|
||||
*/
|
||||
static void serializeAndNotify(
|
||||
RuntimeSamplingProfileTraceEventSerializer(
|
||||
PerformanceTracer& performanceTracer,
|
||||
const RuntimeSamplingProfile& profile,
|
||||
std::chrono::steady_clock::time_point tracingStartTime,
|
||||
const std::function<void(const folly::dynamic& traceEventsChunk)>&
|
||||
std::function<void(const folly::dynamic& traceEventsChunk)>
|
||||
notificationCallback,
|
||||
uint16_t traceEventChunkSize,
|
||||
uint16_t profileChunkSize = 10);
|
||||
uint16_t profileChunkSize = 10)
|
||||
: performanceTracer_(performanceTracer),
|
||||
notificationCallback_(std::move(notificationCallback)),
|
||||
traceEventChunkSize_(traceEventChunkSize),
|
||||
profileChunkSize_(profileChunkSize) {
|
||||
traceEventBuffer_ = folly::dynamic::array();
|
||||
traceEventBuffer_.reserve(traceEventChunkSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* \param profile What we will be serializing.
|
||||
* \param tracingStartTime A timestamp of when tracing of an Instance started,
|
||||
* will be used as a starting reference point of JavaScript samples recording.
|
||||
*/
|
||||
void serializeAndNotify(
|
||||
const RuntimeSamplingProfile& profile,
|
||||
std::chrono::steady_clock::time_point tracingStartTime);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Sends a single "Profile" Trace Event via notificationCallback_.
|
||||
* \param threadId The id of the thread, where the Profile was collected.
|
||||
* \param profileId The id of the Profile.
|
||||
* \param profileStartUnixTimestamp The Unix timestamp of the start of the
|
||||
* profile.
|
||||
*/
|
||||
void sendProfileTraceEvent(
|
||||
uint64_t threadId,
|
||||
uint16_t profileId,
|
||||
uint64_t profileStartUnixTimestamp) const;
|
||||
|
||||
/**
|
||||
* Encapsulates logic for processing the empty sample, when the VM was idling.
|
||||
* \param chunk The profile chunk, which will record this sample.
|
||||
* \param idleNodeId The id of the (idle) node.
|
||||
* \param samplesTimeDelta Delta between the current sample and the
|
||||
* previous one.
|
||||
*/
|
||||
void chunkEmptySample(
|
||||
ProfileChunk& chunk,
|
||||
uint32_t idleNodeId,
|
||||
long long samplesTimeDelta);
|
||||
|
||||
/**
|
||||
* Records ProfileChunk as a "ProfileChunk" Trace Event in traceEventBuffer_.
|
||||
* \param chunk The chunk that will be buffered.
|
||||
* \param profileId The id of the Profile.
|
||||
*/
|
||||
void bufferProfileChunkTraceEvent(ProfileChunk& chunk, uint16_t profileId);
|
||||
|
||||
/**
|
||||
* Encapsulates logic for processing the call stack of the sample.
|
||||
* \param callStack The call stack that will be processed.
|
||||
* \param chunk The profile chunk, which will buffer the sample with the
|
||||
* provided call stack.
|
||||
* \param rootNode Shared pointer to the (root) node. Will be the parent node
|
||||
* of the corresponding profile tree branch.
|
||||
* \param idleNode Shared pointer to the (idle) node. Will be the only node
|
||||
* that is used for the corresponding profile tree branch, in case of an empty
|
||||
* call stack.
|
||||
* \param samplesTimeDelta Delta between the current sample and the previous
|
||||
* one.
|
||||
* \param nodeIdGenerator NodeIdGenerator instance that will be used for
|
||||
* generating unique node ids.
|
||||
*/
|
||||
void processCallStack(
|
||||
const std::vector<RuntimeSamplingProfile::SampleCallStackFrame>&
|
||||
callStack,
|
||||
ProfileChunk& chunk,
|
||||
std::shared_ptr<ProfileTreeNode> rootNode,
|
||||
std::shared_ptr<ProfileTreeNode> idleNode,
|
||||
long long samplesTimeDelta,
|
||||
NodeIdGenerator& nodeIdGenerator);
|
||||
|
||||
/**
|
||||
* Sends buffered Trace Events via notificationCallback_ and then clears it.
|
||||
*/
|
||||
void sendBufferedTraceEventsAndClear();
|
||||
|
||||
PerformanceTracer& performanceTracer_;
|
||||
const std::function<void(const folly::dynamic& traceEventsChunk)>
|
||||
notificationCallback_;
|
||||
uint16_t traceEventChunkSize_;
|
||||
uint16_t profileChunkSize_;
|
||||
|
||||
folly::dynamic traceEventBuffer_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern::tracing
|
||||
|
||||
+15
-6
@@ -25,30 +25,39 @@ TEST(ProfileTreeNodeTest, EqualityOperator) {
|
||||
EXPECT_EQ(*node1 == node2, true);
|
||||
|
||||
node1 = new ProfileTreeNode(
|
||||
3, ProfileTreeNode::CodeType::JavaScript, node1, callFrame);
|
||||
3,
|
||||
ProfileTreeNode::CodeType::JavaScript,
|
||||
std::shared_ptr<ProfileTreeNode>(node1),
|
||||
callFrame);
|
||||
node2 = new ProfileTreeNode(
|
||||
4, ProfileTreeNode::CodeType::JavaScript, nullptr, callFrame);
|
||||
EXPECT_EQ(*node1 == node2, false);
|
||||
|
||||
node1 = new ProfileTreeNode(
|
||||
5, ProfileTreeNode::CodeType::JavaScript, node2, callFrame);
|
||||
5,
|
||||
ProfileTreeNode::CodeType::JavaScript,
|
||||
std::shared_ptr<ProfileTreeNode>(node2),
|
||||
callFrame);
|
||||
node2 = new ProfileTreeNode(
|
||||
6, ProfileTreeNode::CodeType::JavaScript, node2, callFrame);
|
||||
6,
|
||||
ProfileTreeNode::CodeType::JavaScript,
|
||||
std::shared_ptr<ProfileTreeNode>(node2),
|
||||
callFrame);
|
||||
EXPECT_EQ(*node1 == node2, true);
|
||||
}
|
||||
|
||||
TEST(ProfileTreeNodeTest, OnlyAddsUniqueChildren) {
|
||||
auto callFrame = RuntimeSamplingProfile::SampleCallStackFrame{
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction, 0, "foo"};
|
||||
ProfileTreeNode* parent = new ProfileTreeNode(
|
||||
auto parent = std::make_shared<ProfileTreeNode>(
|
||||
1, ProfileTreeNode::CodeType::JavaScript, nullptr, callFrame);
|
||||
ProfileTreeNode* existingChild = new ProfileTreeNode(
|
||||
auto existingChild = std::make_shared<ProfileTreeNode>(
|
||||
2, ProfileTreeNode::CodeType::JavaScript, parent, callFrame);
|
||||
|
||||
auto maybeAlreadyExistingChild = parent->addChild(existingChild);
|
||||
EXPECT_EQ(maybeAlreadyExistingChild, nullptr);
|
||||
|
||||
auto copyOfExistingChild = new ProfileTreeNode(
|
||||
auto copyOfExistingChild = std::make_shared<ProfileTreeNode>(
|
||||
3, ProfileTreeNode::CodeType::JavaScript, parent, callFrame);
|
||||
|
||||
maybeAlreadyExistingChild = parent->addChild(copyOfExistingChild);
|
||||
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
|
||||
namespace facebook::react::jsinspector_modern::tracing {
|
||||
|
||||
class RuntimeSamplingProfileTraceEventSerializerTest : public ::testing::Test {
|
||||
protected:
|
||||
std::vector<folly::dynamic> notificationEvents_;
|
||||
|
||||
std::function<void(const folly::dynamic& traceEventsChunk)>
|
||||
createNotificationCallback() {
|
||||
return [this](const folly::dynamic& traceEventsChunk) {
|
||||
notificationEvents_.push_back(traceEventsChunk);
|
||||
};
|
||||
}
|
||||
|
||||
RuntimeSamplingProfile::SampleCallStackFrame createJSCallFrame(
|
||||
std::string functionName,
|
||||
uint32_t scriptId = 1,
|
||||
std::optional<std::string> url = std::nullopt,
|
||||
std::optional<uint32_t> lineNumber = std::nullopt,
|
||||
std::optional<uint32_t> columnNumber = std::nullopt) {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame(
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction,
|
||||
scriptId,
|
||||
std::move(functionName),
|
||||
std::move(url),
|
||||
lineNumber,
|
||||
columnNumber);
|
||||
}
|
||||
|
||||
RuntimeSamplingProfile::SampleCallStackFrame createGCCallFrame() {
|
||||
return RuntimeSamplingProfile::SampleCallStackFrame(
|
||||
RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector,
|
||||
0,
|
||||
"(garbage collector)");
|
||||
}
|
||||
|
||||
RuntimeSamplingProfile::Sample createSample(
|
||||
uint64_t timestamp,
|
||||
uint64_t threadId,
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack) {
|
||||
return {timestamp, threadId, std::move(callStack)};
|
||||
}
|
||||
|
||||
RuntimeSamplingProfile createEmptyProfile() {
|
||||
return {"TestRuntime", {}};
|
||||
}
|
||||
|
||||
RuntimeSamplingProfile createProfileWithSamples(
|
||||
std::vector<RuntimeSamplingProfile::Sample> samples) {
|
||||
return {"TestRuntime", std::move(samples)};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptyProfile) {
|
||||
// Setup
|
||||
auto notificationCallback = createNotificationCallback();
|
||||
RuntimeSamplingProfileTraceEventSerializer serializer(
|
||||
PerformanceTracer::getInstance(), notificationCallback, 10);
|
||||
|
||||
auto profile = createEmptyProfile();
|
||||
auto tracingStartTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Execute
|
||||
serializer.serializeAndNotify(profile, tracingStartTime);
|
||||
|
||||
// Nothing should be reported if the profile is empty.
|
||||
EXPECT_TRUE(notificationEvents_.empty());
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
RuntimeSamplingProfileTraceEventSerializerTest,
|
||||
SameCallFramesAreMerged) {
|
||||
// Setup
|
||||
auto notificationCallback = createNotificationCallback();
|
||||
RuntimeSamplingProfileTraceEventSerializer serializer(
|
||||
PerformanceTracer::getInstance(), notificationCallback, 10);
|
||||
|
||||
// [ foo ]
|
||||
// [ bar ]
|
||||
// [baz][(gc)]
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack1 = {
|
||||
createJSCallFrame("bar", 1, "test.js", 20, 10),
|
||||
createJSCallFrame("foo", 1, "test.js", 10, 5),
|
||||
};
|
||||
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack2 = {
|
||||
createJSCallFrame("baz", 1, "other.js", 5, 1),
|
||||
createJSCallFrame("bar", 1, "test.js", 20, 10),
|
||||
createJSCallFrame("foo", 1, "test.js", 10, 5),
|
||||
};
|
||||
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack3 = {
|
||||
createGCCallFrame(),
|
||||
createJSCallFrame("bar", 1, "test.js", 20, 10),
|
||||
createJSCallFrame("foo", 1, "test.js", 10, 5),
|
||||
};
|
||||
|
||||
uint64_t threadId = 1;
|
||||
uint64_t timestamp1 = 1000000;
|
||||
uint64_t timestamp2 = 2000000;
|
||||
uint64_t timestamp3 = 3000000;
|
||||
|
||||
auto samples = std::vector<RuntimeSamplingProfile::Sample>{};
|
||||
samples.emplace_back(createSample(timestamp1, threadId, callStack1));
|
||||
samples.emplace_back(createSample(timestamp2, threadId, callStack2));
|
||||
samples.emplace_back(createSample(timestamp3, threadId, callStack3));
|
||||
|
||||
auto profile = createProfileWithSamples(std::move(samples));
|
||||
auto tracingStartTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Execute
|
||||
serializer.serializeAndNotify(profile, tracingStartTime);
|
||||
|
||||
// Verify
|
||||
ASSERT_EQ(notificationEvents_.size(), 2);
|
||||
// (root), (program), (idle), foo, bar, baz, (garbage collector)
|
||||
ASSERT_EQ(
|
||||
notificationEvents_[1][0]["args"]["data"]["cpuProfile"]["nodes"].size(),
|
||||
7);
|
||||
}
|
||||
|
||||
TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptySample) {
|
||||
// Setup
|
||||
auto notificationCallback = createNotificationCallback();
|
||||
RuntimeSamplingProfileTraceEventSerializer serializer(
|
||||
PerformanceTracer::getInstance(), notificationCallback, 10);
|
||||
|
||||
// Create an empty sample (no call stack)
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> emptyCallStack;
|
||||
|
||||
uint64_t threadId = 1;
|
||||
uint64_t timestamp = 1000000;
|
||||
|
||||
auto samples = std::vector<RuntimeSamplingProfile::Sample>{};
|
||||
samples.emplace_back(createSample(timestamp, threadId, emptyCallStack));
|
||||
auto profile = createProfileWithSamples(std::move(samples));
|
||||
|
||||
auto tracingStartTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Mock the performance tracer methods
|
||||
folly::dynamic profileEvent = folly::dynamic::object;
|
||||
folly::dynamic chunkEvent = folly::dynamic::object;
|
||||
|
||||
// Execute
|
||||
serializer.serializeAndNotify(profile, tracingStartTime);
|
||||
|
||||
// Verify
|
||||
// [["Profile"], ["ProfileChunk"]]
|
||||
ASSERT_EQ(notificationEvents_.size(), 2);
|
||||
// (root), (program), (idle)
|
||||
ASSERT_EQ(
|
||||
notificationEvents_[1][0]["args"]["data"]["cpuProfile"]["nodes"].size(),
|
||||
3);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
RuntimeSamplingProfileTraceEventSerializerTest,
|
||||
SamplesFromDifferentThreads) {
|
||||
// Setup
|
||||
auto notificationCallback = createNotificationCallback();
|
||||
RuntimeSamplingProfileTraceEventSerializer serializer(
|
||||
PerformanceTracer::getInstance(), notificationCallback, 10);
|
||||
|
||||
// Create samples with different thread IDs
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack = {
|
||||
createJSCallFrame("foo", 1, "test.js", 10, 5)};
|
||||
|
||||
uint64_t timestamp = 1000000;
|
||||
uint64_t threadId1 = 1;
|
||||
uint64_t threadId2 = 2;
|
||||
|
||||
auto samples = std::vector<RuntimeSamplingProfile::Sample>{};
|
||||
samples.emplace_back(createSample(timestamp, threadId1, callStack));
|
||||
samples.emplace_back(createSample(timestamp + 1000, threadId2, callStack));
|
||||
samples.emplace_back(createSample(timestamp + 2000, threadId1, callStack));
|
||||
|
||||
auto profile = createProfileWithSamples(std::move(samples));
|
||||
|
||||
auto tracingStartTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Execute
|
||||
serializer.serializeAndNotify(profile, tracingStartTime);
|
||||
|
||||
// [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk]]
|
||||
// Samples from different thread should never be grouped together in the same
|
||||
// chunk.
|
||||
ASSERT_EQ(notificationEvents_.size(), 2);
|
||||
ASSERT_EQ(notificationEvents_[1].size(), 3);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
RuntimeSamplingProfileTraceEventSerializerTest,
|
||||
TraceEventChunkSizeLimit) {
|
||||
// Setup
|
||||
auto notificationCallback = createNotificationCallback();
|
||||
uint16_t traceEventChunkSize = 2;
|
||||
uint16_t profileChunkSize = 2;
|
||||
RuntimeSamplingProfileTraceEventSerializer serializer(
|
||||
PerformanceTracer::getInstance(),
|
||||
notificationCallback,
|
||||
traceEventChunkSize,
|
||||
profileChunkSize);
|
||||
|
||||
// Create multiple samples
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack = {
|
||||
createJSCallFrame("foo", 1, "test.js", 10, 5)};
|
||||
|
||||
uint64_t timestamp = 1000000;
|
||||
uint64_t threadId = 1;
|
||||
|
||||
std::vector<RuntimeSamplingProfile::Sample> samples;
|
||||
samples.reserve(5);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
samples.push_back(createSample(timestamp + i * 1000, threadId, callStack));
|
||||
}
|
||||
|
||||
auto profile = createProfileWithSamples(std::move(samples));
|
||||
auto tracingStartTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Execute
|
||||
serializer.serializeAndNotify(profile, tracingStartTime);
|
||||
|
||||
// [["Profile"], ["ProfileChunk", "ProfileChunk"], ["ProfileChunk"]]
|
||||
ASSERT_EQ(notificationEvents_.size(), 3);
|
||||
|
||||
// Check that each chunk has at most traceEventChunkSize events
|
||||
for (size_t i = 1; i < notificationEvents_.size(); i++) {
|
||||
EXPECT_LE(notificationEvents_[i].size(), traceEventChunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) {
|
||||
// Setup
|
||||
auto notificationCallback = createNotificationCallback();
|
||||
// Set a small profile chunk size to test profile chunking
|
||||
uint16_t traceEventChunkSize = 10;
|
||||
uint16_t profileChunkSize = 2;
|
||||
double samplesCount = 5;
|
||||
RuntimeSamplingProfileTraceEventSerializer serializer(
|
||||
PerformanceTracer::getInstance(),
|
||||
notificationCallback,
|
||||
traceEventChunkSize,
|
||||
profileChunkSize);
|
||||
|
||||
// Create multiple samples
|
||||
std::vector<RuntimeSamplingProfile::SampleCallStackFrame> callStack = {
|
||||
createJSCallFrame("foo", 1, "test.js", 10, 5)};
|
||||
|
||||
uint64_t timestamp = 1000000;
|
||||
uint64_t threadId = 1;
|
||||
|
||||
std::vector<RuntimeSamplingProfile::Sample> samples;
|
||||
samples.reserve(samplesCount);
|
||||
for (int i = 0; i < samplesCount; i++) {
|
||||
samples.push_back(createSample(timestamp + i * 1000, threadId, callStack));
|
||||
}
|
||||
|
||||
auto profile = createProfileWithSamples(std::move(samples));
|
||||
auto tracingStartTime = std::chrono::steady_clock::now();
|
||||
|
||||
// Execute
|
||||
serializer.serializeAndNotify(profile, tracingStartTime);
|
||||
|
||||
// [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk"]]
|
||||
ASSERT_EQ(notificationEvents_.size(), 2);
|
||||
ASSERT_EQ(
|
||||
notificationEvents_[1].size(),
|
||||
std::ceil(samplesCount / profileChunkSize));
|
||||
|
||||
for (auto& profileChunk : notificationEvents_[1]) {
|
||||
EXPECT_LE(
|
||||
profileChunk["args"]["data"]["cpuProfile"]["samples"].size(),
|
||||
profileChunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern::tracing
|
||||
Reference in New Issue
Block a user