@scalebun/react-native 1.2.0 → 1.2.2-beta.0
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/android/src/main/java/com/scalebun/core/performance/AppStartCollector.kt +61 -13
- package/android/src/main/java/com/scalebun/rn/performance/PerformanceModule.kt +11 -0
- package/dist/scalebun.full.js +142 -21
- package/dist/scalebun.full.js.map +1 -1
- package/dist/scalebun.slim.js +142 -21
- package/dist/scalebun.slim.js.map +1 -1
- package/lib/commonjs/features/performance/PerformanceFeature.js +121 -23
- package/lib/commonjs/features/performance/PerformanceFeature.js.map +1 -1
- package/lib/commonjs/features/session/BackendSessionAdapter.js +39 -4
- package/lib/commonjs/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/module/features/performance/PerformanceFeature.js +122 -23
- package/lib/module/features/performance/PerformanceFeature.js.map +1 -1
- package/lib/module/features/session/BackendSessionAdapter.js +39 -4
- package/lib/module/features/session/BackendSessionAdapter.js.map +1 -1
- package/lib/typescript/features/performance/PerformanceFeature.d.ts +31 -0
- package/lib/typescript/features/performance/PerformanceFeature.d.ts.map +1 -1
- package/lib/typescript/features/session/BackendSessionAdapter.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/features/performance/PerformanceFeature.ts +130 -20
- package/src/features/session/BackendSessionAdapter.ts +30 -5
|
@@ -6,6 +6,7 @@ import android.os.Build
|
|
|
6
6
|
import android.os.Bundle
|
|
7
7
|
import android.os.Process
|
|
8
8
|
import android.os.SystemClock
|
|
9
|
+
import android.util.Log
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Captures app start timing using Android-native hooks.
|
|
@@ -23,10 +24,24 @@ import android.os.SystemClock
|
|
|
23
24
|
*/
|
|
24
25
|
class AppStartCollector(private val app: Application) {
|
|
25
26
|
|
|
27
|
+
companion object {
|
|
28
|
+
private const val TAG = "ScaleBunPerformance"
|
|
29
|
+
/** Ignore an inferred cold-start proxy longer than this (stale/warm process). */
|
|
30
|
+
private const val MAX_INFERRED_DURATION_MS = 60_000L
|
|
31
|
+
}
|
|
32
|
+
|
|
26
33
|
data class AppStartInfo(
|
|
27
34
|
val launchType: String, // "cold" | "warm"
|
|
28
35
|
val durationMs: Long,
|
|
29
|
-
val breakdown: Breakdown
|
|
36
|
+
val breakdown: Breakdown?,
|
|
37
|
+
/**
|
|
38
|
+
* true when the value was NOT captured from Activity lifecycle callbacks
|
|
39
|
+
* but inferred from process-start → query time. Happens on the common RN
|
|
40
|
+
* path where the SDK's native init runs (JS-driven) only after the launch
|
|
41
|
+
* activity has already resumed, so onActivityResumed is never observed.
|
|
42
|
+
* Without this fallback getInfo() returns null and no app_launch is emitted.
|
|
43
|
+
*/
|
|
44
|
+
val inferred: Boolean = false
|
|
30
45
|
)
|
|
31
46
|
|
|
32
47
|
data class Breakdown(
|
|
@@ -86,9 +101,11 @@ class AppStartCollector(private val app: Application) {
|
|
|
86
101
|
processStartMs = null, // Already at process start
|
|
87
102
|
activityCreateMs = createDuration,
|
|
88
103
|
firstFrameMs = resumeDuration
|
|
89
|
-
)
|
|
104
|
+
),
|
|
105
|
+
inferred = false
|
|
90
106
|
)
|
|
91
107
|
|
|
108
|
+
Log.i(TAG, "app start captured from lifecycle: durationMs=$totalDuration")
|
|
92
109
|
callback?.invoke(info)
|
|
93
110
|
stop()
|
|
94
111
|
}
|
|
@@ -113,20 +130,51 @@ class AppStartCollector(private val app: Application) {
|
|
|
113
130
|
/** Check if app start has been captured */
|
|
114
131
|
fun isCaptured(): Boolean = captured
|
|
115
132
|
|
|
116
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Get app start info for on-demand queries.
|
|
135
|
+
*
|
|
136
|
+
* Prefers the precise value captured from Activity lifecycle callbacks. When
|
|
137
|
+
* capture never happened — the common RN case, because native init is driven
|
|
138
|
+
* from JS and runs only AFTER the launch activity resumed — falls back to an
|
|
139
|
+
* inferred duration from process start to now. Returning null here (the old
|
|
140
|
+
* behaviour) is exactly what caused App start = 0: AppLaunchCollector.collect()
|
|
141
|
+
* bails on a null/invalid duration, so no app_launch event was ever emitted.
|
|
142
|
+
*/
|
|
117
143
|
fun getInfo(): AppStartInfo? {
|
|
118
|
-
if (
|
|
119
|
-
|
|
144
|
+
if (captured) {
|
|
145
|
+
val totalDuration = activityResumeUptimeMs - processStartUptimeMs
|
|
146
|
+
return AppStartInfo(
|
|
147
|
+
launchType = "cold",
|
|
148
|
+
durationMs = totalDuration,
|
|
149
|
+
breakdown = Breakdown(
|
|
150
|
+
processStartMs = null,
|
|
151
|
+
activityCreateMs = if (activityCreateUptimeMs > 0)
|
|
152
|
+
activityCreateUptimeMs - processStartUptimeMs else null,
|
|
153
|
+
firstFrameMs = if (activityCreateUptimeMs > 0)
|
|
154
|
+
activityResumeUptimeMs - activityCreateUptimeMs else null
|
|
155
|
+
),
|
|
156
|
+
inferred = false
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Fallback: infer from process start → now. Guards against a bad clock
|
|
161
|
+
// (non-positive) and against a long-lived process where this proxy would
|
|
162
|
+
// be meaningless (e.g. queried minutes after a warm start).
|
|
163
|
+
if (processStartUptimeMs <= 0) {
|
|
164
|
+
Log.w(TAG, "app start not captured and no process-start clock — returning null")
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
val inferredDuration = SystemClock.uptimeMillis() - processStartUptimeMs
|
|
168
|
+
if (inferredDuration <= 0 || inferredDuration > MAX_INFERRED_DURATION_MS) {
|
|
169
|
+
Log.w(TAG, "app start not captured; inferred duration out of range ($inferredDuration ms) — returning null")
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
Log.i(TAG, "app start not captured from lifecycle; inferred durationMs=$inferredDuration")
|
|
120
173
|
return AppStartInfo(
|
|
121
174
|
launchType = "cold",
|
|
122
|
-
durationMs =
|
|
123
|
-
breakdown =
|
|
124
|
-
|
|
125
|
-
activityCreateMs = if (activityCreateUptimeMs > 0)
|
|
126
|
-
activityCreateUptimeMs - processStartUptimeMs else null,
|
|
127
|
-
firstFrameMs = if (activityCreateUptimeMs > 0)
|
|
128
|
-
activityResumeUptimeMs - activityCreateUptimeMs else null
|
|
129
|
-
)
|
|
175
|
+
durationMs = inferredDuration,
|
|
176
|
+
breakdown = null,
|
|
177
|
+
inferred = true
|
|
130
178
|
)
|
|
131
179
|
}
|
|
132
180
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.scalebun.rn.performance
|
|
2
2
|
|
|
3
3
|
import android.app.Application
|
|
4
|
+
import android.util.Log
|
|
4
5
|
import com.facebook.react.bridge.*
|
|
5
6
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
6
7
|
import com.scalebun.core.performance.AppStartCollector
|
|
@@ -33,6 +34,7 @@ class PerformanceModule(reactContext: ReactApplicationContext) : PerformanceSpec
|
|
|
33
34
|
|
|
34
35
|
companion object {
|
|
35
36
|
const val NAME = "ScaleBunPerformance"
|
|
37
|
+
private const val TAG = "ScaleBunPerformance"
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
override fun getName(): String = NAME
|
|
@@ -65,6 +67,7 @@ class PerformanceModule(reactContext: ReactApplicationContext) : PerformanceSpec
|
|
|
65
67
|
putDouble("maxFrameTimeMs", snapshot.maxFrameTimeMs)
|
|
66
68
|
putDouble("windowMs", snapshot.windowMs.toDouble())
|
|
67
69
|
}
|
|
70
|
+
Log.i(TAG, "emit FrameMetrics: totalFrames=${snapshot.totalFrames} dropped=${snapshot.droppedFrames}")
|
|
68
71
|
emit("ScaleBunPerformance_FrameMetrics", params)
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -73,6 +76,7 @@ class PerformanceModule(reactContext: ReactApplicationContext) : PerformanceSpec
|
|
|
73
76
|
putDouble("durationMs", info.durationMs.toDouble())
|
|
74
77
|
putDouble("timestamp", info.timestamp.toDouble())
|
|
75
78
|
}
|
|
79
|
+
Log.i(TAG, "emit UiHang: durationMs=${info.durationMs}")
|
|
76
80
|
emit("ScaleBunPerformance_UiHang", params)
|
|
77
81
|
}
|
|
78
82
|
}
|
|
@@ -83,12 +87,14 @@ class PerformanceModule(reactContext: ReactApplicationContext) : PerformanceSpec
|
|
|
83
87
|
override fun initialize(promise: Promise) {
|
|
84
88
|
try {
|
|
85
89
|
if (core?.isInitialized == true) {
|
|
90
|
+
Log.i(TAG, "initialize() — already initialized, no-op")
|
|
86
91
|
promise.resolve(true)
|
|
87
92
|
return
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
val app = reactApplicationContext.applicationContext as? Application
|
|
91
96
|
if (app == null) {
|
|
97
|
+
Log.w(TAG, "initialize() — Application context not available")
|
|
92
98
|
promise.reject("PERF_INIT_ERROR", "Application context not available")
|
|
93
99
|
return
|
|
94
100
|
}
|
|
@@ -98,8 +104,10 @@ class PerformanceModule(reactContext: ReactApplicationContext) : PerformanceSpec
|
|
|
98
104
|
performanceCore.initialize()
|
|
99
105
|
core = performanceCore
|
|
100
106
|
|
|
107
|
+
Log.i(TAG, "initialize() — native performance core started")
|
|
101
108
|
promise.resolve(true)
|
|
102
109
|
} catch (e: Exception) {
|
|
110
|
+
Log.w(TAG, "initialize() — failed: ${e.message}")
|
|
103
111
|
promise.reject("PERF_INIT_ERROR", e.message, e)
|
|
104
112
|
}
|
|
105
113
|
}
|
|
@@ -111,9 +119,11 @@ class PerformanceModule(reactContext: ReactApplicationContext) : PerformanceSpec
|
|
|
111
119
|
try {
|
|
112
120
|
val info = core?.getAppStartInfo()
|
|
113
121
|
if (info != null) {
|
|
122
|
+
Log.i(TAG, "getAppStartInfo() — launchType=${info.launchType} durationMs=${info.durationMs} inferred=${info.inferred}")
|
|
114
123
|
val result = Arguments.createMap().apply {
|
|
115
124
|
putString("launchType", info.launchType)
|
|
116
125
|
putDouble("durationMs", info.durationMs.toDouble())
|
|
126
|
+
putBoolean("inferred", info.inferred)
|
|
117
127
|
info.breakdown?.let { breakdown ->
|
|
118
128
|
val breakdownMap = Arguments.createMap().apply {
|
|
119
129
|
breakdown.processStartMs?.let { putDouble("processStartMs", it.toDouble()) }
|
|
@@ -125,6 +135,7 @@ class PerformanceModule(reactContext: ReactApplicationContext) : PerformanceSpec
|
|
|
125
135
|
}
|
|
126
136
|
promise.resolve(result)
|
|
127
137
|
} else {
|
|
138
|
+
Log.w(TAG, "getAppStartInfo() — no info available (core=${if (core == null) "null" else "present"})")
|
|
128
139
|
promise.resolve(null)
|
|
129
140
|
}
|
|
130
141
|
} catch (e: Exception) {
|
package/dist/scalebun.full.js
CHANGED
|
@@ -8369,7 +8369,31 @@ var init_PerformanceFeature = __esm({
|
|
|
8369
8369
|
static CAPTURE_MAX_RETRIES = 3;
|
|
8370
8370
|
/** Delay schedule for each attempt (ms) — escalating to let views settle */
|
|
8371
8371
|
static CAPTURE_DELAYS = [300, 600, 1200];
|
|
8372
|
-
//
|
|
8372
|
+
// ─── Early-metric buffering (backend) ───────────────────────────────
|
|
8373
|
+
//
|
|
8374
|
+
// Performance metrics reach the cloud via the SessionManager's backend
|
|
8375
|
+
// transport. But the first metrics — most importantly app_launch — fire
|
|
8376
|
+
// during PerformanceFeature.initialize() (appLaunchCollector.collect()),
|
|
8377
|
+
// which runs from featureRegistry.initializeAll() BEFORE the bootstrap
|
|
8378
|
+
// attaches the backend transport (SDKBootstrapper). In that window
|
|
8379
|
+
// getBackendTransport() is null, so without buffering the metric is sent
|
|
8380
|
+
// only to the desktop debugger and silently dropped from the cloud
|
|
8381
|
+
// ("App start = 0" in Insights). We buffer such metrics locally and drain
|
|
8382
|
+
// them the moment the transport appears (via subsequent events or a
|
|
8383
|
+
// bounded retry). The backend adapter itself buffers again until the
|
|
8384
|
+
// session row is ready, so ordering across the session boundary is safe.
|
|
8385
|
+
/** Metrics captured before the backend transport was available. */
|
|
8386
|
+
_pendingBackendMetrics = [];
|
|
8387
|
+
/** Retry timer that drains the buffer once the transport attaches. */
|
|
8388
|
+
_backendDrainTimer = null;
|
|
8389
|
+
/** Number of drain attempts made for the current buffered batch. */
|
|
8390
|
+
_backendDrainAttempts = 0;
|
|
8391
|
+
/** Hard cap on buffered metrics — bounds memory if a transport never attaches (non-SaaS). */
|
|
8392
|
+
static BACKEND_BUFFER_MAX = 64;
|
|
8393
|
+
/** Max drain retries before giving up (non-SaaS mode never attaches a transport). */
|
|
8394
|
+
static BACKEND_DRAIN_MAX_ATTEMPTS = 6;
|
|
8395
|
+
/** Escalating retry delays (ms); last value repeats. Covers the startup attach race. */
|
|
8396
|
+
static BACKEND_DRAIN_DELAYS = [250, 500, 1e3, 2e3, 4e3];
|
|
8373
8397
|
constructor(config) {
|
|
8374
8398
|
this.config = mergePerformanceConfig(config);
|
|
8375
8399
|
this.transport = new PerformanceTransport(this.config);
|
|
@@ -8504,6 +8528,9 @@ var init_PerformanceFeature = __esm({
|
|
|
8504
8528
|
clearTimeout(this._captureTimer);
|
|
8505
8529
|
this._captureTimer = null;
|
|
8506
8530
|
}
|
|
8531
|
+
this._clearBackendDrainTimer();
|
|
8532
|
+
this._backendDrainAttempts = 0;
|
|
8533
|
+
this._pendingBackendMetrics = [];
|
|
8507
8534
|
}
|
|
8508
8535
|
get isActive() {
|
|
8509
8536
|
return this.active;
|
|
@@ -8687,24 +8714,83 @@ var init_PerformanceFeature = __esm({
|
|
|
8687
8714
|
// ─── Internal ───────────────────────────────────────────────────────
|
|
8688
8715
|
_handleEvent(event) {
|
|
8689
8716
|
this.transport.sendEvent(event);
|
|
8717
|
+
this._forwardMetricToBackend(this._toBackendMetric(event));
|
|
8718
|
+
}
|
|
8719
|
+
/** Map a raw perf event onto the backend metric envelope. */
|
|
8720
|
+
_toBackendMetric(event) {
|
|
8721
|
+
const e = event;
|
|
8722
|
+
const metadata = {
|
|
8723
|
+
id: event.id
|
|
8724
|
+
};
|
|
8725
|
+
if (event.type === "app_launch") {
|
|
8726
|
+
if (e.launchType) metadata.launchType = e.launchType;
|
|
8727
|
+
if (e.breakdown) metadata.breakdown = e.breakdown;
|
|
8728
|
+
if (e.phaseLabels) metadata.phaseLabels = e.phaseLabels;
|
|
8729
|
+
}
|
|
8730
|
+
return {
|
|
8731
|
+
type: event.type,
|
|
8732
|
+
screenName: e.screenName ?? e.context?.screenKey,
|
|
8733
|
+
duration: e.durationMs,
|
|
8734
|
+
value: e.estimatedFps ?? e.value,
|
|
8735
|
+
metadata
|
|
8736
|
+
};
|
|
8737
|
+
}
|
|
8738
|
+
/**
|
|
8739
|
+
* Hand a metric to the backend transport, or buffer it until one attaches.
|
|
8740
|
+
* Every call first drains anything buffered, so a late-arriving transport
|
|
8741
|
+
* flushes the backlog in the order the metrics were produced.
|
|
8742
|
+
*/
|
|
8743
|
+
_forwardMetricToBackend(metric) {
|
|
8690
8744
|
const backendTransport = SessionManager.getExistingInstance()?.getBackendTransport();
|
|
8691
8745
|
if (backendTransport) {
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8746
|
+
this._drainPendingBackendMetrics(backendTransport);
|
|
8747
|
+
backendTransport.queuePerformanceMetric(metric);
|
|
8748
|
+
return;
|
|
8749
|
+
}
|
|
8750
|
+
this._pendingBackendMetrics.push(metric);
|
|
8751
|
+
if (this._pendingBackendMetrics.length > _PerformanceFeature.BACKEND_BUFFER_MAX) {
|
|
8752
|
+
this._pendingBackendMetrics.shift();
|
|
8753
|
+
}
|
|
8754
|
+
this._scheduleBackendDrain();
|
|
8755
|
+
}
|
|
8756
|
+
/** Flush all buffered metrics into the transport, preserving order. */
|
|
8757
|
+
_drainPendingBackendMetrics(backendTransport) {
|
|
8758
|
+
if (this._pendingBackendMetrics.length === 0) return;
|
|
8759
|
+
const buffered = this._pendingBackendMetrics;
|
|
8760
|
+
this._pendingBackendMetrics = [];
|
|
8761
|
+
for (const metric of buffered) {
|
|
8762
|
+
backendTransport.queuePerformanceMetric(metric);
|
|
8763
|
+
}
|
|
8764
|
+
this._clearBackendDrainTimer();
|
|
8765
|
+
this._backendDrainAttempts = 0;
|
|
8766
|
+
}
|
|
8767
|
+
/**
|
|
8768
|
+
* Poll for the backend transport a bounded number of times so the sole
|
|
8769
|
+
* early metric (app_launch) still reaches the cloud even when no later
|
|
8770
|
+
* event arrives to trigger a drain. Gives up after BACKEND_DRAIN_MAX_ATTEMPTS
|
|
8771
|
+
* (non-SaaS mode never attaches a transport — the desktop path already has
|
|
8772
|
+
* the data), leaving the bounded buffer to be GC'd on teardown.
|
|
8773
|
+
*/
|
|
8774
|
+
_scheduleBackendDrain() {
|
|
8775
|
+
if (this._backendDrainTimer) return;
|
|
8776
|
+
if (this._backendDrainAttempts >= _PerformanceFeature.BACKEND_DRAIN_MAX_ATTEMPTS) return;
|
|
8777
|
+
const delays = _PerformanceFeature.BACKEND_DRAIN_DELAYS;
|
|
8778
|
+
const delay = delays[Math.min(this._backendDrainAttempts, delays.length - 1)];
|
|
8779
|
+
this._backendDrainAttempts++;
|
|
8780
|
+
this._backendDrainTimer = setTimeout(() => {
|
|
8781
|
+
this._backendDrainTimer = null;
|
|
8782
|
+
const backendTransport = SessionManager.getExistingInstance()?.getBackendTransport();
|
|
8783
|
+
if (backendTransport) {
|
|
8784
|
+
this._drainPendingBackendMetrics(backendTransport);
|
|
8785
|
+
} else if (this._pendingBackendMetrics.length > 0) {
|
|
8786
|
+
this._scheduleBackendDrain();
|
|
8787
|
+
}
|
|
8788
|
+
}, delay);
|
|
8789
|
+
}
|
|
8790
|
+
_clearBackendDrainTimer() {
|
|
8791
|
+
if (this._backendDrainTimer) {
|
|
8792
|
+
clearTimeout(this._backendDrainTimer);
|
|
8793
|
+
this._backendDrainTimer = null;
|
|
8708
8794
|
}
|
|
8709
8795
|
}
|
|
8710
8796
|
};
|
|
@@ -15663,12 +15749,23 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
|
|
|
15663
15749
|
this._triggerNativeFlush();
|
|
15664
15750
|
}
|
|
15665
15751
|
queuePerformanceMetric(data) {
|
|
15666
|
-
if (this.destroyed || !this.config.clientKey)
|
|
15752
|
+
if (this.destroyed || !this.config.clientKey) {
|
|
15753
|
+
logger.warn("[perf.queue] dropped \u2014 no clientKey or adapter destroyed", {
|
|
15754
|
+
type: data.type,
|
|
15755
|
+
destroyed: this.destroyed,
|
|
15756
|
+
hasClientKey: !!this.config.clientKey
|
|
15757
|
+
});
|
|
15758
|
+
return;
|
|
15759
|
+
}
|
|
15667
15760
|
this.pendingPerformanceMetrics.push({
|
|
15668
15761
|
...data,
|
|
15669
15762
|
clientId: this._clientId(),
|
|
15670
15763
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
15671
15764
|
});
|
|
15765
|
+
logger.debug("[perf.queue] enqueued performance metric", {
|
|
15766
|
+
type: data.type,
|
|
15767
|
+
pending: this.pendingPerformanceMetrics.length
|
|
15768
|
+
});
|
|
15672
15769
|
if (this.pendingPerformanceMetrics.length >= 20) {
|
|
15673
15770
|
this._flushPerformance().catch(() => {
|
|
15674
15771
|
});
|
|
@@ -16235,19 +16332,43 @@ var BackendSessionAdapter = class _BackendSessionAdapter {
|
|
|
16235
16332
|
}
|
|
16236
16333
|
}
|
|
16237
16334
|
async _flushPerformance() {
|
|
16238
|
-
if (this.pendingPerformanceMetrics.length === 0
|
|
16335
|
+
if (this.pendingPerformanceMetrics.length === 0) return;
|
|
16336
|
+
if (!this.currentSessionId || !this.config.clientKey) {
|
|
16337
|
+
logger.debug("[perf.flush] waiting \u2014 not deliverable yet", {
|
|
16338
|
+
pending: this.pendingPerformanceMetrics.length,
|
|
16339
|
+
hasSession: !!this.currentSessionId,
|
|
16340
|
+
hasClientKey: !!this.config.clientKey
|
|
16341
|
+
});
|
|
16342
|
+
return;
|
|
16343
|
+
}
|
|
16239
16344
|
const batch = this.pendingPerformanceMetrics.splice(0, 50);
|
|
16240
16345
|
try {
|
|
16241
|
-
if (await this._enqueueNative("performance", batch))
|
|
16346
|
+
if (await this._enqueueNative("performance", batch)) {
|
|
16347
|
+
logger.debug("[perf.flush] handed to native outbox", {
|
|
16348
|
+
count: batch.length
|
|
16349
|
+
});
|
|
16350
|
+
return;
|
|
16351
|
+
}
|
|
16242
16352
|
if (!this._sessionRowReady()) {
|
|
16353
|
+
logger.debug("[perf.flush] session row not ready \u2014 re-buffering", {
|
|
16354
|
+
count: batch.length
|
|
16355
|
+
});
|
|
16243
16356
|
this.pendingPerformanceMetrics.unshift(...batch);
|
|
16244
16357
|
return;
|
|
16245
16358
|
}
|
|
16246
16359
|
await this._post(ENDPOINTS.INGESTION_PERFORMANCE(this.currentSessionId), {
|
|
16247
16360
|
metrics: batch
|
|
16248
16361
|
});
|
|
16362
|
+
logger.info("[perf.flush] POST performance metrics ok", {
|
|
16363
|
+
count: batch.length
|
|
16364
|
+
});
|
|
16249
16365
|
} catch (err) {
|
|
16250
|
-
|
|
16366
|
+
const permanent = _BackendSessionAdapter._isPermanentHttpError(err);
|
|
16367
|
+
logger.warn("[perf.flush] POST performance metrics failed", {
|
|
16368
|
+
count: batch.length,
|
|
16369
|
+
permanent
|
|
16370
|
+
});
|
|
16371
|
+
if (!permanent) this.pendingPerformanceMetrics.unshift(...batch);
|
|
16251
16372
|
}
|
|
16252
16373
|
}
|
|
16253
16374
|
async _flushLogs() {
|