1
1
package io .sentry .react ;
2
2
3
+ import static java .util .concurrent .TimeUnit .SECONDS ;
3
4
import static io .sentry .android .core .internal .util .ScreenshotUtils .takeScreenshot ;
5
+ import static io .sentry .vendor .Base64 .NO_PADDING ;
6
+ import static io .sentry .vendor .Base64 .NO_WRAP ;
4
7
5
8
import android .app .Activity ;
6
9
import android .content .Context ;
28
31
29
32
import java .io .BufferedInputStream ;
30
33
import java .io .BufferedReader ;
34
+ import java .io .ByteArrayOutputStream ;
31
35
import java .io .File ;
36
+ import java .io .FileInputStream ;
32
37
import java .io .FileNotFoundException ;
33
38
import java .io .FileReader ;
39
+ import java .io .IOException ;
34
40
import java .io .InputStream ;
35
41
import java .nio .charset .Charset ;
36
42
import java .util .HashMap ;
37
43
import java .util .List ;
38
44
import java .util .Map ;
45
+ import java .util .Properties ;
39
46
import java .util .concurrent .CountDownLatch ;
40
- import java .util .concurrent .TimeUnit ;
41
47
42
48
import io .sentry .Breadcrumb ;
43
49
import io .sentry .DateUtils ;
44
50
import io .sentry .HubAdapter ;
45
51
import io .sentry .ILogger ;
52
+ import io .sentry .ISentryExecutorService ;
46
53
import io .sentry .IScope ;
47
54
import io .sentry .ISerializer ;
48
55
import io .sentry .Integration ;
49
56
import io .sentry .Sentry ;
50
57
import io .sentry .SentryDate ;
51
58
import io .sentry .SentryEvent ;
59
+ import io .sentry .SentryExecutorService ;
52
60
import io .sentry .SentryLevel ;
53
61
import io .sentry .SentryOptions ;
54
62
import io .sentry .UncaughtExceptionHandlerIntegration ;
55
63
import io .sentry .android .core .AndroidLogger ;
64
+ import io .sentry .android .core .AndroidProfiler ;
56
65
import io .sentry .android .core .AnrIntegration ;
57
66
import io .sentry .android .core .BuildConfig ;
58
67
import io .sentry .android .core .BuildInfoProvider ;
62
71
import io .sentry .android .core .SentryAndroid ;
63
72
import io .sentry .android .core .SentryAndroidOptions ;
64
73
import io .sentry .android .core .ViewHierarchyEventProcessor ;
74
+ import io .sentry .android .core .internal .debugmeta .AssetsDebugMetaLoader ;
75
+ import io .sentry .android .core .internal .util .SentryFrameMetricsCollector ;
65
76
import io .sentry .android .core .performance .AppStartMetrics ;
66
77
import io .sentry .protocol .SdkVersion ;
67
78
import io .sentry .protocol .SentryException ;
68
79
import io .sentry .protocol .SentryPackage ;
69
80
import io .sentry .protocol .User ;
70
81
import io .sentry .protocol .ViewHierarchy ;
82
+ import io .sentry .util .DebugMetaPropertiesApplier ;
71
83
import io .sentry .util .JsonSerializationUtils ;
72
84
import io .sentry .vendor .Base64 ;
85
+ import io .sentry .util .FileUtils ;
73
86
74
87
public class RNSentryModuleImpl {
75
88
@@ -96,6 +109,23 @@ public class RNSentryModuleImpl {
96
109
97
110
private static final int SCREENSHOT_TIMEOUT_SECONDS = 2 ;
98
111
112
+ /**
113
+ * Profiling traces rate. 101 hz means 101 traces in 1 second. Defaults to 101 to avoid possible
114
+ * lockstep sampling. More on
115
+ * https://stackoverflow.com/questions/45470758/what-is-lockstep-sampling
116
+ */
117
+ private int profilingTracesHz = 101 ;
118
+
119
+ private AndroidProfiler androidProfiler = null ;
120
+
121
+ private boolean isProguardDebugMetaLoaded = false ;
122
+ private @ Nullable String proguardUuid = null ;
123
+ private String cacheDirPath = null ;
124
+ private ISentryExecutorService executorService = null ;
125
+
126
+ /** Max trace file size in bytes. */
127
+ private long maxTraceFileSize = 5 * 1024 * 1024 ;
128
+
99
129
public RNSentryModuleImpl (ReactApplicationContext reactApplicationContext ) {
100
130
packageInfo = getPackageInfo (reactApplicationContext );
101
131
this .reactApplicationContext = reactApplicationContext ;
@@ -393,7 +423,7 @@ private static byte[] takeScreenshotOnUiThread(Activity activity) {
393
423
}
394
424
395
425
try {
396
- doneSignal .await (SCREENSHOT_TIMEOUT_SECONDS , TimeUnit . SECONDS );
426
+ doneSignal .await (SCREENSHOT_TIMEOUT_SECONDS , SECONDS );
397
427
} catch (InterruptedException e ) {
398
428
logger .log (SentryLevel .ERROR , "Screenshot process was interrupted." );
399
429
return null ;
@@ -611,10 +641,41 @@ public void disableNativeFramesTracking() {
611
641
}
612
642
}
613
643
644
+ private String getProfilingTracesDirPath () {
645
+ if (cacheDirPath == null ) {
646
+ cacheDirPath = new File (getReactApplicationContext ().getCacheDir (), "sentry/react" ).getAbsolutePath ();
647
+ }
648
+ File profilingTraceDir = new File (cacheDirPath , "profiling_trace" );
649
+ profilingTraceDir .mkdirs ();
650
+ return profilingTraceDir .getAbsolutePath ();
651
+ }
652
+
653
+ private void initializeAndroidProfiler () {
654
+ if (executorService == null ) {
655
+ executorService = new SentryExecutorService ();
656
+ }
657
+ final String tracesFilesDirPath = getProfilingTracesDirPath ();
658
+
659
+ androidProfiler = new AndroidProfiler (
660
+ tracesFilesDirPath ,
661
+ (int ) SECONDS .toMicros (1 ) / profilingTracesHz ,
662
+ new SentryFrameMetricsCollector (reactApplicationContext , logger , buildInfo ),
663
+ executorService ,
664
+ logger ,
665
+ buildInfo
666
+ );
667
+ }
668
+
614
669
public WritableMap startProfiling () {
615
670
final WritableMap result = new WritableNativeMap ();
671
+ if (androidProfiler == null ) {
672
+ initializeAndroidProfiler ();
673
+ }
674
+
616
675
try {
617
676
HermesSamplingProfiler .enable ();
677
+ androidProfiler .start ();
678
+
618
679
result .putBoolean ("started" , true );
619
680
} catch (Throwable e ) {
620
681
result .putBoolean ("started" , false );
@@ -628,27 +689,26 @@ public WritableMap stopProfiling() {
628
689
final WritableMap result = new WritableNativeMap ();
629
690
File output = null ;
630
691
try {
692
+ AndroidProfiler .ProfileEndData end = androidProfiler .endAndCollect (false , null );
631
693
HermesSamplingProfiler .disable ();
632
694
633
695
output = File .createTempFile (
634
696
"sampling-profiler-trace" , ".cpuprofile" , reactApplicationContext .getCacheDir ());
635
-
636
697
if (isDebug ) {
637
698
logger .log (SentryLevel .INFO , "Profile saved to: " + output .getAbsolutePath ());
638
699
}
639
700
640
- try ( final BufferedReader br = new BufferedReader ( new FileReader ( output ));) {
641
- HermesSamplingProfiler . dumpSampledTraceToFile ( output . getPath ( ));
701
+ HermesSamplingProfiler . dumpSampledTraceToFile ( output . getPath ( ));
702
+ result . putString ( "profile" , readStringFromFile ( output ));
642
703
643
- final StringBuilder text = new StringBuilder ();
644
- String line ;
645
- while ((line = br .readLine ()) != null ) {
646
- text .append (line );
647
- text .append ('\n' );
648
- }
704
+ WritableMap androidProfile = new WritableNativeMap ();
705
+ byte [] androidProfileBytes = FileUtils .readBytesFromFile (end .traceFile .getPath (), maxTraceFileSize );
706
+ String base64AndroidProfile = Base64 .encodeToString (androidProfileBytes , NO_WRAP | NO_PADDING );
649
707
650
- result .putString ("profile" , text .toString ());
651
- }
708
+ androidProfile .putString ("sampled_profile" , base64AndroidProfile );
709
+ androidProfile .putInt ("android_api_level" , buildInfo .getSdkInfoVersion ());
710
+ androidProfile .putString ("build_id" , getProguardUuid ());
711
+ result .putMap ("androidProfile" , androidProfile );
652
712
} catch (Throwable e ) {
653
713
result .putString ("error" , e .toString ());
654
714
} finally {
@@ -666,6 +726,32 @@ public WritableMap stopProfiling() {
666
726
return result ;
667
727
}
668
728
729
+ private @ Nullable String getProguardUuid () {
730
+ if (isProguardDebugMetaLoaded ) {
731
+ return proguardUuid ;
732
+ }
733
+ isProguardDebugMetaLoaded = true ;
734
+ final @ Nullable Properties debugMeta = (new AssetsDebugMetaLoader (this .getReactApplicationContext (), logger )).loadDebugMeta ();
735
+ if (debugMeta != null ) {
736
+ proguardUuid = DebugMetaPropertiesApplier .getProguardUuid (debugMeta );
737
+ return proguardUuid ;
738
+ }
739
+ return null ;
740
+ }
741
+
742
+ private String readStringFromFile (File path ) throws IOException {
743
+ try (final BufferedReader br = new BufferedReader (new FileReader (path ));) {
744
+
745
+ final StringBuilder text = new StringBuilder ();
746
+ String line ;
747
+ while ((line = br .readLine ()) != null ) {
748
+ text .append (line );
749
+ text .append ('\n' );
750
+ }
751
+ return text .toString ();
752
+ }
753
+ }
754
+
669
755
public void fetchNativeDeviceContexts (Promise promise ) {
670
756
final @ NotNull SentryOptions options = HubAdapter .getInstance ().getOptions ();
671
757
if (!(options instanceof SentryAndroidOptions )) {
0 commit comments