Skip to content

Commit 6aa7e9d

Browse files
huntiefacebook-github-bot
authored andcommitted
Move screenshot Base64 encoding to trace serialization (react#55803)
Summary: Refactor Android FrameTimings to reduce memory usage in trace buffer. Binary data instead of Base64 should be ~25% smaller, and eliminates string copies during JNI transfer. Changelog: [Internal] Differential Revision: D94657907
1 parent 34779df commit 6aa7e9d

10 files changed

Lines changed: 57 additions & 20 deletions

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingSequence.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77

88
package com.facebook.react.devsupport.inspector
99

10+
import java.nio.ByteBuffer
11+
1012
internal data class FrameTimingSequence(
1113
val id: Int,
1214
val threadId: Int,
1315
val beginTimestamp: Long,
1416
val endTimestamp: Long,
15-
val screenshot: String? = null,
17+
val screenshot: ByteBuffer? = null,
1618
)

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/inspector/FrameTimingsObserver.kt

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import android.os.Build
1212
import android.os.Handler
1313
import android.os.Looper
1414
import android.os.Process
15-
import android.util.Base64
1615
import android.view.FrameMetrics
1716
import android.view.PixelCopy
1817
import android.view.Window
1918
import com.facebook.proguard.annotations.DoNotStripAny
2019
import java.io.ByteArrayOutputStream
20+
import java.nio.ByteBuffer
2121
import kotlinx.coroutines.CoroutineScope
2222
import kotlinx.coroutines.Dispatchers
2323
import kotlinx.coroutines.launch
@@ -109,7 +109,7 @@ internal class FrameTimingsObserver(
109109
}
110110

111111
// Must be called from the main thread so that PixelCopy captures the current frame.
112-
private fun captureScreenshot(callback: (String?) -> Unit) {
112+
private fun captureScreenshot(callback: (ByteBuffer?) -> Unit) {
113113
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
114114
callback(null)
115115
return
@@ -143,7 +143,12 @@ internal class FrameTimingsObserver(
143143
)
144144
}
145145

146-
private fun encodeScreenshot(window: Window, bitmap: Bitmap, width: Int, height: Int): String? {
146+
private fun encodeScreenshot(
147+
window: Window,
148+
bitmap: Bitmap,
149+
width: Int,
150+
height: Int,
151+
): ByteBuffer? {
147152
var scaledBitmap: Bitmap? = null
148153
return try {
149154
val density = window.context.resources.displayMetrics.density
@@ -155,9 +160,15 @@ internal class FrameTimingsObserver(
155160
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Bitmap.CompressFormat.WEBP_LOSSY
156161
else Bitmap.CompressFormat.JPEG
157162

158-
ByteArrayOutputStream().use { outputStream ->
163+
// 64 KB as an initial ByteArray capacity, to avoid unnecessary resizing
164+
ByteArrayOutputStream(65536).use { outputStream ->
159165
scaledBitmap.compress(compressFormat, SCREENSHOT_QUALITY, outputStream)
160-
Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP)
166+
val bytes = outputStream.toByteArray()
167+
// Send exact sized ByteBuffer
168+
ByteBuffer.allocateDirect(bytes.size).apply {
169+
put(bytes)
170+
flip()
171+
}
161172
}
162173
} catch (e: Exception) {
163174
null

packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#pragma once
99

10+
#include <fbjni/ByteBuffer.h>
1011
#include <fbjni/fbjni.h>
1112

1213
#include <jsinspector-modern/HostTarget.h>
@@ -98,13 +99,19 @@ struct JFrameTimingSequence : public jni::JavaClass<JFrameTimingSequence> {
9899
std::chrono::steady_clock::time_point(std::chrono::nanoseconds(getFieldValue(field))));
99100
}
100101

101-
std::optional<std::string> getScreenshot() const
102+
std::optional<std::vector<uint8_t>> getScreenshot() const
102103
{
103-
auto field = javaClassStatic()->getField<jstring>("screenshot");
104+
auto field = javaClassStatic()->getField<jni::JByteBuffer>("screenshot");
104105
auto javaScreenshot = getFieldValue(field);
105106
if (javaScreenshot) {
106-
auto jstring = jni::static_ref_cast<jni::JString>(javaScreenshot);
107-
return jstring->toStdString();
107+
auto byteBuffer = jni::static_ref_cast<jni::JByteBuffer>(javaScreenshot);
108+
auto data = byteBuffer->getDirectBytes();
109+
auto size = byteBuffer->getDirectSize();
110+
if (data != nullptr && size > 0) {
111+
std::vector<uint8_t> result(size);
112+
std::memcpy(result.data(), data, size);
113+
return result;
114+
}
108115
}
109116
return std::nullopt;
110117
}

packages/react-native/ReactAndroid/src/main/jni/third-party/folly/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ SET(folly_runtime_SRC
3838
folly/detail/Futex.cpp
3939
folly/detail/SplitStringSimd.cpp
4040
folly/detail/UniqueInstance.cpp
41+
folly/base64.cpp
4142
folly/hash/SpookyHashV2.cpp
4243
folly/json/dynamic.cpp
4344
folly/json/json_pointer.cpp

packages/react-native/ReactCommon/jsinspector-modern/tests/TracingTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ TEST_F(TracingTest, EmitsScreenshotEventWhenScreenshotValuePassed) {
103103
11, // threadId
104104
now,
105105
now + HighResDuration::fromNanoseconds(50),
106-
"base64EncodedScreenshotData"));
106+
std::vector<uint8_t>{}));
107107

108108
auto allTraceEvents = endTracingAndCollectEvents();
109109
EXPECT_THAT(allTraceEvents, Contains(AtJsonPtr("/name", "Screenshot")));

packages/react-native/ReactCommon/jsinspector-modern/tracing/FrameTimingSequence.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
#include <react/timing/primitives.h>
1313

14+
#include <cstdint>
15+
#include <optional>
16+
#include <vector>
17+
1418
namespace facebook::react::jsinspector_modern::tracing {
1519

1620
using FrameSequenceId = uint64_t;
@@ -26,7 +30,7 @@ struct FrameTimingSequence {
2630
ThreadId threadId,
2731
HighResTimeStamp beginTimestamp,
2832
HighResTimeStamp endTimestamp,
29-
std::optional<std::string> screenshot = std::nullopt)
33+
std::optional<std::vector<uint8_t>> screenshot = std::nullopt)
3034
: id(id),
3135
threadId(threadId),
3236
beginTimestamp(beginTimestamp),
@@ -49,9 +53,9 @@ struct FrameTimingSequence {
4953
HighResTimeStamp endTimestamp;
5054

5155
/**
52-
* Optional screenshot data (base64 encoded) captured during the frame.
56+
* Optional screenshot data captured during the frame.
5357
*/
54-
std::optional<std::string> screenshot;
58+
std::optional<std::vector<uint8_t>> screenshot;
5559
};
5660

5761
} // namespace facebook::react::jsinspector_modern::tracing

packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventGenerator.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "Timing.h"
1010
#include "TracingCategory.h"
1111

12+
#include <folly/base64.h>
13+
1214
namespace facebook::react::jsinspector_modern::tracing {
1315

1416
/* static */ TraceEvent TraceEventGenerator::createSetLayerTreeIdEvent(
@@ -70,14 +72,19 @@ TraceEventGenerator::createFrameTimingsEvents(
7072
/* static */ TraceEvent TraceEventGenerator::createScreenshotEvent(
7173
FrameSequenceId frameSequenceId,
7274
int sourceId,
73-
std::string&& snapshot,
75+
std::vector<uint8_t>&& snapshot,
7476
HighResTimeStamp expectedDisplayTime,
7577
ProcessId processId,
7678
ThreadId threadId) {
77-
folly::dynamic args = folly::dynamic::object("snapshot", std::move(snapshot))(
78-
"source_id", sourceId)("frame_sequence", frameSequenceId)(
79-
"expected_display_time",
80-
highResTimeStampToTracingClockTimeStamp(expectedDisplayTime));
79+
// Convert binary data to string for Base64 encoding
80+
std::string snapshotBytes(snapshot.begin(), snapshot.end());
81+
std::string base64Snapshot = folly::base64Encode(snapshotBytes);
82+
83+
folly::dynamic args =
84+
folly::dynamic::object("snapshot", std::move(base64Snapshot))(
85+
"source_id", sourceId)("frame_sequence", frameSequenceId)(
86+
"expected_display_time",
87+
highResTimeStampToTracingClockTimeStamp(expectedDisplayTime));
8188

8289
return TraceEvent{
8390
.name = "Screenshot",

packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventGenerator.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
#include <jsinspector-modern/tracing/FrameTimingSequence.h>
1313
#include <react/timing/primitives.h>
1414

15+
#include <cstdint>
1516
#include <utility>
17+
#include <vector>
1618

1719
namespace facebook::react::jsinspector_modern::tracing {
1820

@@ -49,7 +51,7 @@ class TraceEventGenerator {
4951
static TraceEvent createScreenshotEvent(
5052
FrameSequenceId frameSequenceId,
5153
int sourceId,
52-
std::string &&snapshot,
54+
std::vector<uint8_t> &&snapshot,
5355
HighResTimeStamp expectedDisplayTime,
5456
ProcessId processId,
5557
ThreadId threadId);

packages/react-native/third-party-podspecs/RCT-Folly.podspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Pod::Spec.new do |spec|
6363
'folly/container/*.h',
6464
'folly/container/detail/*.h',
6565
'folly/detail/*.h',
66+
'folly/detail/base64_detail/*.h',
6667
'folly/functional/*.h',
6768
'folly/hash/*.h',
6869
'folly/json/*.h',
@@ -82,6 +83,7 @@ Pod::Spec.new do |spec|
8283
'folly/container/*.h',
8384
'folly/container/detail/*.h',
8485
'folly/detail/*.h',
86+
'folly/detail/base64_detail/*.h',
8587
'folly/functional/*.h',
8688
'folly/hash/*.h',
8789
'folly/json/*.h',

private/react-native-fantom/tester/third-party/folly/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ SET(folly_runtime_SRC
3838
folly/detail/Futex.cpp
3939
folly/detail/SplitStringSimd.cpp
4040
folly/detail/UniqueInstance.cpp
41+
folly/base64.cpp
4142
folly/hash/SpookyHashV2.cpp
4243
folly/io/IOBuf.cpp
4344
folly/json/dynamic.cpp

0 commit comments

Comments
 (0)