Skip to content

Commit 25062b7

Browse files
authored
enh: improve envelope conversion to Uint8List in FileSystemTransport (#3147)
* Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * melos setup #1: initial set up and start with dart package (#3117) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Add link to packages * Update * Update * Update * Update * Fix test * Fix test * Fix test * Fix test * Fix test * Add flutter to packages * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Clean up * melos #2: logging package (#3129) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * melos #3: dio (#3130) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * melos #4: hive (#3131) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * melos #5: file (#3132) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Update file.yml * melos #6: link (#3133) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Add link to packages * Update * melos #7: drift (#3134) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Add link to packages * Update * Update * Update * Update * Fix test * Fix test * Fix test * Fix test * Fix test * Update drift.yml * melos #8: flutter (#3135) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Add link to packages * Update * Update * Update * Update * Fix test * Fix test * Fix test * Fix test * Fix test * Add flutter to packages * Update * Update * Update * Update * Update * Update * Update * melos #9: isar (#3136) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Add link to packages * Update * Update * Update * Update * Fix test * Fix test * Fix test * Fix test * Fix test * Add flutter to packages * Update * Update * Update * Update * Update * Update * Update * Update * Update * melos #10: sqflite (#3137) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Add link to packages * Update * Update * Update * Update * Fix test * Fix test * Fix test * Fix test * Fix test * Add flutter to packages * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * melos #11: firebase remote config (#3139) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Add link to packages * Update * Update * Update * Update * Fix test * Fix test * Fix test * Fix test * Fix test * Add flutter to packages * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Updaet * Updaet * Update * melos #12: general cleanup (#3142) * Add melos setup and check only set up Dart for now * Update * Update] * Update] * Update * Update * Update * Fix CHANGELOG * Update gitignore * Fix symlink in CHANGELOG * Add logging to melos * Update * Update * Update * Update * Add dio to packages * Update workflow * Update * Update * Update * Update * Update * Add hive to packages * Update * Add file to packages * Add link to packages * Update * Update * Update * Update * Fix test * Fix test * Fix test * Fix test * Fix test * Add flutter to packages * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Clean up * Updaet * Updaet * Update * Update * Update * update * update
1 parent 6b69699 commit 25062b7

File tree

4 files changed

+186
-9
lines changed

4 files changed

+186
-9
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
- Tag all spans with thread info ([#3101](https://github.com/getsentry/sentry-dart/pull/3101))
88

9+
### Enhancements
10+
11+
- Improve envelope conversion to `Uint8List` in `FileSystemTransport` ([#3147](https://github.com/getsentry/sentry-dart/pull/3147))
12+
913
## 9.6.0
1014

1115
Note: this release might require updating your Android Gradle Plugin version to at least `8.1.4`.

packages/flutter/lib/src/file_system_transport.dart

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
// backcompatibility for Flutter < 3.3
2-
// ignore: unnecessary_import
31
import 'dart:typed_data';
42

5-
import 'package:flutter/services.dart';
6-
73
import '../sentry_flutter.dart';
84
import 'native/sentry_native_binding.dart';
95

@@ -15,12 +11,13 @@ class FileSystemTransport implements Transport {
1511

1612
@override
1713
Future<SentryId?> send(SentryEnvelope envelope) async {
18-
final envelopeData = <int>[];
19-
await envelope.envelopeStream(_options).forEach(envelopeData.addAll);
14+
final bytesBuilder = BytesBuilder(copy: false);
15+
await envelope.envelopeStream(_options).forEach(bytesBuilder.add);
16+
final envelopeData = bytesBuilder.takeBytes();
17+
2018
try {
21-
// TODO avoid copy
22-
await _native.captureEnvelope(Uint8List.fromList(envelopeData),
23-
envelope.containsUnhandledException);
19+
await _native.captureEnvelope(
20+
envelopeData, envelope.containsUnhandledException);
2421
} catch (exception, stackTrace) {
2522
_options.log(
2623
SentryLevel.error,

packages/flutter/microbenchmarks/lib/main.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'dart:io';
22
import 'src/image_bench.dart' as image_bench;
33
import 'src/memory_bench.dart' as memory_bench;
44
import 'src/jni_bench.dart' as jni_bench;
5+
import 'src/envelope_builder_bench.dart' as envelope_builder_bench;
56

67
typedef BenchmarkSet = (String name, Future<void> Function() callback);
78

@@ -12,6 +13,7 @@ Future<void> main() async {
1213
('Image', image_bench.execute),
1314
('Memory', memory_bench.execute),
1415
if (Platform.isAndroid) ('JNI', jni_bench.execute),
16+
('Envelope builder', envelope_builder_bench.execute)
1517
];
1618

1719
RegExp? filterRegexp;
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import 'dart:typed_data';
2+
import 'dart:math';
3+
4+
const _minIterations = 50;
5+
const _maxIterations = 1000;
6+
7+
Future<void> execute() async {
8+
print('Envelope Builder Benchmark');
9+
print('==========================');
10+
print('Comparing legacy List<int> vs BytesBuilder approaches\n');
11+
12+
// Test with different envelope sizes
13+
final sizes = [
14+
(1024, '1 KB'),
15+
(10 * 1024, '10 KB'),
16+
(100 * 1024, '100 KB'),
17+
(1024 * 1024, '1 MB'),
18+
(5 * 1024 * 1024, '5 MB'),
19+
];
20+
21+
for (final (size, label) in sizes) {
22+
print('Envelope size: $label');
23+
print('-' * 40);
24+
25+
// Use adaptive iteration count based on data size
26+
final iterations = _getIterationCount(size);
27+
print('Running $iterations iterations...');
28+
29+
// Create mock envelope data
30+
final mockData = _generateMockEnvelopeData(size);
31+
32+
// Benchmark legacy approach
33+
final legacyResults = await _benchmarkLegacyApproach(mockData, iterations);
34+
final legacyAvg =
35+
legacyResults.reduce((a, b) => a + b) / legacyResults.length;
36+
final legacyMin = legacyResults.reduce(min);
37+
final legacyMax = legacyResults.reduce(max);
38+
39+
// Benchmark new approach
40+
final newResults = await _benchmarkNewApproach(mockData, iterations);
41+
final newAvg = newResults.reduce((a, b) => a + b) / newResults.length;
42+
final newMin = newResults.reduce(min);
43+
final newMax = newResults.reduce(max);
44+
45+
// Calculate improvement
46+
final improvement =
47+
((legacyAvg - newAvg) / legacyAvg * 100).toStringAsFixed(1);
48+
final speedup = (legacyAvg / newAvg).toStringAsFixed(2);
49+
50+
print('Legacy approach (List<int> + addAll):');
51+
print(' Average: ${_formatMicroseconds(legacyAvg)}');
52+
print(' Min: ${_formatMicroseconds(legacyMin)}');
53+
print(' Max: ${_formatMicroseconds(legacyMax)}');
54+
55+
print('New approach (BytesBuilder):');
56+
print(' Average: ${_formatMicroseconds(newAvg)}');
57+
print(' Min: ${_formatMicroseconds(newMin)}');
58+
print(' Max: ${_formatMicroseconds(newMax)}');
59+
60+
print('Performance improvement: $improvement% (${speedup}x faster)');
61+
print('');
62+
}
63+
}
64+
65+
// Adaptive iteration count to avoid memory pressure and hanging
66+
int _getIterationCount(int dataSize) {
67+
if (dataSize <= 10 * 1024) {
68+
return _maxIterations; // 1K iterations for <= 10KB
69+
} else if (dataSize <= 100 * 1024) {
70+
return _maxIterations ~/ 2; // 500 iterations for <= 100KB
71+
} else if (dataSize <= 1024 * 1024) {
72+
return _maxIterations ~/ 5; // 200 iterations for <= 1MB
73+
} else {
74+
return _minIterations; // 50 iterations for > 1MB
75+
}
76+
}
77+
78+
// Generate mock envelope data chunks to simulate streaming
79+
List<List<int>> _generateMockEnvelopeData(int totalSize) {
80+
final chunks = <List<int>>[];
81+
final random = Random(42); // Fixed seed for reproducibility
82+
83+
// Simulate realistic chunk sizes (similar to how envelope streams work)
84+
final chunkSizes = [64, 128, 256, 512, 1024];
85+
var remaining = totalSize;
86+
87+
while (remaining > 0) {
88+
final chunkSize = chunkSizes[random.nextInt(chunkSizes.length)];
89+
final actualSize = remaining < chunkSize ? remaining : chunkSize;
90+
91+
// Create chunk with random data
92+
final chunk = List<int>.generate(actualSize, (_) => random.nextInt(256));
93+
chunks.add(chunk);
94+
remaining -= actualSize;
95+
}
96+
97+
return chunks;
98+
}
99+
100+
Future<List<double>> _benchmarkLegacyApproach(
101+
List<List<int>> chunks, int iterations) async {
102+
final results = <double>[];
103+
104+
// Reduced warmup for large data
105+
final warmupIterations = min(20, iterations ~/ 5);
106+
107+
// Warmup
108+
for (var i = 0; i < warmupIterations; i++) {
109+
_runLegacyApproach(chunks);
110+
}
111+
112+
// Actual benchmark
113+
for (var i = 0; i < iterations; i++) {
114+
final stopwatch = Stopwatch()..start();
115+
_runLegacyApproach(chunks);
116+
stopwatch.stop();
117+
results.add(stopwatch.elapsedMicroseconds.toDouble());
118+
}
119+
120+
return results;
121+
}
122+
123+
Future<List<double>> _benchmarkNewApproach(
124+
List<List<int>> chunks, int iterations) async {
125+
final results = <double>[];
126+
127+
// Reduced warmup for large data
128+
final warmupIterations = min(20, iterations ~/ 5);
129+
130+
// Warmup
131+
for (var i = 0; i < warmupIterations; i++) {
132+
_runNewApproach(chunks);
133+
}
134+
135+
// Actual benchmark
136+
for (var i = 0; i < iterations; i++) {
137+
final stopwatch = Stopwatch()..start();
138+
_runNewApproach(chunks);
139+
stopwatch.stop();
140+
results.add(stopwatch.elapsedMicroseconds.toDouble());
141+
}
142+
143+
return results;
144+
}
145+
146+
Uint8List _runLegacyApproach(List<List<int>> chunks) {
147+
final envelopeData = <int>[];
148+
for (final chunk in chunks) {
149+
envelopeData.addAll(chunk);
150+
}
151+
return Uint8List.fromList(envelopeData);
152+
}
153+
154+
Uint8List _runNewApproach(List<List<int>> chunks) {
155+
final builder = BytesBuilder(copy: false);
156+
for (final chunk in chunks) {
157+
builder.add(chunk);
158+
}
159+
return builder.takeBytes();
160+
}
161+
162+
String _formatMicroseconds(double microseconds) {
163+
if (microseconds < 1000) {
164+
return '${microseconds.toStringAsFixed(1)} μs';
165+
} else if (microseconds < 1000000) {
166+
return '${(microseconds / 1000).toStringAsFixed(2)} ms';
167+
} else {
168+
return '${(microseconds / 1000000).toStringAsFixed(2)} s';
169+
}
170+
}
171+
172+
void main() async {
173+
await execute();
174+
}

0 commit comments

Comments
 (0)