1818
1919namespace facebook ::react::jsinspector_modern::tracing {
2020
21+ namespace {
22+ /* *
23+ * Instead of validating that the indicated duration is positive, we can ensure
24+ * the value is sensible (less than 1 seccond doesn't make much sense).
25+ */
26+ const HighResDuration MIN_VALUE_FOR_MAX_TRACE_DURATION_OPTION =
27+ HighResDuration::fromMilliseconds (1000 ); // 1 second
28+ } // namespace
29+
2130PerformanceTracer& PerformanceTracer::getInstance () {
2231 static PerformanceTracer tracer;
2332 return tracer;
@@ -27,20 +36,38 @@ PerformanceTracer::PerformanceTracer()
2736 : processId_(oscompat::getCurrentProcessId()) {}
2837
2938bool PerformanceTracer::startTracing () {
39+ return startTracingImpl ();
40+ }
41+
42+ bool PerformanceTracer::startTracing (HighResDuration maxDuration) {
43+ return startTracingImpl (maxDuration);
44+ }
45+
46+ bool PerformanceTracer::startTracingImpl (
47+ std::optional<HighResDuration> maxDuration) {
3048 std::lock_guard lock (mutex_);
3149
3250 if (tracingAtomic_) {
3351 return false ;
3452 }
3553
3654 tracingAtomic_ = true ;
55+
56+ if (maxDuration && *maxDuration < MIN_VALUE_FOR_MAX_TRACE_DURATION_OPTION ) {
57+ throw std::invalid_argument (" maxDuration should be at least 1 second" );
58+ }
59+
3760 currentTraceStartTime_ = HighResTimeStamp::now ();
61+ currentTraceMaxDuration_ = maxDuration;
62+
3863 return true ;
3964}
4065
4166std::optional<std::vector<TraceEvent>> PerformanceTracer::stopTracing () {
4267 std::vector<TraceEvent> events;
4368
69+ auto currentTraceEndTime = HighResTimeStamp::now ();
70+
4471 {
4572 std::lock_guard lock (mutex_);
4673
@@ -50,8 +77,15 @@ std::optional<std::vector<TraceEvent>> PerformanceTracer::stopTracing() {
5077
5178 tracingAtomic_ = false ;
5279 performanceMeasureCount_ = 0 ;
80+ currentTraceMaxDuration_ = std::nullopt ;
5381
54- buffer_.swap (events);
82+ events = collectEventsAndClearBuffers (currentTraceEndTime);
83+ }
84+
85+ auto currentTraceStartTime = currentTraceStartTime_;
86+ if (currentTraceMaxDuration_ &&
87+ currentTraceEndTime - *currentTraceMaxDuration_ > currentTraceStartTime) {
88+ currentTraceStartTime = currentTraceEndTime - *currentTraceMaxDuration_;
5589 }
5690
5791 // This is synthetic Trace Event, which should not be represented on a
@@ -65,7 +99,7 @@ std::optional<std::vector<TraceEvent>> PerformanceTracer::stopTracing() {
6599 .name = " TracingStartedInPage" ,
66100 .cat = " disabled-by-default-devtools.timeline" ,
67101 .ph = ' I' ,
68- .ts = currentTraceStartTime_ ,
102+ .ts = currentTraceStartTime ,
69103 .pid = processId_,
70104 .tid = oscompat::getCurrentThreadId (),
71105 .args = folly::dynamic::object (" data" , folly::dynamic::object ()),
@@ -75,7 +109,7 @@ std::optional<std::vector<TraceEvent>> PerformanceTracer::stopTracing() {
75109 .name = " ReactNative-TracingStopped" ,
76110 .cat = " disabled-by-default-devtools.timeline" ,
77111 .ph = ' I' ,
78- .ts = HighResTimeStamp::now () ,
112+ .ts = currentTraceEndTime ,
79113 .pid = processId_,
80114 .tid = oscompat::getCurrentThreadId (),
81115 });
@@ -302,8 +336,96 @@ PerformanceTracer::constructRuntimeProfileChunkTraceEvent(
302336 };
303337}
304338
339+ #pragma mark - Tracing window methods
340+
341+ /* *
342+ * If a `maxDuration` value is set when starting a trace, we use 2 buffers
343+ * for events. Each buffer can contain entries for a range up to `maxDuration`.
344+ * When the current buffer is full, we clear the previous one and we start
345+ * collecting events in a new buffer, which becomes current.
346+ *
347+ * Example:
348+ * - Start:
349+ * previousBuffer: null, currentBuffer: []
350+ * - As entries are added:
351+ * previousBuffer: null, currentBuffer: [a, b, c...]
352+ * - When now - currentBuffer start time > maxDuration:
353+ * previousBuffer: [a, b, c...], currentBuffer: []
354+ * - As entries are added:
355+ * previousbuffer: [a, b, c...], currentBuffer: [x, y, z...]
356+ * - When now - currentBuffer start time > maxDuration:
357+ * previousBuffer: [x, y, z...], currentBuffer: []
358+ * - When the trace finishes:
359+ * We collect all events in both buffers that are still within
360+ * `maxDuration`.
361+ *
362+ * This way, we ensure we keep all events in the `maxDuration` window, and
363+ * clearing expired events is trivial (just clearing a vector).
364+ */
365+
366+ std::vector<TraceEvent> PerformanceTracer::collectEventsAndClearBuffers (
367+ HighResTimeStamp currentTraceEndTime) {
368+ std::vector<TraceEvent> events;
369+
370+ if (currentTraceMaxDuration_) {
371+ // Collect non-expired entries from the previous buffer
372+ if (previousBuffer_ != nullptr ) {
373+ collectEventsAndClearBuffer (
374+ events, *previousBuffer_, currentTraceEndTime);
375+ }
376+
377+ collectEventsAndClearBuffer (events, *currentBuffer_, currentTraceEndTime);
378+
379+ // Reset state.
380+ currentBuffer_ = &buffer_;
381+ previousBuffer_ = nullptr ;
382+ } else {
383+ // Trivial case.
384+ currentBuffer_->swap (events);
385+ }
386+
387+ return events;
388+ }
389+
390+ void PerformanceTracer::collectEventsAndClearBuffer (
391+ std::vector<TraceEvent>& events,
392+ std::vector<TraceEvent>& buffer,
393+ HighResTimeStamp currentTraceEndTime) {
394+ for (auto && event : buffer) {
395+ if (isInTracingWindow (currentTraceEndTime, event.createdAt_ )) {
396+ events.emplace_back (std::move (event));
397+ }
398+ }
399+
400+ buffer.clear ();
401+ buffer.shrink_to_fit ();
402+ }
403+
404+ bool PerformanceTracer::isInTracingWindow (
405+ HighResTimeStamp now,
406+ HighResTimeStamp timeStampToCheck) const {
407+ if (!currentTraceMaxDuration_) {
408+ return true ;
409+ }
410+
411+ return timeStampToCheck > now - *currentTraceMaxDuration_;
412+ }
413+
305414void PerformanceTracer::enqueueEvent (TraceEvent&& traceEvent) {
306- buffer_.emplace_back (std::move (traceEvent));
415+ if (currentTraceMaxDuration_) {
416+ // Check if the current buffer is "full"
417+ if (traceEvent.createdAt_ >
418+ currentBufferStartTime_ + *currentTraceMaxDuration_) {
419+ // We moved past the current buffer. We need to switch the other buffer as
420+ // current.
421+ previousBuffer_ = currentBuffer_;
422+ currentBuffer_ = currentBuffer_ == &buffer_ ? &altBuffer_ : &buffer_;
423+ currentBuffer_->clear ();
424+ currentBufferStartTime_ = traceEvent.createdAt_ ;
425+ }
426+ }
427+
428+ currentBuffer_->emplace_back (std::move (traceEvent));
307429}
308430
309431} // namespace facebook::react::jsinspector_modern::tracing
0 commit comments