@keyframerdev/vitals 0.1.1 → 0.1.3
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/dist/index.js +188 -30
- package/dist/types/controller.d.ts +38 -3
- package/dist/types/jsLoop.d.ts +16 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -618,9 +618,13 @@ function slimArg(a, idOf) {
|
|
|
618
618
|
return null;
|
|
619
619
|
}
|
|
620
620
|
|
|
621
|
+
// ../protocol/src/vitals-validate.ts
|
|
622
|
+
var VITALS_MAX_HZ = 240;
|
|
623
|
+
var MAX_FRAME_MS = 10 * 60 * 1000;
|
|
624
|
+
|
|
621
625
|
// src/version.ts
|
|
622
626
|
var SDK_NAME = "@keyframerdev/vitals";
|
|
623
|
-
var SDK_VERSION = "0.1.
|
|
627
|
+
var SDK_VERSION = "0.1.3";
|
|
624
628
|
|
|
625
629
|
// src/controller.ts
|
|
626
630
|
var STORAGE_KEYS = {
|
|
@@ -707,6 +711,11 @@ class VitalsController {
|
|
|
707
711
|
meters = null;
|
|
708
712
|
screen = "app";
|
|
709
713
|
reduceMotion = false;
|
|
714
|
+
traced = {};
|
|
715
|
+
tracedAnims = 0;
|
|
716
|
+
metering = false;
|
|
717
|
+
drawing = true;
|
|
718
|
+
toldDevelopment = null;
|
|
710
719
|
constructor(env) {
|
|
711
720
|
this.env = env;
|
|
712
721
|
}
|
|
@@ -736,7 +745,8 @@ class VitalsController {
|
|
|
736
745
|
this.enabled = false;
|
|
737
746
|
return;
|
|
738
747
|
}
|
|
739
|
-
|
|
748
|
+
const dev = opts.environment === "development";
|
|
749
|
+
this.config = dev ? null : cached?.config ?? null;
|
|
740
750
|
if (!this.allowed()) {
|
|
741
751
|
this.enabled = false;
|
|
742
752
|
return;
|
|
@@ -753,7 +763,7 @@ class VitalsController {
|
|
|
753
763
|
return;
|
|
754
764
|
this.retryStep = 0;
|
|
755
765
|
this.scheduleRetry();
|
|
756
|
-
const fresh = cached && this.env.wall() - cached.at < Math.max(60, cached.config.ttlSec) * 1000;
|
|
766
|
+
const fresh = !dev && cached && this.env.wall() - cached.at < Math.max(60, cached.config.ttlSec) * 1000;
|
|
757
767
|
if (!fresh)
|
|
758
768
|
this.configTimer = this.env.setTimeout(() => void this.fetchConfig(run), START_DELAY_MS);
|
|
759
769
|
await this.beginSession(run);
|
|
@@ -803,6 +813,7 @@ class VitalsController {
|
|
|
803
813
|
}
|
|
804
814
|
}
|
|
805
815
|
this.sampled = sampled;
|
|
816
|
+
this.debug(`session ${this.nonce.slice(0, 6)}: ${sampled ? "sampled" : "not sampled"} (rate ${this.sampleRate}, environment ${env}` + `${devOk ? "" : ", the project does not collect development builds"})`);
|
|
806
817
|
if (!sampled)
|
|
807
818
|
return;
|
|
808
819
|
this.collector.clear();
|
|
@@ -815,6 +826,7 @@ class VitalsController {
|
|
|
815
826
|
this.meters.install();
|
|
816
827
|
}
|
|
817
828
|
endSampling() {
|
|
829
|
+
this.metering = false;
|
|
818
830
|
if (this.sampled) {
|
|
819
831
|
this.meters?.stop();
|
|
820
832
|
this.meters?.uninstall();
|
|
@@ -829,10 +841,17 @@ class VitalsController {
|
|
|
829
841
|
this.checkpointTimer = null;
|
|
830
842
|
}
|
|
831
843
|
}
|
|
832
|
-
appState(active) {
|
|
844
|
+
appState(active, drawing = active) {
|
|
833
845
|
if (!this.enabled)
|
|
834
846
|
return;
|
|
835
847
|
const now = this.env.now();
|
|
848
|
+
if (this.drawing !== drawing) {
|
|
849
|
+
this.drawing = drawing;
|
|
850
|
+
if (!drawing && this.sampled && this.foreground) {
|
|
851
|
+
this.pauseMeters(now);
|
|
852
|
+
this.debug("app inactive: meters paused until it is active again");
|
|
853
|
+
}
|
|
854
|
+
}
|
|
836
855
|
if (!active && this.foreground) {
|
|
837
856
|
this.foreground = false;
|
|
838
857
|
this.backgroundAt = now;
|
|
@@ -841,8 +860,7 @@ class VitalsController {
|
|
|
841
860
|
this.retryTimer = null;
|
|
842
861
|
}
|
|
843
862
|
if (this.sampled) {
|
|
844
|
-
this.
|
|
845
|
-
this.meters?.stop();
|
|
863
|
+
this.pauseMeters(now);
|
|
846
864
|
this.collector.foreground(false, now);
|
|
847
865
|
this.closeWindow("background");
|
|
848
866
|
}
|
|
@@ -882,6 +900,8 @@ class VitalsController {
|
|
|
882
900
|
if (isRef2(a))
|
|
883
901
|
this.collector.consume(a.$anim);
|
|
884
902
|
this.collector.animStart(site, t, ms, thread, eid);
|
|
903
|
+
if (this.opts?.debug)
|
|
904
|
+
this.tracedAnims++;
|
|
885
905
|
this.motion(t + ms, t);
|
|
886
906
|
}
|
|
887
907
|
layout(site, kind, builder, t = this.env.now()) {
|
|
@@ -905,17 +925,35 @@ class VitalsController {
|
|
|
905
925
|
if (!this.sampled || !this.foreground)
|
|
906
926
|
return;
|
|
907
927
|
this.collector.touch(down, t);
|
|
928
|
+
if (!this.drawing)
|
|
929
|
+
return;
|
|
908
930
|
if (this.gate.touch(down, t))
|
|
909
931
|
this.opened(t);
|
|
910
932
|
this.keepUi(t);
|
|
911
933
|
}
|
|
912
934
|
motion(end, now) {
|
|
935
|
+
if (!this.drawing)
|
|
936
|
+
return;
|
|
913
937
|
if (this.gate.note(end, now))
|
|
914
938
|
this.opened(now);
|
|
915
939
|
this.keepUi(now);
|
|
916
940
|
this.checkWindow(now);
|
|
917
941
|
}
|
|
942
|
+
pauseMeters(now) {
|
|
943
|
+
this.gate.reset();
|
|
944
|
+
this.meters?.stop();
|
|
945
|
+
this.collector.measure(false, now);
|
|
946
|
+
if (this.metering && this.opts?.debug) {
|
|
947
|
+
this.flushTrace();
|
|
948
|
+
this.debug("meters off: the app left the screen");
|
|
949
|
+
}
|
|
950
|
+
this.metering = false;
|
|
951
|
+
this.uiKnownUntil = -Infinity;
|
|
952
|
+
}
|
|
918
953
|
opened(now) {
|
|
954
|
+
if (!this.metering)
|
|
955
|
+
this.debug("meters on: something moves");
|
|
956
|
+
this.metering = true;
|
|
919
957
|
this.collector.measure(true, now);
|
|
920
958
|
this.meters?.start();
|
|
921
959
|
this.scheduleCheckpoint();
|
|
@@ -929,12 +967,20 @@ class VitalsController {
|
|
|
929
967
|
}
|
|
930
968
|
}
|
|
931
969
|
jsFrame(t, dt, budget) {
|
|
932
|
-
if (!this.sampled || !this.foreground)
|
|
970
|
+
if (!this.sampled || !this.foreground || !this.drawing)
|
|
933
971
|
return false;
|
|
934
|
-
if (dt > 0)
|
|
972
|
+
if (dt > 0) {
|
|
935
973
|
this.collector.frame("js", t, dt, budget);
|
|
974
|
+
if (this.opts?.debug)
|
|
975
|
+
this.traceFrame("js", dt, t);
|
|
976
|
+
}
|
|
936
977
|
if (!this.gate.open(t)) {
|
|
937
978
|
this.collector.measure(false, Math.min(t, this.gate.until));
|
|
979
|
+
if (this.metering && this.opts?.debug) {
|
|
980
|
+
this.flushTrace();
|
|
981
|
+
this.debug("meters off: the motion window closed");
|
|
982
|
+
}
|
|
983
|
+
this.metering = false;
|
|
938
984
|
return false;
|
|
939
985
|
}
|
|
940
986
|
this.keepUi(t);
|
|
@@ -946,6 +992,49 @@ class VitalsController {
|
|
|
946
992
|
return;
|
|
947
993
|
for (let i = 0;i + 2 < buf.length; i += 3)
|
|
948
994
|
this.collector.frame("ui", buf[i], buf[i + 1], buf[i + 2]);
|
|
995
|
+
if (this.opts?.debug)
|
|
996
|
+
for (let i = 0;i + 2 < buf.length; i += 3)
|
|
997
|
+
this.traceFrame("ui", buf[i + 1], buf[i]);
|
|
998
|
+
}
|
|
999
|
+
traceFrame(thread, dt, t) {
|
|
1000
|
+
const cur = this.traced[thread];
|
|
1001
|
+
if (cur && (t - cur.since >= 1000 || t - dt > cur.last + 100))
|
|
1002
|
+
this.flushTrace();
|
|
1003
|
+
const s = this.traced[thread] ??= { n: 0, sum: 0, min: Infinity, max: 0, since: t - dt, last: t };
|
|
1004
|
+
s.n++;
|
|
1005
|
+
s.sum += dt;
|
|
1006
|
+
if (dt < s.min)
|
|
1007
|
+
s.min = dt;
|
|
1008
|
+
if (dt > s.max)
|
|
1009
|
+
s.max = dt;
|
|
1010
|
+
s.since = Math.min(s.since, t - dt);
|
|
1011
|
+
s.last = Math.max(s.last, t);
|
|
1012
|
+
}
|
|
1013
|
+
flushTrace() {
|
|
1014
|
+
const all = Object.values(this.traced);
|
|
1015
|
+
if (!all.length)
|
|
1016
|
+
return;
|
|
1017
|
+
const ms = (v) => (Math.round(v * 10) / 10).toString();
|
|
1018
|
+
const part = (th) => {
|
|
1019
|
+
const s = this.traced[th];
|
|
1020
|
+
if (!s)
|
|
1021
|
+
return `${th}: no frames`;
|
|
1022
|
+
const span = Math.max(1, s.last - s.since);
|
|
1023
|
+
return `${th}: ${s.n} frames in ${Math.round(span)} ms, ${Math.round(s.n * 1000 / span)}/s (gap min ${ms(s.min)}, avg ${ms(s.sum / s.n)}, max ${ms(s.max)} ms)`;
|
|
1024
|
+
};
|
|
1025
|
+
this.debug(`on ${this.collector.currentScreen}, ${this.tracedAnims} animation starts. ${part("js")}; ${part("ui")}`);
|
|
1026
|
+
this.traced = {};
|
|
1027
|
+
this.tracedAnims = 0;
|
|
1028
|
+
}
|
|
1029
|
+
traceReport(report) {
|
|
1030
|
+
const frames = {};
|
|
1031
|
+
for (const s of report.screens)
|
|
1032
|
+
for (const [th, st] of Object.entries(s.threads))
|
|
1033
|
+
frames[th] = (frames[th] ?? 0) + (st?.frames ?? 0);
|
|
1034
|
+
const measured = report.session.measuredMs;
|
|
1035
|
+
const cap = Math.ceil(measured * VITALS_MAX_HZ / 1000);
|
|
1036
|
+
const over = Object.entries(frames).filter(([, n]) => n > cap).map(([th]) => th);
|
|
1037
|
+
this.debug(`report ${report.session.seq} (${report.session.ended}): measured ${measured} ms of ${report.session.foregroundMs} ms in the foreground, ` + `so at most about ${cap} frames a thread; js ${frames.js ?? 0}, ui ${frames.ui ?? 0}` + `${over.length ? ` (too many on ${over.join(" and ")}: the server will refuse it)` : ""}; ` + `${report.screens.length} screens, ${report.sites.length} animation sites`);
|
|
949
1038
|
}
|
|
950
1039
|
checkWindow(now) {
|
|
951
1040
|
if (this.collector.foregroundAt(now) >= VITALS_WINDOW_MS)
|
|
@@ -971,10 +1060,15 @@ class VitalsController {
|
|
|
971
1060
|
}
|
|
972
1061
|
const storage = this.opts?.storage;
|
|
973
1062
|
if (!report) {
|
|
1063
|
+
this.debug(`window closed (${ended}) with nothing to report`);
|
|
974
1064
|
await write(storage, STORAGE_KEYS.checkpoint, null);
|
|
975
1065
|
return;
|
|
976
1066
|
}
|
|
977
1067
|
this.seq++;
|
|
1068
|
+
if (this.opts?.debug) {
|
|
1069
|
+
this.flushTrace();
|
|
1070
|
+
this.traceReport(report);
|
|
1071
|
+
}
|
|
978
1072
|
await write(storage, STORAGE_KEYS.checkpoint, report);
|
|
979
1073
|
await this.send(JSON.stringify(report));
|
|
980
1074
|
await write(storage, STORAGE_KEYS.checkpoint, null);
|
|
@@ -1103,6 +1197,7 @@ class VitalsController {
|
|
|
1103
1197
|
body
|
|
1104
1198
|
});
|
|
1105
1199
|
if (res.status === 200) {
|
|
1200
|
+
this.debug("report accepted");
|
|
1106
1201
|
const json = await res.json().catch(() => null);
|
|
1107
1202
|
if (json?.config)
|
|
1108
1203
|
await this.applyConfig(json.config);
|
|
@@ -1131,8 +1226,10 @@ class VitalsController {
|
|
|
1131
1226
|
return;
|
|
1132
1227
|
if (res.status === 200) {
|
|
1133
1228
|
const config = await res.json();
|
|
1134
|
-
if (config && typeof config === "object" && typeof config.sampleRate === "number")
|
|
1229
|
+
if (config && typeof config === "object" && typeof config.sampleRate === "number") {
|
|
1230
|
+
this.debug(`config: sample rate ${config.sampleRate}, development builds ${config.collect?.development ? "collected" : "not collected"}`);
|
|
1135
1231
|
await this.applyConfig(config);
|
|
1232
|
+
}
|
|
1136
1233
|
} else if (res.status === 401 || res.status === 410)
|
|
1137
1234
|
await this.killSwitch();
|
|
1138
1235
|
} catch {}
|
|
@@ -1148,6 +1245,19 @@ class VitalsController {
|
|
|
1148
1245
|
}
|
|
1149
1246
|
if (!config.collect.reduceMotion)
|
|
1150
1247
|
this.setReduceMotion(false);
|
|
1248
|
+
if (this.opts?.environment === "development")
|
|
1249
|
+
this.developmentConfig(config.collect.development);
|
|
1250
|
+
}
|
|
1251
|
+
developmentConfig(kept) {
|
|
1252
|
+
if (this.toldDevelopment !== kept) {
|
|
1253
|
+
this.toldDevelopment = kept;
|
|
1254
|
+
if (kept)
|
|
1255
|
+
this.debug("this project keeps development builds: this build's reports show in the dashboard's Development view");
|
|
1256
|
+
else
|
|
1257
|
+
this.env.log?.("[keyframer vitals] This project does not keep reports from development builds, so this build sends nothing. " + `To test here, turn on "Development builds" in the project's setup on keyframer.dev, then reload. Release builds are not affected.`, "info");
|
|
1258
|
+
}
|
|
1259
|
+
if (!kept && this.sampled)
|
|
1260
|
+
this.endSampling();
|
|
1151
1261
|
}
|
|
1152
1262
|
async killSwitch() {
|
|
1153
1263
|
this.log("turned off on this device for 7 days by the project");
|
|
@@ -1183,6 +1293,10 @@ class VitalsController {
|
|
|
1183
1293
|
log(message) {
|
|
1184
1294
|
this.env.log?.(`[keyframer vitals] ${message}`);
|
|
1185
1295
|
}
|
|
1296
|
+
debug(message) {
|
|
1297
|
+
if (this.opts?.debug)
|
|
1298
|
+
this.env.log?.(`[keyframer vitals] ${message}`, "debug");
|
|
1299
|
+
}
|
|
1186
1300
|
}
|
|
1187
1301
|
|
|
1188
1302
|
// src/hook.ts
|
|
@@ -1265,6 +1379,49 @@ function tickFieldMeter(f, t) {
|
|
|
1265
1379
|
return f.buf.length >= 90;
|
|
1266
1380
|
}
|
|
1267
1381
|
|
|
1382
|
+
// src/jsLoop.ts
|
|
1383
|
+
var MIN_FRAME_GAP_MS = 1000 / 240;
|
|
1384
|
+
function createJsLoop({ raf, later, now, frame }) {
|
|
1385
|
+
let running = false;
|
|
1386
|
+
let gen = 0;
|
|
1387
|
+
let last = -1;
|
|
1388
|
+
const chain = (mine) => {
|
|
1389
|
+
const tick = () => {
|
|
1390
|
+
if (!running || mine !== gen)
|
|
1391
|
+
return;
|
|
1392
|
+
const t = now();
|
|
1393
|
+
if (last >= 0 && t - last < MIN_FRAME_GAP_MS) {
|
|
1394
|
+
later(tick);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
last = t;
|
|
1398
|
+
if (frame(t))
|
|
1399
|
+
raf(tick);
|
|
1400
|
+
else
|
|
1401
|
+
running = false;
|
|
1402
|
+
};
|
|
1403
|
+
return tick;
|
|
1404
|
+
};
|
|
1405
|
+
return {
|
|
1406
|
+
get running() {
|
|
1407
|
+
return running;
|
|
1408
|
+
},
|
|
1409
|
+
start() {
|
|
1410
|
+
if (running)
|
|
1411
|
+
return false;
|
|
1412
|
+
running = true;
|
|
1413
|
+
gen++;
|
|
1414
|
+
last = -1;
|
|
1415
|
+
raf(chain(gen));
|
|
1416
|
+
return true;
|
|
1417
|
+
},
|
|
1418
|
+
stop() {
|
|
1419
|
+
running = false;
|
|
1420
|
+
gen++;
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1268
1425
|
// src/native.ts
|
|
1269
1426
|
var perf = globalThis.performance;
|
|
1270
1427
|
var now = () => perf && typeof perf.now === "function" ? perf.now() : Date.now();
|
|
@@ -1289,7 +1446,7 @@ var env = {
|
|
|
1289
1446
|
setTimeout: (fn, ms) => setTimeout(fn, ms),
|
|
1290
1447
|
clearTimeout: (h) => clearTimeout(h),
|
|
1291
1448
|
platform: { os: Platform.OS === "ios" ? "ios" : "android", osMajor: osMajor() },
|
|
1292
|
-
log: (m) => console.warn(m)
|
|
1449
|
+
log: (m, level) => level === "debug" || level === "info" ? console.log(m) : console.warn(m)
|
|
1293
1450
|
};
|
|
1294
1451
|
var controller = new VitalsController(env);
|
|
1295
1452
|
var jsHook = createJsHook(controller, () => checkScreen());
|
|
@@ -1405,21 +1562,19 @@ function extendUi(ms) {
|
|
|
1405
1562
|
}, ms);
|
|
1406
1563
|
}
|
|
1407
1564
|
var jsMeter = createFieldMeter("js");
|
|
1408
|
-
var
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
jsRunning = false;
|
|
1422
|
-
}
|
|
1565
|
+
var jsLoop = createJsLoop({
|
|
1566
|
+
raf: (cb) => requestAnimationFrame(cb),
|
|
1567
|
+
later: (cb) => setTimeout(cb, 16),
|
|
1568
|
+
now,
|
|
1569
|
+
frame: (t) => {
|
|
1570
|
+
const last = jsMeter.m.last;
|
|
1571
|
+
const interval = jsMeter.m.interval;
|
|
1572
|
+
tickFrameMeter(jsMeter.m, t);
|
|
1573
|
+
jsMeter.m.stats.length = 0;
|
|
1574
|
+
jsMeter.m.drops.length = 0;
|
|
1575
|
+
return controller.jsFrame(t, last >= 0 ? t - last : 0, interval);
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1423
1578
|
var uiInstalled = false;
|
|
1424
1579
|
var meters = {
|
|
1425
1580
|
install() {
|
|
@@ -1438,10 +1593,9 @@ var meters = {
|
|
|
1438
1593
|
uiInstalled = false;
|
|
1439
1594
|
},
|
|
1440
1595
|
start() {
|
|
1441
|
-
if (!
|
|
1442
|
-
jsRunning = true;
|
|
1596
|
+
if (!jsLoop.running) {
|
|
1443
1597
|
jsMeter.m.last = -1;
|
|
1444
|
-
|
|
1598
|
+
jsLoop.start();
|
|
1445
1599
|
}
|
|
1446
1600
|
},
|
|
1447
1601
|
extendUi(ms) {
|
|
@@ -1449,7 +1603,7 @@ var meters = {
|
|
|
1449
1603
|
extendUi(ms);
|
|
1450
1604
|
},
|
|
1451
1605
|
stop() {
|
|
1452
|
-
|
|
1606
|
+
jsLoop.stop();
|
|
1453
1607
|
if (uiInstalled)
|
|
1454
1608
|
stopUi();
|
|
1455
1609
|
}
|
|
@@ -1496,7 +1650,11 @@ async function startVitals(opts) {
|
|
|
1496
1650
|
lastRoute = null;
|
|
1497
1651
|
manualScreen = false;
|
|
1498
1652
|
const environment = opts.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "development" : "production");
|
|
1499
|
-
const appState = AppState.addEventListener("change", (s) =>
|
|
1653
|
+
const appState = AppState.addEventListener("change", (s) => {
|
|
1654
|
+
if (s !== "active")
|
|
1655
|
+
touchDown = false;
|
|
1656
|
+
controller.appState(s !== "background", s === "active");
|
|
1657
|
+
});
|
|
1500
1658
|
subscriptions.push(() => appState.remove());
|
|
1501
1659
|
if (opts.navigationRef) {
|
|
1502
1660
|
const off = opts.navigationRef.addListener("state", checkScreen);
|
|
@@ -48,6 +48,12 @@ export type ControllerOptions = {
|
|
|
48
48
|
endpoint?: string;
|
|
49
49
|
/** Bundle id or package name. */
|
|
50
50
|
appId?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Logs what this device measures and sends, with console.log: the sampling decision, when the frame
|
|
53
|
+
* meters run, frames a second per thread with their gaps, and each report against the server's
|
|
54
|
+
* limits. For development builds; leave it off in production.
|
|
55
|
+
*/
|
|
56
|
+
debug?: boolean;
|
|
51
57
|
};
|
|
52
58
|
type HttpResponse = {
|
|
53
59
|
status: number;
|
|
@@ -72,7 +78,8 @@ export type ControllerEnv = {
|
|
|
72
78
|
};
|
|
73
79
|
/** Local calendar day of a wall time, `YYYY-MM-DD`. */
|
|
74
80
|
day?(wall: number): string;
|
|
75
|
-
|
|
81
|
+
/** Warnings (refusals, kill switch), notes for the developer ('info') and, with `debug`, the trace ('debug'). */
|
|
82
|
+
log?(message: string, level?: 'warn' | 'info' | 'debug'): void;
|
|
76
83
|
};
|
|
77
84
|
/** The frame meters and the instrumentation hooks, as the platform runs them. */
|
|
78
85
|
export type Meters = {
|
|
@@ -134,6 +141,19 @@ export declare class VitalsController {
|
|
|
134
141
|
private meters;
|
|
135
142
|
private screen;
|
|
136
143
|
private reduceMotion;
|
|
144
|
+
/**
|
|
145
|
+
* debug: frames per thread, printed about once a second. Each thread keeps its own span (its earliest
|
|
146
|
+
* frame start to its latest end): UI frames reach the JS side in batches of about half a second, so a
|
|
147
|
+
* shared span would halve one thread's rate.
|
|
148
|
+
*/
|
|
149
|
+
private traced;
|
|
150
|
+
private tracedAnims;
|
|
151
|
+
/** debug: between "meters on" and "meters off", so each is logged once per motion window. */
|
|
152
|
+
private metering;
|
|
153
|
+
/** False while the app is in the foreground but not active (iOS 'inactive'): nothing is measured. */
|
|
154
|
+
private drawing;
|
|
155
|
+
/** What this launch last told the developer about the project keeping development builds. */
|
|
156
|
+
private toldDevelopment;
|
|
137
157
|
constructor(env: ControllerEnv);
|
|
138
158
|
get isSampled(): boolean;
|
|
139
159
|
get isEnabled(): boolean;
|
|
@@ -150,8 +170,11 @@ export declare class VitalsController {
|
|
|
150
170
|
stop(drop: boolean): Promise<void>;
|
|
151
171
|
private beginSession;
|
|
152
172
|
private endSampling;
|
|
153
|
-
/**
|
|
154
|
-
|
|
173
|
+
/**
|
|
174
|
+
* AppState. `active`: in the foreground (iOS 'inactive' counts, the app is still on screen). `drawing`:
|
|
175
|
+
* 'active' itself; while it is false the meters stop and motion does not start them (jsLoop.ts).
|
|
176
|
+
*/
|
|
177
|
+
appState(active: boolean, drawing?: boolean): void;
|
|
155
178
|
setScreen(name: string): void;
|
|
156
179
|
setReduceMotion(on: boolean): void;
|
|
157
180
|
/** A new id for an animation call on the JS thread (the UI runtime numbers its own from UI_EVENT_BASE). */
|
|
@@ -169,6 +192,8 @@ export declare class VitalsController {
|
|
|
169
192
|
scroll(t?: number): void;
|
|
170
193
|
touch(down: boolean, t?: number): void;
|
|
171
194
|
private motion;
|
|
195
|
+
/** Meters off and the gate shut: measured time stops at `now`; the next motion starts them again. */
|
|
196
|
+
private pauseMeters;
|
|
172
197
|
private opened;
|
|
173
198
|
/** The UI loop keeps its own time: tell it how long the gate stays open when that grows. */
|
|
174
199
|
private keepUi;
|
|
@@ -179,6 +204,10 @@ export declare class VitalsController {
|
|
|
179
204
|
jsFrame(t: number, dt: number, budget: number): boolean;
|
|
180
205
|
/** UI-thread frames as `[t, dt, budget, …]`, `t` already on the JS clock. */
|
|
181
206
|
uiFrames(buf: number[]): void;
|
|
207
|
+
private traceFrame;
|
|
208
|
+
/** One line: frames a second per thread (60, 90 or 120 when the meters are right) and their gaps. */
|
|
209
|
+
private flushTrace;
|
|
210
|
+
private traceReport;
|
|
182
211
|
private checkWindow;
|
|
183
212
|
/** The report the open window would send now, or null when it holds nothing. */
|
|
184
213
|
report(ended: VitalsReport['session']['ended'], now?: number): VitalsReport | null;
|
|
@@ -200,10 +229,16 @@ export declare class VitalsController {
|
|
|
200
229
|
private fetchConfig;
|
|
201
230
|
/** A config from the server: cached, and acted on (stop, disabled, a minimum SDK version). */
|
|
202
231
|
applyConfig(config: VitalsConfig): Promise<void>;
|
|
232
|
+
/**
|
|
233
|
+
* A development build learns whether the project keeps its reports. Off: the developer is told once a
|
|
234
|
+
* launch (the server would drop every report) and measuring stops; on: said under `debug`.
|
|
235
|
+
*/
|
|
236
|
+
private developmentConfig;
|
|
203
237
|
private killSwitch;
|
|
204
238
|
private allowed;
|
|
205
239
|
private rate;
|
|
206
240
|
private clearTimers;
|
|
207
241
|
private log;
|
|
242
|
+
private debug;
|
|
208
243
|
}
|
|
209
244
|
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The shortest gap between two display frames: 240 Hz, the server's limit (VITALS_MAX_HZ). */
|
|
2
|
+
export declare const MIN_FRAME_GAP_MS: number;
|
|
3
|
+
export type JsLoopDeps = {
|
|
4
|
+
raf: (cb: () => void) => unknown;
|
|
5
|
+
/** Waits about a frame without the display (setTimeout): used when requestAnimationFrame stops waiting for it. */
|
|
6
|
+
later: (cb: () => void) => unknown;
|
|
7
|
+
now: () => number;
|
|
8
|
+
/** One frame that ended at `t`: return false to end the loop (the motion window closed). */
|
|
9
|
+
frame: (t: number) => boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function createJsLoop({ raf, later, now, frame }: JsLoopDeps): {
|
|
12
|
+
readonly running: boolean;
|
|
13
|
+
/** Starts the loop unless it is already running. Returns true when it started a new chain. */
|
|
14
|
+
start(): boolean;
|
|
15
|
+
stop(): void;
|
|
16
|
+
};
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** This package's version: sent in every report's `sdk`, and compared with the config's `minSdk`. Keep it in step with package.json. */
|
|
2
2
|
export declare const SDK_NAME = "@keyframerdev/vitals";
|
|
3
|
-
export declare const SDK_VERSION = "0.1.
|
|
3
|
+
export declare const SDK_VERSION = "0.1.3";
|
package/package.json
CHANGED