@sansavision/apphelm-react-native-profiler 0.0.0-stage → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ApphelmReactNativeProfiler.podspec +35 -0
- package/CHANGELOG.md +14 -0
- package/LICENSE +3 -0
- package/README.md +41 -2
- package/android/build.gradle +30 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/dev/apphelm/reactnative/profiler/ApphelmHermesProfilerModule.java +106 -0
- package/android/src/main/java/dev/apphelm/reactnative/profiler/ApphelmHermesProfilerPackage.java +24 -0
- package/index.d.ts +7 -0
- package/index.js +13 -0
- package/ios/ApphelmHermesProfiler.mm +127 -0
- package/package.json +14 -4
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
# React Native's version decides which Hermes API the module calls (IHermesRootAPI from 0.81, as
|
|
6
|
+
# Sentry React Native's podspec decides) and whether Hermes is the default engine (0.70 and later).
|
|
7
|
+
rn_package_path = ENV["REACT_NATIVE_NODE_MODULES_DIR"] ? File.join(ENV["REACT_NATIVE_NODE_MODULES_DIR"], "package.json") : `node --print "require.resolve('react-native/package.json')"`.strip
|
|
8
|
+
rn_version = File.exist?(rn_package_path) ? JSON.parse(File.read(rn_package_path))["version"].to_s.split(".").map(&:to_i) : [0, 0]
|
|
9
|
+
rn_major, rn_minor = rn_version[0] || 0, rn_version[1] || 0
|
|
10
|
+
new_hermes_runtime = rn_major >= 1 || (rn_major == 0 && rn_minor >= 81)
|
|
11
|
+
hermes_default = rn_major >= 1 || (rn_major == 0 && rn_minor >= 70)
|
|
12
|
+
using_hermes = (ENV["USE_HERMES"].nil? && hermes_default) || ENV["USE_HERMES"] == "1"
|
|
13
|
+
|
|
14
|
+
Pod::Spec.new do |s|
|
|
15
|
+
s.name = "ApphelmReactNativeProfiler"
|
|
16
|
+
s.version = package["version"]
|
|
17
|
+
s.summary = package["description"]
|
|
18
|
+
s.homepage = "https://apphelm.dev"
|
|
19
|
+
s.license = { :type => "Proprietary", :file => "LICENSE" }
|
|
20
|
+
s.authors = "SansaVision"
|
|
21
|
+
s.platforms = { :ios => "13.4" }
|
|
22
|
+
s.source = { :git => "https://github.com/Sansa-Organisation/apphelm.git", :tag => "v#{s.version}" }
|
|
23
|
+
s.source_files = "ios/**/*.{h,m,mm}"
|
|
24
|
+
s.requires_arc = true
|
|
25
|
+
s.pod_target_xcconfig = {
|
|
26
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
|
|
27
|
+
"OTHER_CPLUSPLUSFLAGS" => "$(inherited)" + (new_hermes_runtime ? " -DAPPHELM_NEW_HERMES_RUNTIME" : ""),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
s.dependency "React-Core"
|
|
31
|
+
if using_hermes
|
|
32
|
+
s.dependency "React-hermes"
|
|
33
|
+
s.dependency "hermes-engine"
|
|
34
|
+
end
|
|
35
|
+
end
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.8.1 (unreleased)
|
|
4
|
+
|
|
5
|
+
No code changes; released in lockstep at 0.8.1. The README now says Hermes profile frames are mapped to source through the bundle's uploaded source map (the server already did this).
|
|
6
|
+
|
|
7
|
+
## 0.8.0 (released 7 Nov 2026)
|
|
8
|
+
|
|
9
|
+
- First version (for SDK 0.8.0): the native half of `@sansavision/apphelm/react-native-profiling`. An
|
|
10
|
+
Android module (`HermesSamplingProfiler.enable()`, `disable()`, `dumpSampledTraceToFile()` into the
|
|
11
|
+
cache directory) and an iOS module (`HermesRuntime::enableSamplingProfiler()` and
|
|
12
|
+
`dumpSampledTraceToStream()`, or `IHermesRootAPI` from React Native 0.81), as Sentry React Native
|
|
13
|
+
starts Hermes's sampling profiler. `hermesSampler` is null when the module is not linked. Compiled
|
|
14
|
+
against stubs of React Native's classes and headers; not yet built in an app or run on a device.
|
package/LICENSE
ADDED
package/README.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @sansavision/apphelm-react-native-profiler
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The native half of AppHelm's React Native profiling. It starts and stops Hermes's sampling profiler on Android and iOS, so `@sansavision/apphelm/react-native-profiling` can profile your app's sampled transactions. The rest of the AppHelm React Native SDK has no native code: install this package only if you want profiles.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @sansavision/apphelm @sansavision/apphelm-react-native-profiler
|
|
7
|
+
cd ios && pod install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
React Native's autolinking picks the module up (Android: `ApphelmHermesProfilerPackage`; iOS: the `ApphelmReactNativeProfiler` pod). Rebuild the app afterwards: a JavaScript reload is not enough.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Platform } from "react-native";
|
|
14
|
+
import { createReactNativeSdk } from "@sansavision/apphelm/react-native";
|
|
15
|
+
import { hermesProfiling } from "@sansavision/apphelm/react-native-profiling";
|
|
16
|
+
import { hermesSampler } from "@sansavision/apphelm-react-native-profiler";
|
|
17
|
+
|
|
18
|
+
const apphelm = createReactNativeSdk({
|
|
19
|
+
apiKey, workspaceId, productId, release: "app@2.3.0+45",
|
|
20
|
+
tracesSampleRate: 0.2,
|
|
21
|
+
profiling: hermesProfiling({ profilesSampleRate: 0.5, sampler: hermesSampler, platform: Platform.OS }),
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## What it does
|
|
26
|
+
|
|
27
|
+
- `start()` calls `HermesSamplingProfiler.enable()` (Android) or `HermesRuntime::enableSamplingProfiler()` (iOS; `IHermesRootAPI` from React Native 0.81). Hermes then samples the JavaScript thread about every 10 ms.
|
|
28
|
+
- `stop()` disables the profiler and returns its trace: on Android it is dumped to a file in the app's cache directory, read and deleted; on iOS it is written to memory.
|
|
29
|
+
- When the React instance goes away (a reload, the app closing) a running profiler is disabled.
|
|
30
|
+
- Every failure resolves to `false` or `null`, never an error: the transaction is then sent without a profile.
|
|
31
|
+
|
|
32
|
+
This is how Sentry React Native profiles Hermes (`RNSentryModuleImpl.startProfiling` on Android, `RNSentryHermesSampler.mm` on iOS). Unlike Sentry, AppHelm doesn't add a native platform profile (Android's method trace, or sentry-cocoa's) to the Hermes profile: the AppHelm Swift and Kotlin SDKs profile native code on their own.
|
|
33
|
+
|
|
34
|
+
## Limits
|
|
35
|
+
|
|
36
|
+
- **Hermes only.** On JavaScriptCore `start()` resolves to `false` and nothing is profiled. Expo Go has no custom native modules: use a development build.
|
|
37
|
+
- `hermesSampler` is `null` when the module isn't linked (the app wasn't rebuilt); `hermesProfiling` then does nothing.
|
|
38
|
+
- One profile at a time, at most 30 seconds (the JavaScript side stops it).
|
|
39
|
+
- Release builds name frames by their Hermes bytecode position (`app:///index.android.bundle:1:<offset>`), with minified function names. AppHelm stores those positions and maps them back to your source when the profile is shown, through the bundle's uploaded source map (`apphelm sourcemaps upload --bundle`), naming functions by Metro's function map (`x_facebook_sources`), as it does for errors. A map uploaded after the profile still applies.
|
|
40
|
+
- Not yet built in a React Native app or run on a device by AppHelm: the Java module was compiled against stubs of React Native's classes and the iOS module type-checked against stubs of its headers.
|
|
41
|
+
|
|
42
|
+
Requires React Native 0.71 or later.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// The Android half of @sansavision/apphelm-react-native-profiler, built by the app's Gradle
|
|
2
|
+
// through React Native's autolinking. HermesSamplingProfiler comes with React Native itself.
|
|
3
|
+
apply plugin: "com.android.library"
|
|
4
|
+
|
|
5
|
+
def safeExtGet(prop, fallback) {
|
|
6
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
android {
|
|
10
|
+
namespace "dev.apphelm.reactnative.profiler"
|
|
11
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 35)
|
|
12
|
+
defaultConfig {
|
|
13
|
+
minSdkVersion safeExtGet("minSdkVersion", 24)
|
|
14
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 35)
|
|
15
|
+
}
|
|
16
|
+
compileOptions {
|
|
17
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
18
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
repositories {
|
|
23
|
+
google()
|
|
24
|
+
mavenCentral()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
dependencies {
|
|
28
|
+
// Resolved by the React Native Gradle plugin to the app's react-android.
|
|
29
|
+
implementation "com.facebook.react:react-native:+"
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
package/android/src/main/java/dev/apphelm/reactnative/profiler/ApphelmHermesProfilerModule.java
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
package dev.apphelm.reactnative.profiler;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import com.facebook.hermes.instrumentation.HermesSamplingProfiler;
|
|
5
|
+
import com.facebook.react.bridge.Promise;
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
7
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
8
|
+
import com.facebook.react.bridge.ReactMethod;
|
|
9
|
+
import java.io.ByteArrayOutputStream;
|
|
10
|
+
import java.io.File;
|
|
11
|
+
import java.io.FileInputStream;
|
|
12
|
+
import java.io.InputStream;
|
|
13
|
+
import java.nio.charset.StandardCharsets;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Starts and stops Hermes's sampling profiler for AppHelm's React Native profiling, as Sentry
|
|
17
|
+
* React Native's RNSentryModuleImpl does: {@code HermesSamplingProfiler.enable()}, then
|
|
18
|
+
* {@code disable()} and {@code dumpSampledTraceToFile()} into the app's cache directory; the file
|
|
19
|
+
* is read, returned to JavaScript and deleted. Every failure (JavaScriptCore, an old Hermes, no
|
|
20
|
+
* cache directory) resolves to false or null: profiling must never break the app.
|
|
21
|
+
*/
|
|
22
|
+
public class ApphelmHermesProfilerModule extends ReactContextBaseJavaModule {
|
|
23
|
+
public static final String NAME = "ApphelmHermesProfiler";
|
|
24
|
+
/** A trace larger than this is not read (a 30-second profile is far smaller). */
|
|
25
|
+
private static final long MAX_TRACE_BYTES = 16L * 1024 * 1024;
|
|
26
|
+
|
|
27
|
+
private final Object lock = new Object();
|
|
28
|
+
private boolean running = false;
|
|
29
|
+
|
|
30
|
+
public ApphelmHermesProfilerModule(ReactApplicationContext context) {
|
|
31
|
+
super(context);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@NonNull
|
|
35
|
+
@Override
|
|
36
|
+
public String getName() {
|
|
37
|
+
return NAME;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@ReactMethod
|
|
41
|
+
public void start(Promise promise) {
|
|
42
|
+
synchronized (lock) {
|
|
43
|
+
try {
|
|
44
|
+
// Hermes keeps no state to ask about: clear a profile a previous run left enabled.
|
|
45
|
+
HermesSamplingProfiler.disable();
|
|
46
|
+
HermesSamplingProfiler.enable();
|
|
47
|
+
running = true;
|
|
48
|
+
promise.resolve(true);
|
|
49
|
+
} catch (Throwable error) {
|
|
50
|
+
running = false;
|
|
51
|
+
promise.resolve(false);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@ReactMethod
|
|
57
|
+
public void stop(Promise promise) {
|
|
58
|
+
File output = null;
|
|
59
|
+
try {
|
|
60
|
+
synchronized (lock) {
|
|
61
|
+
if (!running) {
|
|
62
|
+
promise.resolve(null);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
running = false;
|
|
66
|
+
HermesSamplingProfiler.disable();
|
|
67
|
+
}
|
|
68
|
+
output = File.createTempFile("apphelm-hermes-", ".cpuprofile", getReactApplicationContext().getCacheDir());
|
|
69
|
+
HermesSamplingProfiler.dumpSampledTraceToFile(output.getPath());
|
|
70
|
+
promise.resolve(read(output));
|
|
71
|
+
} catch (Throwable error) {
|
|
72
|
+
promise.resolve(null);
|
|
73
|
+
} finally {
|
|
74
|
+
if (output != null) {
|
|
75
|
+
//noinspection ResultOfMethodCallIgnored
|
|
76
|
+
output.delete();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The sampler must not outlive the React instance (a reload, the app closing). */
|
|
82
|
+
@Override
|
|
83
|
+
public void invalidate() {
|
|
84
|
+
synchronized (lock) {
|
|
85
|
+
if (running) {
|
|
86
|
+
running = false;
|
|
87
|
+
try {
|
|
88
|
+
HermesSamplingProfiler.disable();
|
|
89
|
+
} catch (Throwable ignored) {
|
|
90
|
+
// Best effort on teardown.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
super.invalidate();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private static String read(File file) throws Exception {
|
|
98
|
+
if (!file.exists() || file.length() == 0 || file.length() > MAX_TRACE_BYTES) return null;
|
|
99
|
+
try (InputStream input = new FileInputStream(file); ByteArrayOutputStream bytes = new ByteArrayOutputStream((int) file.length())) {
|
|
100
|
+
byte[] buffer = new byte[64 * 1024];
|
|
101
|
+
int read;
|
|
102
|
+
while ((read = input.read(buffer)) != -1) bytes.write(buffer, 0, read);
|
|
103
|
+
return new String(bytes.toByteArray(), StandardCharsets.UTF_8);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
package/android/src/main/java/dev/apphelm/reactnative/profiler/ApphelmHermesProfilerPackage.java
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package dev.apphelm.reactnative.profiler;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import com.facebook.react.ReactPackage;
|
|
5
|
+
import com.facebook.react.bridge.NativeModule;
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
7
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
8
|
+
import java.util.Collections;
|
|
9
|
+
import java.util.List;
|
|
10
|
+
|
|
11
|
+
/** Registered by React Native's autolinking. */
|
|
12
|
+
public class ApphelmHermesProfilerPackage implements ReactPackage {
|
|
13
|
+
@NonNull
|
|
14
|
+
@Override
|
|
15
|
+
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext context) {
|
|
16
|
+
return Collections.<NativeModule>singletonList(new ApphelmHermesProfilerModule(context));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@NonNull
|
|
20
|
+
@Override
|
|
21
|
+
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext context) {
|
|
22
|
+
return Collections.emptyList();
|
|
23
|
+
}
|
|
24
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Hermes's sampling profiler, for `hermesProfiling({ sampler })`; null when the native module isn't linked. */
|
|
2
|
+
export declare const hermesSampler: {
|
|
3
|
+
/** Start Hermes's sampling profiler; false on JavaScriptCore or when it can't start. */
|
|
4
|
+
start(): Promise<boolean>;
|
|
5
|
+
/** Stop it and return its trace (Hermes's Chrome trace JSON), or null. */
|
|
6
|
+
stop(): Promise<string | null>;
|
|
7
|
+
} | null;
|
package/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The native module (android/ and ios/) as the `sampler` that
|
|
2
|
+
// @sansavision/apphelm/react-native-profiling's `hermesProfiling` takes. When the module isn't
|
|
3
|
+
// linked (Expo Go, a build made before installing this package) it is null and nothing is profiled.
|
|
4
|
+
import { NativeModules } from "react-native";
|
|
5
|
+
|
|
6
|
+
const native = NativeModules.ApphelmHermesProfiler;
|
|
7
|
+
|
|
8
|
+
export const hermesSampler = native && typeof native.start === "function" && typeof native.stop === "function"
|
|
9
|
+
? {
|
|
10
|
+
start: () => native.start().then((started) => started === true, () => false),
|
|
11
|
+
stop: () => native.stop().then((trace) => (typeof trace === "string" && trace.length > 0 ? trace : null), () => null),
|
|
12
|
+
}
|
|
13
|
+
: null;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// The iOS half of @sansavision/apphelm-react-native-profiler: starts and stops Hermes's sampling
|
|
2
|
+
// profiler for AppHelm's React Native profiling, as Sentry React Native's RNSentryHermesSampler.mm
|
|
3
|
+
// does (HermesRuntime::enableSamplingProfiler / disableSamplingProfiler /
|
|
4
|
+
// dumpSampledTraceToStream; the IHermesRootAPI form from React Native 0.81). Without Hermes
|
|
5
|
+
// (JavaScriptCore) `start` resolves to NO and nothing is profiled. No call ever rejects.
|
|
6
|
+
|
|
7
|
+
#import <Foundation/Foundation.h>
|
|
8
|
+
#import <React/RCTBridgeModule.h>
|
|
9
|
+
#import <mutex>
|
|
10
|
+
#import <sstream>
|
|
11
|
+
#import <string>
|
|
12
|
+
|
|
13
|
+
#if __has_include(<hermes/hermes.h>)
|
|
14
|
+
# define APPHELM_HERMES_SAMPLER 1
|
|
15
|
+
# import <hermes/hermes.h>
|
|
16
|
+
#else
|
|
17
|
+
# define APPHELM_HERMES_SAMPLER 0
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
#if APPHELM_HERMES_SAMPLER
|
|
22
|
+
std::mutex samplerMutex;
|
|
23
|
+
bool running = false;
|
|
24
|
+
|
|
25
|
+
void enableSampler()
|
|
26
|
+
{
|
|
27
|
+
# ifdef APPHELM_NEW_HERMES_RUNTIME
|
|
28
|
+
auto *api = facebook::jsi::castInterface<facebook::hermes::IHermesRootAPI>(facebook::hermes::makeHermesRootAPI());
|
|
29
|
+
if (api) api->enableSamplingProfiler();
|
|
30
|
+
# else
|
|
31
|
+
facebook::hermes::HermesRuntime::enableSamplingProfiler();
|
|
32
|
+
# endif
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
void disableSampler()
|
|
36
|
+
{
|
|
37
|
+
# ifdef APPHELM_NEW_HERMES_RUNTIME
|
|
38
|
+
auto *api = facebook::jsi::castInterface<facebook::hermes::IHermesRootAPI>(facebook::hermes::makeHermesRootAPI());
|
|
39
|
+
if (api) api->disableSamplingProfiler();
|
|
40
|
+
# else
|
|
41
|
+
facebook::hermes::HermesRuntime::disableSamplingProfiler();
|
|
42
|
+
# endif
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
std::string dumpTrace()
|
|
46
|
+
{
|
|
47
|
+
std::stringstream stream;
|
|
48
|
+
# ifdef APPHELM_NEW_HERMES_RUNTIME
|
|
49
|
+
auto *api = facebook::jsi::castInterface<facebook::hermes::IHermesRootAPI>(facebook::hermes::makeHermesRootAPI());
|
|
50
|
+
if (api) api->dumpSampledTraceToStream(stream);
|
|
51
|
+
# else
|
|
52
|
+
facebook::hermes::HermesRuntime::dumpSampledTraceToStream(stream);
|
|
53
|
+
# endif
|
|
54
|
+
return stream.str();
|
|
55
|
+
}
|
|
56
|
+
#endif
|
|
57
|
+
} // namespace
|
|
58
|
+
|
|
59
|
+
@interface ApphelmHermesProfiler : NSObject <RCTBridgeModule>
|
|
60
|
+
@end
|
|
61
|
+
|
|
62
|
+
@implementation ApphelmHermesProfiler
|
|
63
|
+
|
|
64
|
+
RCT_EXPORT_MODULE(ApphelmHermesProfiler)
|
|
65
|
+
|
|
66
|
+
+ (BOOL)requiresMainQueueSetup
|
|
67
|
+
{
|
|
68
|
+
return NO;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
RCT_EXPORT_METHOD(start : (RCTPromiseResolveBlock)resolve reject : (__unused RCTPromiseRejectBlock)reject)
|
|
72
|
+
{
|
|
73
|
+
#if APPHELM_HERMES_SAMPLER
|
|
74
|
+
std::lock_guard<std::mutex> lock(samplerMutex);
|
|
75
|
+
try {
|
|
76
|
+
// Hermes keeps no state to ask about: clear a profile a previous run left enabled.
|
|
77
|
+
disableSampler();
|
|
78
|
+
enableSampler();
|
|
79
|
+
running = true;
|
|
80
|
+
resolve(@YES);
|
|
81
|
+
} catch (...) {
|
|
82
|
+
running = false;
|
|
83
|
+
resolve(@NO);
|
|
84
|
+
}
|
|
85
|
+
#else
|
|
86
|
+
resolve(@NO);
|
|
87
|
+
#endif
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
RCT_EXPORT_METHOD(stop : (RCTPromiseResolveBlock)resolve reject : (__unused RCTPromiseRejectBlock)reject)
|
|
91
|
+
{
|
|
92
|
+
#if APPHELM_HERMES_SAMPLER
|
|
93
|
+
std::lock_guard<std::mutex> lock(samplerMutex);
|
|
94
|
+
if (!running) {
|
|
95
|
+
resolve([NSNull null]);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
running = false;
|
|
99
|
+
try {
|
|
100
|
+
disableSampler();
|
|
101
|
+
std::string trace = dumpTrace();
|
|
102
|
+
NSString *text = trace.empty() ? nil : [[NSString alloc] initWithBytes:trace.data() length:trace.size() encoding:NSUTF8StringEncoding];
|
|
103
|
+
resolve(text ?: (id)[NSNull null]);
|
|
104
|
+
} catch (...) {
|
|
105
|
+
resolve([NSNull null]);
|
|
106
|
+
}
|
|
107
|
+
#else
|
|
108
|
+
resolve([NSNull null]);
|
|
109
|
+
#endif
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The sampler must not outlive the React instance (a reload, the app closing).
|
|
113
|
+
- (void)invalidate
|
|
114
|
+
{
|
|
115
|
+
#if APPHELM_HERMES_SAMPLER
|
|
116
|
+
std::lock_guard<std::mutex> lock(samplerMutex);
|
|
117
|
+
if (running) {
|
|
118
|
+
running = false;
|
|
119
|
+
try {
|
|
120
|
+
disableSampler();
|
|
121
|
+
} catch (...) {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
#endif
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sansavision/apphelm-react-native-profiler",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.8.1",
|
|
4
|
+
"description": "The native half of AppHelm's React Native profiling: starts and stops Hermes's sampling profiler (Android and iOS) for @sansavision/apphelm/react-native-profiling.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./index.js",
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": { "types": "./index.d.ts", "import": "./index.js" }
|
|
11
|
+
},
|
|
12
|
+
"files": ["index.js", "index.d.ts", "ApphelmReactNativeProfiler.podspec", "ios", "android/build.gradle", "android/src", "README.md", "CHANGELOG.md", "LICENSE"],
|
|
13
|
+
"peerDependencies": { "react-native": ">=0.71" },
|
|
14
|
+
"peerDependenciesMeta": { "react-native": { "optional": true } },
|
|
15
|
+
"publishConfig": { "access": "public", "provenance": false }
|
|
16
|
+
}
|