@xenon-device-management/xenon 2.16.0 → 2.17.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/README.md +2 -2
- package/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-2W4JvEyZ.js → AnnotationOverlay-CjmMCS0S.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-GmckX_7r.js → ApiKeyGate-B4Ob0v3C.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-DqODzkyo.js → DeviceMosaicView-DTVOB_NO.js} +1 -1
- package/lib/public/assets/{EmptyState-7hETKEWb.js → EmptyState-DcEG-oAi.js} +1 -1
- package/lib/public/assets/{FieldGroup-GOgssFEP.js → FieldGroup-CtnCDshE.js} +1 -1
- package/lib/public/assets/{FilterMenu-Yvf6RGMd.js → FilterMenu-BK0dE9cc.js} +1 -1
- package/lib/public/assets/{Menu-KrPnfdsf.js → Menu-eKsmLpKS.js} +1 -1
- package/lib/public/assets/{Modal-C6WjZFeI.js → Modal-DPwop3Lz.js} +1 -1
- package/lib/public/assets/{RecordingPage-CSRIhZEh.js → RecordingPage-CfgSkZkB.js} +1 -1
- package/lib/public/assets/{RecordingsPage-CB8ST3nE.js → RecordingsPage-Bzl9pTUY.js} +1 -1
- package/lib/public/assets/{SegmentedControl-C9o90KId.js → SegmentedControl-B_TZTrPm.js} +1 -1
- package/lib/public/assets/{SettingCard-CPm3w9l2.js → SettingCard-DlmvZJPM.js} +1 -1
- package/lib/public/assets/{Table-DwIgERRt.js → Table-BigPWKXi.js} +1 -1
- package/lib/public/assets/{activity-DrLbHifn.js → activity-CvuZdQYu.js} +1 -1
- package/lib/public/assets/{ai-settings-DU_Ls06P.js → ai-settings-lI2UJ2n7.js} +1 -1
- package/lib/public/assets/{api-keys-CWYdEzJ_.js → api-keys-CPgf9XJM.js} +1 -1
- package/lib/public/assets/{apps-BqNVdln7.js → apps-DIzOvtUp.js} +1 -1
- package/lib/public/assets/{arrow-left-BtB0_Jg_.js → arrow-left-DiP5ywBT.js} +1 -1
- package/lib/public/assets/{arrow-right-C8NhdtyN.js → arrow-right-DtW_8hm5.js} +1 -1
- package/lib/public/assets/{arrow-up-right-gMg0ffvv.js → arrow-up-right-C89FbNU0.js} +1 -1
- package/lib/public/assets/{auth-shell-CiYZ3N4M.js → auth-shell-B7sUDb--.js} +2 -2
- package/lib/public/assets/{builds-page-EaaqDmLN.js → builds-page-BBhvrMfA.js} +1 -1
- package/lib/public/assets/{button-DDau4Axv.js → button-DugsdUw1.js} +1 -1
- package/lib/public/assets/{calendar-DQ83B1ed.js → calendar-grKd08ZH.js} +1 -1
- package/lib/public/assets/{check-K1ovTQam.js → check-ORjyNfnX.js} +1 -1
- package/lib/public/assets/{chevron-right-Biq00pMt.js → chevron-right-Ei2FxUXq.js} +1 -1
- package/lib/public/assets/{circle-check-CBv88cH7.js → circle-check-Day0HjW4.js} +1 -1
- package/lib/public/assets/{circle-x-Bmltailn.js → circle-x-CHxSRLHp.js} +1 -1
- package/lib/public/assets/{clock-BPrld-3h.js → clock-BDHewWTx.js} +1 -1
- package/lib/public/assets/{copy-t0QB14JI.js → copy-CTwT4I3D.js} +1 -1
- package/lib/public/assets/{device-explorer-BgGZ8AvV.js → device-explorer-DlhrKJ-g.js} +1 -1
- package/lib/public/assets/{download-BroHxNOh.js → download-D3bjz_vr.js} +1 -1
- package/lib/public/assets/{forgot-password-Bjmf49i6.js → forgot-password-BNBLi023.js} +1 -1
- package/lib/public/assets/{h264Stream-DeD__6wa.js → h264Stream-C51Au2i_.js} +1 -1
- package/lib/public/assets/{index-B0gQkEg0.js → index-Dk5J6HB1.js} +2 -2
- package/lib/public/assets/{index-B0JeuZbg.js → index-DncSz5LG.js} +1 -1
- package/lib/public/assets/{index-72oF89LJ.js → index-Z7_BY3pJ.js} +1 -1
- package/lib/public/assets/{input-DIc4MSC7.js → input-bTXM4l09.js} +1 -1
- package/lib/public/assets/{line-chart-BcWm2vW8.js → line-chart-B3YmVYyr.js} +1 -1
- package/lib/public/assets/{list-checks-B06IJJad.js → list-checks-BClKEj1-.js} +1 -1
- package/lib/public/assets/{lock-pVLvnN30.js → lock-CZsenX3D.js} +1 -1
- package/lib/public/assets/{login-CN77vy--.js → login-B6A6tOp9.js} +1 -1
- package/lib/public/assets/{maintenance-settings-CSdzQSkY.js → maintenance-settings-CmCJGy7O.js} +1 -1
- package/lib/public/assets/{monitor-BtrN5IkI.js → monitor-BdTNEglU.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-CNsQfo6w.js → mouse-pointer-2-BM8CaLtt.js} +1 -1
- package/lib/public/assets/{network-BSfA8ZxG.js → network-BS6vUc1x.js} +1 -1
- package/lib/public/assets/{overview-BfTebCK5.js → overview-Yfk_Y7g3.js} +1 -1
- package/lib/public/assets/{page-header-D8KUX6Qg.js → page-header-DhXCwpml.js} +1 -1
- package/lib/public/assets/{play-Cvc8xzic.js → play-D08gP1td.js} +1 -1
- package/lib/public/assets/{plus-C9Zj84UF.js → plus-BKboorfa.js} +1 -1
- package/lib/public/assets/{profile-page-Bi27STgs.js → profile-page-Cd9NodrA.js} +1 -1
- package/lib/public/assets/{reset-password-CPsOuwSu.js → reset-password-acLDyyDW.js} +1 -1
- package/lib/public/assets/{runbook-page-DEQ-DBOX.js → runbook-page-CWLkC1Fd.js} +1 -1
- package/lib/public/assets/{select-BWpnhWXj.js → select-DZoRKNti.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BefSCSpe.js → selector-detail-redirect-DS4qrt84.js} +1 -1
- package/lib/public/assets/{selector-health-page-MSk96Zz7.js → selector-health-page-TpmN61G3.js} +1 -1
- package/lib/public/assets/{session-detail-page-DPyBNef_.js → session-detail-page-C0wYaXqy.js} +1 -1
- package/lib/public/assets/{settings-V6x-cXwy.js → settings-CxPGEY2j.js} +1 -1
- package/lib/public/assets/{stat-tile-Duex-GZD.js → stat-tile-DUpyYgl-.js} +1 -1
- package/lib/public/assets/{tablet-BBcoqBmL.js → tablet-SA02BJNQ.js} +1 -1
- package/lib/public/assets/{teams-ts8p5rEh.js → teams-jamD0Kxr.js} +1 -1
- package/lib/public/assets/{trash-2-CZ2ew6AZ.js → trash-2-DO8fv6w4.js} +1 -1
- package/lib/public/assets/{upload-Dv99y_le.js → upload-CaiGCStb.js} +1 -1
- package/lib/public/assets/{use-builds-data-DrYC8JCF.js → use-builds-data-jS-fcvVf.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-C7QkBNkP.js → use-password-reset-mode-BzoenegM.js} +1 -1
- package/lib/public/assets/{users-gJ5Y01yo.js → users-Bnw7Vs_B.js} +1 -1
- package/lib/public/assets/{users-D7tKMESH.js → users-DHvDrriG.js} +1 -1
- package/lib/public/assets/{video-off-CqZVuZGs.js → video-off-FEvy0w5U.js} +1 -1
- package/lib/public/assets/{webhook-settings-DI5DSW99.js → webhook-settings-CBVB5lxi.js} +1 -1
- package/lib/public/assets/{zap-ClcHFxfo.js → zap-BMrTJO6M.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +1 -1
- package/lib/src/app/index.js +2 -0
- package/lib/src/app/openapi/grid.yaml +102 -0
- package/lib/src/dashboard/event-manager.js +5 -8
- package/lib/src/device-utils.js +11 -2
- package/lib/src/gateway/nodeAsk.js +57 -0
- package/lib/src/gateway/nodeSessionDeviceLogs.js +35 -0
- package/lib/src/plugin.js +4 -1
- package/lib/src/scripts/initialize-database.js +0 -6
- package/lib/src/scripts/run-migrations.js +123 -880
- package/lib/src/services/SessionLifecycleService.js +20 -5
- package/lib/src/services/interceptor/CertManager.js +50 -16
- package/lib/src/services/logcat/NodeDeviceLogStore.js +99 -0
- package/lib/src/services/logcat/NodeDeviceLogsCollector.js +164 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +199 -44
- package/lib/src/services/logcat/nodeDeviceLogs.js +91 -0
- package/lib/src/services/metrics/nodeMetrics.js +20 -49
- package/lib/src/sessions/RemoteSession.js +11 -2
- package/lib/src/sessions/SessionManager.js +28 -1
- package/lib/test/helpers/fake-logcat.js +80 -0
- package/lib/test/helpers/hub-node-pair.js +227 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +15 -0
- package/lib/test/unit/hub-node-iphone-readiness.spec.js +134 -0
- package/lib/test/unit/hub-node-session-device-logs.spec.js +194 -0
- package/lib/test/unit/hub-node-session-metrics.spec.js +24 -192
- package/lib/test/unit/hub-remote-session-heartbeat.spec.js +8 -1
- package/lib/test/unit/interceptor/CertManager.spec.js +89 -0
- package/lib/test/unit/node-device-log-store.spec.js +102 -0
- package/lib/test/unit/node-device-logs-collector.spec.js +343 -0
- package/lib/test/unit/node-gateway-create-appium-server.spec.js +8 -1
- package/lib/test/unit/node-hub-session-owner.spec.js +8 -1
- package/lib/test/unit/node-session-device-logs-route.spec.js +103 -0
- package/lib/test/unit/run-migrations-database.spec.js +57 -424
- package/lib/test/unit/run-migrations.test.js +74 -674
- package/lib/test/unit/session-device-logs-hub-node.spec.js +340 -0
- package/lib/test/unit/session-device-logs-lifecycle.spec.js +137 -0
- package/lib/test/unit/session-device-logs.spec.js +32 -94
- package/lib/test/unit/session-log-indexes.spec.js +15 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/schema.json +1 -1
|
@@ -26,6 +26,9 @@ const PluginContext_1 = require("../../PluginContext");
|
|
|
26
26
|
const localDeviceHosts_1 = require("../../device-managers/localDeviceHosts");
|
|
27
27
|
const LogcatStreamService_1 = require("../../device-managers/android/LogcatStreamService");
|
|
28
28
|
const adbForPhone_1 = require("../network/adbForPhone");
|
|
29
|
+
const NodeDeviceLogStore_1 = require("./NodeDeviceLogStore");
|
|
30
|
+
const NodeDeviceLogsCollector_1 = require("./NodeDeviceLogsCollector");
|
|
31
|
+
const nodeDeviceLogs_1 = require("./nodeDeviceLogs");
|
|
29
32
|
const deviceLogBook_1 = require("./deviceLogBook");
|
|
30
33
|
const iosDriverLog_1 = require("./iosDriverLog");
|
|
31
34
|
/** How often a session's new lines are written. */
|
|
@@ -37,6 +40,11 @@ const CLOCK_TIMEOUT_MS = 5000;
|
|
|
37
40
|
/** Waits before opening the phone's log stream again: doubling from the first, up to the last. */
|
|
38
41
|
const RETRY_FIRST_MS = 1000;
|
|
39
42
|
const RETRY_MAX_MS = 30000;
|
|
43
|
+
/**
|
|
44
|
+
* The newest stored rows a hub reads when its collection resumes after a
|
|
45
|
+
* restart, to find where they end in the node's first answer.
|
|
46
|
+
*/
|
|
47
|
+
const RESUME_TAIL = 32;
|
|
40
48
|
/**
|
|
41
49
|
* The session page's Device logs for a session on this server's own Android
|
|
42
50
|
* phone, iPhone or simulator: its lines from start to end, each once, with
|
|
@@ -58,6 +66,14 @@ const RETRY_MAX_MS = 30000;
|
|
|
58
66
|
* ostrace`): that one has no simulators, needs the phone's go-ios tunnel on
|
|
59
67
|
* iOS 17+, serves one reader per phone and carries ten times the lines.
|
|
60
68
|
*
|
|
69
|
+
* A node records the sessions its hub creates the same way, whatever its
|
|
70
|
+
* dashboard setting, but has no Session row for them: it holds the rows in
|
|
71
|
+
* memory (NodeDeviceLogStore) for the hub to collect, and stops recording a
|
|
72
|
+
* session no hub has asked about within UNCLAIMED_MS (a hub with its
|
|
73
|
+
* dashboard off never asks). On a hub, a session on a node's phone gets a
|
|
74
|
+
* NodeDeviceLogsCollector instead, which asks the node for the rows and
|
|
75
|
+
* writes them here, in the order the node kept them.
|
|
76
|
+
*
|
|
61
77
|
* Until 2.14 each recorded command dumped the last 500 lines (`logcat -d`),
|
|
62
78
|
* kept the last 100 and skipped as many as the previous dump had given, so
|
|
63
79
|
* after the first command nothing more was ever saved, each line stamped with
|
|
@@ -68,73 +84,184 @@ let SessionDeviceLogs = class SessionDeviceLogs {
|
|
|
68
84
|
constructor() {
|
|
69
85
|
this.log = logger_1.default.scope('DeviceLogs');
|
|
70
86
|
this.running = new Map();
|
|
87
|
+
/** Stops under way: a second ending waits for the first one's last lines. */
|
|
88
|
+
this.stopping = new Map();
|
|
71
89
|
}
|
|
72
90
|
/**
|
|
73
91
|
* Whether a session on this device is recorded: an Android phone or
|
|
74
|
-
* emulator, an iPhone or a simulator, that this server drives
|
|
92
|
+
* emulator, an iPhone or a simulator, that this server drives, or, on a
|
|
93
|
+
* hub, a node's through its session.
|
|
75
94
|
*/
|
|
76
|
-
appliesTo(device) {
|
|
95
|
+
appliesTo(device, source) {
|
|
77
96
|
var _a;
|
|
78
97
|
if (!device || device.cloud)
|
|
79
98
|
return false;
|
|
80
99
|
if (!['android', 'ios', 'tvos'].includes(String((_a = device.platform) !== null && _a !== void 0 ? _a : '').toLowerCase())) {
|
|
81
100
|
return false;
|
|
82
101
|
}
|
|
102
|
+
if (this.ownPhone(device))
|
|
103
|
+
return true;
|
|
83
104
|
// A node's phone isn't reachable from here (its driver and its adb are
|
|
84
|
-
// the node's)
|
|
105
|
+
// the node's): on a hub, its lines are collected from the node. Never a
|
|
106
|
+
// cloud provider's (above).
|
|
107
|
+
return !!source && !this.holdsForHub();
|
|
108
|
+
}
|
|
109
|
+
ownPhone(device) {
|
|
85
110
|
const ctx = this.context();
|
|
86
111
|
return (0, localDeviceHosts_1.isOwnDevice)((0, localDeviceHosts_1.localDeviceHosts)(ctx.pluginArgs, ctx.port), ctx.nodeId, device);
|
|
87
112
|
}
|
|
88
113
|
/**
|
|
89
114
|
* Starts recording the session's device log. Resolves once it listens to
|
|
90
|
-
* the phone's stream (or is waiting to try again)
|
|
115
|
+
* the phone's stream (or is waiting to try again), or asks the node;
|
|
116
|
+
* callers needn't wait.
|
|
91
117
|
*/
|
|
92
|
-
start({ sessionId, device, since, driverLog, appUnderTest }) {
|
|
93
|
-
var _a, _b;
|
|
94
|
-
if (this.running.has(sessionId) || !this.appliesTo(device))
|
|
118
|
+
start({ sessionId, device, since, source, resume, driverLog, appUnderTest, }) {
|
|
119
|
+
var _a, _b, _c, _d;
|
|
120
|
+
if (this.running.has(sessionId) || !this.appliesTo(device, source))
|
|
95
121
|
return Promise.resolve();
|
|
122
|
+
const fromNode = !!source && !this.ownPhone(device);
|
|
96
123
|
const android = String(device.platform).toLowerCase() === 'android';
|
|
97
|
-
if (!android && !(0, iosDriverLog_1.isDriverLog)(driverLog)) {
|
|
124
|
+
if (!fromNode && !android && !(0, iosDriverLog_1.isDriverLog)(driverLog)) {
|
|
98
125
|
this.log.info(`[${sessionId}] No device log for ${device.udid}: the driver isn't capturing one ` +
|
|
99
126
|
'(appium:skipLogCapture?)');
|
|
100
127
|
return Promise.resolve();
|
|
101
128
|
}
|
|
129
|
+
// A node has no Session row for the hub's sessions, so it holds the rows
|
|
130
|
+
// for its hub to collect (GET /node/sessions/:id/device-logs).
|
|
131
|
+
const held = this.holdsForHub();
|
|
102
132
|
const entry = {
|
|
103
133
|
udid: device.udid,
|
|
104
134
|
buffer: [],
|
|
105
|
-
flushTimer: setInterval(() => void this.write(sessionId, entry), this.flushIntervalMs()),
|
|
106
135
|
writing: Promise.resolve(),
|
|
107
136
|
lastCreatedAt: 0,
|
|
108
137
|
attempts: 0,
|
|
109
138
|
stopped: false,
|
|
139
|
+
held,
|
|
110
140
|
};
|
|
111
|
-
|
|
141
|
+
if (!held) {
|
|
142
|
+
entry.flushTimer = setInterval(() => void this.write(sessionId, entry), this.flushIntervalMs());
|
|
143
|
+
(_b = (_a = entry.flushTimer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
144
|
+
}
|
|
112
145
|
this.running.set(sessionId, entry);
|
|
146
|
+
const warn = (err) => { var _a; return this.log.warn(`[${sessionId}] Device log not recorded: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`); };
|
|
147
|
+
if (fromNode) {
|
|
148
|
+
this.log.info(`[${sessionId}] Collecting the device log of ${device.udid} from its node`);
|
|
149
|
+
entry.collecting = this.collect(sessionId, entry, source, resume === true).catch(warn);
|
|
150
|
+
return entry.collecting;
|
|
151
|
+
}
|
|
152
|
+
if (held) {
|
|
153
|
+
this.nodeStore().begin(sessionId);
|
|
154
|
+
entry.unclaimedTimer = setTimeout(() => this.dropUnclaimed(sessionId), this.unclaimedMs());
|
|
155
|
+
(_d = (_c = entry.unclaimedTimer).unref) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
156
|
+
}
|
|
113
157
|
this.log.info(`[${sessionId}] Recording the device log of ${device.udid}`);
|
|
114
158
|
const opened = android
|
|
115
159
|
? this.open(sessionId, entry, since !== null && since !== void 0 ? since : Date.now())
|
|
116
160
|
: Promise.resolve(this.listenToDriver(sessionId, entry, since !== null && since !== void 0 ? since : Date.now(), driverLog, appUnderTest));
|
|
117
|
-
return opened.catch(
|
|
161
|
+
return opened.catch(warn);
|
|
118
162
|
}
|
|
119
|
-
/**
|
|
163
|
+
/**
|
|
164
|
+
* Stops listening and writes what is left (on a node, holds it for the
|
|
165
|
+
* hub). Idempotent: a stop while one is under way resolves with it, once
|
|
166
|
+
* the last lines are written, since what follows an ending (the failure
|
|
167
|
+
* analysis) reads them.
|
|
168
|
+
*/
|
|
120
169
|
stop(sessionId) {
|
|
170
|
+
const underway = this.stopping.get(sessionId);
|
|
171
|
+
if (underway)
|
|
172
|
+
return underway;
|
|
173
|
+
const entry = this.running.get(sessionId);
|
|
174
|
+
if (!entry)
|
|
175
|
+
return Promise.resolve();
|
|
176
|
+
const done = this.finishStop(sessionId, entry).finally(() => this.stopping.delete(sessionId));
|
|
177
|
+
this.stopping.set(sessionId, done);
|
|
178
|
+
return done;
|
|
179
|
+
}
|
|
180
|
+
finishStop(sessionId, entry) {
|
|
121
181
|
return __awaiter(this, void 0, void 0, function* () {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
entry.
|
|
128
|
-
|
|
129
|
-
if (entry.retryTimer)
|
|
130
|
-
clearTimeout(entry.retryTimer);
|
|
131
|
-
(_a = entry.remove) === null || _a === void 0 ? void 0 : _a.call(entry);
|
|
132
|
-
entry.remove = undefined;
|
|
182
|
+
this.halt(sessionId, entry);
|
|
183
|
+
// On a hub: the node's last lines. Its session ended first (the hub's
|
|
184
|
+
// DELETE reached it), so they are all there.
|
|
185
|
+
if (entry.collecting)
|
|
186
|
+
yield entry.collecting;
|
|
187
|
+
if (entry.collector)
|
|
188
|
+
yield entry.collector.stop();
|
|
133
189
|
if (entry.book)
|
|
134
|
-
|
|
135
|
-
|
|
190
|
+
this.keep(sessionId, entry, entry.book.finish(new Date()));
|
|
191
|
+
if (entry.held)
|
|
192
|
+
this.nodeStore().end(sessionId);
|
|
193
|
+
else
|
|
194
|
+
yield this.write(sessionId, entry);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/** Stops listening, with nothing more written or held. */
|
|
198
|
+
halt(sessionId, entry) {
|
|
199
|
+
var _a;
|
|
200
|
+
this.running.delete(sessionId);
|
|
201
|
+
entry.stopped = true;
|
|
202
|
+
if (entry.flushTimer)
|
|
203
|
+
clearInterval(entry.flushTimer);
|
|
204
|
+
if (entry.retryTimer)
|
|
205
|
+
clearTimeout(entry.retryTimer);
|
|
206
|
+
if (entry.unclaimedTimer)
|
|
207
|
+
clearTimeout(entry.unclaimedTimer);
|
|
208
|
+
(_a = entry.remove) === null || _a === void 0 ? void 0 : _a.call(entry);
|
|
209
|
+
entry.remove = undefined;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* On a node: no hub has asked about the session since it started, so none
|
|
213
|
+
* collects it (a hub with its dashboard off, the default, or an older
|
|
214
|
+
* one). Recording it would run the phone's log stream and hold up to the
|
|
215
|
+
* book's limit for nothing.
|
|
216
|
+
*/
|
|
217
|
+
dropUnclaimed(sessionId) {
|
|
218
|
+
const entry = this.running.get(sessionId);
|
|
219
|
+
if (!entry || this.nodeStore().claimed(sessionId))
|
|
220
|
+
return;
|
|
221
|
+
this.halt(sessionId, entry);
|
|
222
|
+
this.nodeStore().forget(sessionId);
|
|
223
|
+
this.log.debug(`[${sessionId}] No hub asked for the device log of ${entry.udid}; stopped recording it`);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* On a hub: asks the node for the session's lines and writes each answer's
|
|
227
|
+
* at once. After a restart, new rows go after the newest ones stored.
|
|
228
|
+
*/
|
|
229
|
+
collect(sessionId, entry, source, resume) {
|
|
230
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
231
|
+
var _a;
|
|
232
|
+
let tail = [];
|
|
233
|
+
if (resume) {
|
|
234
|
+
try {
|
|
235
|
+
tail = yield this.storedTail(sessionId);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
this.log.warn(`[${sessionId}] Can't read the newest device log lines stored: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
|
239
|
+
}
|
|
240
|
+
if (tail.length > 0)
|
|
241
|
+
entry.lastCreatedAt = tail[tail.length - 1].createdAt;
|
|
242
|
+
}
|
|
243
|
+
entry.collector = this.collectorFor(source, (rows) => {
|
|
244
|
+
this.keep(sessionId, entry, rows);
|
|
245
|
+
return this.write(sessionId, entry);
|
|
246
|
+
}, tail.map(({ message, timestamp }) => ({ message, timestamp })));
|
|
247
|
+
// Made even when the session ended meanwhile: its stop then asks the
|
|
248
|
+
// node once, for the last lines.
|
|
249
|
+
if (!entry.stopped)
|
|
250
|
+
entry.collector.start();
|
|
136
251
|
});
|
|
137
252
|
}
|
|
253
|
+
/** The session's new rows: held for the hub on a node, else written soon. */
|
|
254
|
+
keep(sessionId, entry, rows) {
|
|
255
|
+
if (rows.length === 0)
|
|
256
|
+
return;
|
|
257
|
+
if (entry.held) {
|
|
258
|
+
this.nodeStore().add(sessionId, rows);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
entry.buffer.push(...rows);
|
|
262
|
+
if (entry.buffer.length >= exports.WRITE_BATCH)
|
|
263
|
+
void this.write(sessionId, entry);
|
|
264
|
+
}
|
|
138
265
|
/** Whether the session is recorded here. */
|
|
139
266
|
isRecording(sessionId) {
|
|
140
267
|
return this.running.has(sessionId);
|
|
@@ -207,12 +334,7 @@ let SessionDeviceLogs = class SessionDeviceLogs {
|
|
|
207
334
|
delivered = true;
|
|
208
335
|
entry.attempts = 0;
|
|
209
336
|
}
|
|
210
|
-
|
|
211
|
-
if (rows.length === 0)
|
|
212
|
-
return;
|
|
213
|
-
entry.buffer.push(...rows);
|
|
214
|
-
if (entry.buffer.length >= exports.WRITE_BATCH)
|
|
215
|
-
void this.write(sessionId, entry);
|
|
337
|
+
this.keep(sessionId, entry, book.add(rec));
|
|
216
338
|
},
|
|
217
339
|
// Never refused: what the session keeps is bounded by its book.
|
|
218
340
|
() => true, () => {
|
|
@@ -224,7 +346,7 @@ let SessionDeviceLogs = class SessionDeviceLogs {
|
|
|
224
346
|
// A stream that delivered nothing (a phone that's gone) isn't noted
|
|
225
347
|
// again on every try.
|
|
226
348
|
if (delivered) {
|
|
227
|
-
|
|
349
|
+
this.keep(sessionId, entry, note);
|
|
228
350
|
this.log.info(`[${sessionId}] The device log of ${entry.udid} ended; opening it again`);
|
|
229
351
|
}
|
|
230
352
|
this.retry(sessionId, entry);
|
|
@@ -246,12 +368,14 @@ let SessionDeviceLogs = class SessionDeviceLogs {
|
|
|
246
368
|
entry.book = book;
|
|
247
369
|
const appLines = appUnderTest ? new iosDriverLog_1.IosAppLines(appUnderTest) : undefined;
|
|
248
370
|
if (appLines) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
371
|
+
this.keep(sessionId, entry, [
|
|
372
|
+
{
|
|
373
|
+
message: `Xenon: Kept here: what the app under test (${appUnderTest}) logs itself, ` +
|
|
374
|
+
'every error inside it, what the phone says about its launch, state, crashes ' +
|
|
375
|
+
"and end, and faults. The rest of the phone's log is left out.",
|
|
376
|
+
timestamp: new Date(since),
|
|
377
|
+
},
|
|
378
|
+
]);
|
|
255
379
|
}
|
|
256
380
|
const take = (line) => {
|
|
257
381
|
const rec = (0, iosDriverLog_1.recordFromDriverLog)(line);
|
|
@@ -259,12 +383,7 @@ let SessionDeviceLogs = class SessionDeviceLogs {
|
|
|
259
383
|
return;
|
|
260
384
|
if (appLines && !appLines.keeps(rec.message, rec.level))
|
|
261
385
|
return;
|
|
262
|
-
|
|
263
|
-
if (rows.length === 0)
|
|
264
|
-
return;
|
|
265
|
-
entry.buffer.push(...rows);
|
|
266
|
-
if (entry.buffer.length >= exports.WRITE_BATCH)
|
|
267
|
-
void this.write(sessionId, entry);
|
|
386
|
+
this.keep(sessionId, entry, book.add(rec));
|
|
268
387
|
};
|
|
269
388
|
// The create's lines first teach the filter the app's process, so its
|
|
270
389
|
// first lines aren't lost to an announcement that comes after them.
|
|
@@ -326,6 +445,42 @@ let SessionDeviceLogs = class SessionDeviceLogs {
|
|
|
326
445
|
retryFirstMs() {
|
|
327
446
|
return RETRY_FIRST_MS;
|
|
328
447
|
}
|
|
448
|
+
/** A node (a server with `hub`) holds the rows for its hub instead of writing them. */
|
|
449
|
+
holdsForHub() {
|
|
450
|
+
var _a;
|
|
451
|
+
return ((_a = this.context().pluginArgs) === null || _a === void 0 ? void 0 : _a.hub) !== undefined;
|
|
452
|
+
}
|
|
453
|
+
nodeStore() {
|
|
454
|
+
return typedi_1.Container.get(NodeDeviceLogStore_1.NodeDeviceLogStore);
|
|
455
|
+
}
|
|
456
|
+
unclaimedMs() {
|
|
457
|
+
return nodeDeviceLogs_1.UNCLAIMED_MS;
|
|
458
|
+
}
|
|
459
|
+
collectorFor(source, onLines, storedTail) {
|
|
460
|
+
return new NodeDeviceLogsCollector_1.NodeDeviceLogsCollector({
|
|
461
|
+
source,
|
|
462
|
+
onLines,
|
|
463
|
+
storedTail,
|
|
464
|
+
support: typedi_1.Container.get(nodeDeviceLogs_1.NodeDeviceLogsSupport),
|
|
465
|
+
logger: this.log,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
/** The session's newest Device logs rows, by `createdAt`, oldest first. */
|
|
469
|
+
storedTail(sessionId) {
|
|
470
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
471
|
+
const rows = yield prisma_1.prisma.log.findMany({
|
|
472
|
+
where: { session_id: sessionId, log_type: 'DEVICE' },
|
|
473
|
+
orderBy: { createdAt: 'desc' },
|
|
474
|
+
take: RESUME_TAIL,
|
|
475
|
+
select: { message: true, timestamp: true, createdAt: true },
|
|
476
|
+
});
|
|
477
|
+
return rows.reverse().map((row) => ({
|
|
478
|
+
message: row.message,
|
|
479
|
+
timestamp: row.timestamp.getTime(),
|
|
480
|
+
createdAt: row.createdAt.getTime(),
|
|
481
|
+
}));
|
|
482
|
+
});
|
|
483
|
+
}
|
|
329
484
|
/** The phone's clock against this server's, or null if its answer isn't one. */
|
|
330
485
|
readClock(udid) {
|
|
331
486
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.NodeDeviceLogsSupport = exports.UNCLAIMED_MS = exports.NODE_DEVICE_LOGS_KEEP_AFTER_END_MS = exports.NODE_DEVICE_LOG_PAGE = exports.NODE_DEVICE_LOGS_HEADER = exports.NODE_DEVICE_LOGS_ROUTE = void 0;
|
|
13
|
+
exports.readNodeDeviceLogsReply = readNodeDeviceLogsReply;
|
|
14
|
+
exports.nodeDeviceLogsSourceOf = nodeDeviceLogsSourceOf;
|
|
15
|
+
const typedi_1 = require("typedi");
|
|
16
|
+
const logger_1 = __importDefault(require("../../logger"));
|
|
17
|
+
const SessionType_1 = __importDefault(require("../../enums/SessionType"));
|
|
18
|
+
const nodeAsk_1 = require("../../gateway/nodeAsk");
|
|
19
|
+
/** Where a node serves a session's device log lines, under `/xenon/api`. */
|
|
20
|
+
exports.NODE_DEVICE_LOGS_ROUTE = '/node/sessions/:sessionId/device-logs';
|
|
21
|
+
/** On every answer of that route, so a hub can tell a node that has it. */
|
|
22
|
+
exports.NODE_DEVICE_LOGS_HEADER = 'x-xenon-node-device-logs';
|
|
23
|
+
/** Lines in one answer. */
|
|
24
|
+
exports.NODE_DEVICE_LOG_PAGE = 2000;
|
|
25
|
+
/** How long a node keeps an ended session's lines for its hub's last ask. */
|
|
26
|
+
exports.NODE_DEVICE_LOGS_KEEP_AFTER_END_MS = 10 * 60000;
|
|
27
|
+
/**
|
|
28
|
+
* A node stops recording a session no hub has asked about this long after
|
|
29
|
+
* it started: its hub doesn't collect (its dashboard is off, the default) or
|
|
30
|
+
* is older. A hub that collects asks as soon as the session is created.
|
|
31
|
+
*/
|
|
32
|
+
exports.UNCLAIMED_MS = 2 * 60000;
|
|
33
|
+
const STATES = ['recording', 'ended', 'off'];
|
|
34
|
+
/**
|
|
35
|
+
* The line, field by field, or null when it isn't one: one malformed row
|
|
36
|
+
* would fail every later write of the session's lines (a failed batch goes
|
|
37
|
+
* back to the front of the buffer).
|
|
38
|
+
*/
|
|
39
|
+
function lineOf(v) {
|
|
40
|
+
if (!v || typeof v !== 'object')
|
|
41
|
+
return null;
|
|
42
|
+
if (!Number.isSafeInteger(v.seq) || v.seq < 1)
|
|
43
|
+
return null;
|
|
44
|
+
if (typeof v.message !== 'string')
|
|
45
|
+
return null;
|
|
46
|
+
if (typeof v.timestamp !== 'number' || !Number.isFinite(v.timestamp))
|
|
47
|
+
return null;
|
|
48
|
+
return { seq: v.seq, message: v.message, timestamp: v.timestamp };
|
|
49
|
+
}
|
|
50
|
+
function deviceLogsAnswerOf(value) {
|
|
51
|
+
if (!value || !STATES.includes(value.state) || !Array.isArray(value.lines))
|
|
52
|
+
return null;
|
|
53
|
+
return {
|
|
54
|
+
state: value.state,
|
|
55
|
+
lines: value.lines.map(lineOf).filter((x) => !!x),
|
|
56
|
+
more: value.more === true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function readNodeDeviceLogsReply(status, headers, data) {
|
|
60
|
+
return (0, nodeAsk_1.readNodeReply)(exports.NODE_DEVICE_LOGS_HEADER, status, headers, data, deviceLogsAnswerOf);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The session as a NodeDeviceLogsSource, when it runs on a node. A cloud
|
|
64
|
+
* provider's session (CloudSession) and this server's own (LocalSession)
|
|
65
|
+
* inherit the methods from RemoteSession, so the type decides.
|
|
66
|
+
*/
|
|
67
|
+
function nodeDeviceLogsSourceOf(session) {
|
|
68
|
+
var _a;
|
|
69
|
+
const s = session;
|
|
70
|
+
return s &&
|
|
71
|
+
((_a = s.getType) === null || _a === void 0 ? void 0 : _a.call(s)) === SessionType_1.default.REMOTE &&
|
|
72
|
+
typeof s.nodeDeviceLogs === 'function' &&
|
|
73
|
+
typeof s.nodeOrigin === 'function'
|
|
74
|
+
? s
|
|
75
|
+
: undefined;
|
|
76
|
+
}
|
|
77
|
+
/** Hub side: which nodes lack the route (an older Xenon). Their sessions have no Device logs. */
|
|
78
|
+
let NodeDeviceLogsSupport = class NodeDeviceLogsSupport extends nodeAsk_1.OlderNodes {
|
|
79
|
+
constructor() {
|
|
80
|
+
super(...arguments);
|
|
81
|
+
this.logger = logger_1.default.scope('DeviceLogs');
|
|
82
|
+
}
|
|
83
|
+
warning(origin, status) {
|
|
84
|
+
return (`Node ${origin} has no session device log route (answered ${status}): an older Xenon. ` +
|
|
85
|
+
'Its sessions have no device logs here. Upgrade the node.');
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
exports.NodeDeviceLogsSupport = NodeDeviceLogsSupport;
|
|
89
|
+
exports.NodeDeviceLogsSupport = NodeDeviceLogsSupport = __decorate([
|
|
90
|
+
(0, typedi_1.Service)()
|
|
91
|
+
], NodeDeviceLogsSupport);
|
|
@@ -9,12 +9,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
9
9
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.NodeMetricsSupport = exports.
|
|
12
|
+
exports.NodeMetricsSupport = exports.KEEP_AFTER_END_MS = exports.NODE_METRICS_HEADER = exports.NODE_METRICS_ROUTE = void 0;
|
|
13
13
|
exports.readNodeMetricsReply = readNodeMetricsReply;
|
|
14
14
|
exports.nodeMetricsSourceOf = nodeMetricsSourceOf;
|
|
15
15
|
const typedi_1 = require("typedi");
|
|
16
16
|
const logger_1 = __importDefault(require("../../logger"));
|
|
17
17
|
const SessionType_1 = __importDefault(require("../../enums/SessionType"));
|
|
18
|
+
const nodeAsk_1 = require("../../gateway/nodeAsk");
|
|
18
19
|
/** Where a node serves a session's figures, under `/xenon/api`. */
|
|
19
20
|
exports.NODE_METRICS_ROUTE = '/node/sessions/:sessionId/metrics';
|
|
20
21
|
/** On every answer of that route, so a hub can tell a node that has it. */
|
|
@@ -41,33 +42,20 @@ function sampleOf(v) {
|
|
|
41
42
|
return null;
|
|
42
43
|
return { at: v.at, deviceCpuPct, deviceMemMb, deviceMemTotalMb, appCpuPct, appMemMb, appId };
|
|
43
44
|
}
|
|
44
|
-
function
|
|
45
|
+
function metricsAnswerOf(value) {
|
|
45
46
|
var _a;
|
|
46
|
-
if (!
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (status === 200 && value && STATES.includes(value.state) && Array.isArray(value.samples)) {
|
|
59
|
-
return {
|
|
60
|
-
kind: 'answer',
|
|
61
|
-
answer: {
|
|
62
|
-
platform: String((_a = value.platform) !== null && _a !== void 0 ? _a : ''),
|
|
63
|
-
state: value.state,
|
|
64
|
-
samples: value.samples
|
|
65
|
-
.map(sampleOf)
|
|
66
|
-
.filter((x) => x !== null),
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
return { kind: 'unavailable', reason: `answered ${status}` };
|
|
47
|
+
if (!value || !STATES.includes(value.state) || !Array.isArray(value.samples))
|
|
48
|
+
return null;
|
|
49
|
+
return {
|
|
50
|
+
platform: String((_a = value.platform) !== null && _a !== void 0 ? _a : ''),
|
|
51
|
+
state: value.state,
|
|
52
|
+
samples: value.samples
|
|
53
|
+
.map(sampleOf)
|
|
54
|
+
.filter((x) => x !== null),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function readNodeMetricsReply(status, headers, data) {
|
|
58
|
+
return (0, nodeAsk_1.readNodeReply)(exports.NODE_METRICS_HEADER, status, headers, data, metricsAnswerOf);
|
|
71
59
|
}
|
|
72
60
|
/**
|
|
73
61
|
* The session as a NodeMetricsSource, when it runs on a node. A cloud
|
|
@@ -84,35 +72,18 @@ function nodeMetricsSourceOf(session) {
|
|
|
84
72
|
? s
|
|
85
73
|
: undefined;
|
|
86
74
|
}
|
|
87
|
-
/** How long the hub leaves a node without the route alone before asking it again. */
|
|
88
|
-
exports.NODE_METRICS_RECHECK_MS = 10 * 60000;
|
|
89
75
|
/**
|
|
90
76
|
* Hub side: which nodes lack the route (an older Xenon). Their sessions show
|
|
91
77
|
* no figures; the hub says so once per node, and asks again after
|
|
92
|
-
*
|
|
78
|
+
* OLDER_NODE_RECHECK_MS, so a node upgraded in place is picked up.
|
|
93
79
|
*/
|
|
94
|
-
let NodeMetricsSupport = class NodeMetricsSupport {
|
|
80
|
+
let NodeMetricsSupport = class NodeMetricsSupport extends nodeAsk_1.OlderNodes {
|
|
95
81
|
constructor() {
|
|
82
|
+
super(...arguments);
|
|
96
83
|
this.logger = logger_1.default.scope('NodeMetrics');
|
|
97
|
-
this.now = () => Date.now();
|
|
98
|
-
this.unsupportedUntil = new Map();
|
|
99
|
-
this.warned = new Set();
|
|
100
|
-
}
|
|
101
|
-
shouldAsk(origin) {
|
|
102
|
-
const until = this.unsupportedUntil.get(origin);
|
|
103
|
-
if (until === undefined)
|
|
104
|
-
return true;
|
|
105
|
-
if (until > this.now())
|
|
106
|
-
return false;
|
|
107
|
-
this.unsupportedUntil.delete(origin);
|
|
108
|
-
return true;
|
|
109
84
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (this.warned.has(origin))
|
|
113
|
-
return;
|
|
114
|
-
this.warned.add(origin);
|
|
115
|
-
this.logger.warn(`Node ${origin} has no session metrics route (answered ${status}): an older Xenon. ` +
|
|
85
|
+
warning(origin, status) {
|
|
86
|
+
return (`Node ${origin} has no session metrics route (answered ${status}): an older Xenon. ` +
|
|
116
87
|
'Its sessions show no CPU or memory. Upgrade the node.');
|
|
117
88
|
}
|
|
118
89
|
};
|
|
@@ -20,6 +20,7 @@ const SessionType_1 = __importDefault(require("../enums/SessionType"));
|
|
|
20
20
|
const hubSessionToken_1 = require("../gateway/hubSessionToken");
|
|
21
21
|
const outboundProxy_1 = require("../helpers/outboundProxy");
|
|
22
22
|
const nodeMetrics_1 = require("../services/metrics/nodeMetrics");
|
|
23
|
+
const nodeDeviceLogs_1 = require("../services/logcat/nodeDeviceLogs");
|
|
23
24
|
const nodeSessionStatus_1 = require("../gateway/nodeSessionStatus");
|
|
24
25
|
const XenonSession_1 = require("./XenonSession");
|
|
25
26
|
class RemoteSession extends XenonSession_1.XenonSession {
|
|
@@ -235,6 +236,14 @@ class RemoteSession extends XenonSession_1.XenonSession {
|
|
|
235
236
|
}
|
|
236
237
|
/** The node's CPU and memory for this session newer than `after` (NodeMetricsCollector). */
|
|
237
238
|
nodeMetrics(after) {
|
|
239
|
+
return this.askNode('metrics', after, nodeMetrics_1.readNodeMetricsReply);
|
|
240
|
+
}
|
|
241
|
+
/** The node's device log lines for this session after line `after` (NodeDeviceLogsCollector). */
|
|
242
|
+
nodeDeviceLogs(after) {
|
|
243
|
+
return this.askNode('device-logs', after, nodeDeviceLogs_1.readNodeDeviceLogsReply);
|
|
244
|
+
}
|
|
245
|
+
/** One of the node's routes about this session, `/xenon/api/node/sessions/<id>/<what>?after=`. */
|
|
246
|
+
askNode(what, after, read) {
|
|
238
247
|
return __awaiter(this, void 0, void 0, function* () {
|
|
239
248
|
var _a, _b, _c;
|
|
240
249
|
const origin = this.nodeOrigin();
|
|
@@ -243,12 +252,12 @@ class RemoteSession extends XenonSession_1.XenonSession {
|
|
|
243
252
|
try {
|
|
244
253
|
const response = yield this.call({
|
|
245
254
|
method: 'get',
|
|
246
|
-
url: `${origin}${nodeSessionStatus_1.NODE_SESSION_STATUS_PATH}/${encodeURIComponent(this.sessionId)}
|
|
255
|
+
url: `${origin}${nodeSessionStatus_1.NODE_SESSION_STATUS_PATH}/${encodeURIComponent(this.sessionId)}/${what}`,
|
|
247
256
|
params: after === null ? undefined : { after },
|
|
248
257
|
timeout: 5000,
|
|
249
258
|
validateStatus: () => true,
|
|
250
259
|
});
|
|
251
|
-
return (
|
|
260
|
+
return read(response.status, (_a = response.headers) !== null && _a !== void 0 ? _a : {}, response.data);
|
|
252
261
|
}
|
|
253
262
|
catch (err) {
|
|
254
263
|
return { kind: 'unavailable', reason: String((_c = (_b = err === null || err === void 0 ? void 0 : err.code) !== null && _b !== void 0 ? _b : err === null || err === void 0 ? void 0 : err.message) !== null && _c !== void 0 ? _c : err) };
|
|
@@ -59,9 +59,11 @@ const logger_1 = __importDefault(require("../logger"));
|
|
|
59
59
|
const RemoteSession_1 = require("./RemoteSession");
|
|
60
60
|
const CloudSession_1 = require("./CloudSession");
|
|
61
61
|
const device_store_1 = require("../data-service/device-store");
|
|
62
|
+
const PluginContext_1 = require("../PluginContext");
|
|
62
63
|
const nodeWebDriverUrl_1 = require("../gateway/nodeWebDriverUrl");
|
|
63
64
|
const XenonCapabilityManager_1 = require("../XenonCapabilityManager");
|
|
64
65
|
const nodeMetrics_1 = require("../services/metrics/nodeMetrics");
|
|
66
|
+
const nodeDeviceLogs_1 = require("../services/logcat/nodeDeviceLogs");
|
|
65
67
|
// A session still running at boot that can't be picked up again is filed
|
|
66
68
|
// under it; the failure analysis also writes it for a shutdown's drain.
|
|
67
69
|
const failureCategories_1 = require("../dashboard/services/failureCategories");
|
|
@@ -121,7 +123,7 @@ let SessionManager = class SessionManager {
|
|
|
121
123
|
*/
|
|
122
124
|
recoverActiveSessions(currentNodeId_1, nodeBasePath_1) {
|
|
123
125
|
return __awaiter(this, arguments, void 0, function* (currentNodeId, nodeBasePath, isLocalHost = () => false) {
|
|
124
|
-
var _a, _b;
|
|
126
|
+
var _a, _b, _c;
|
|
125
127
|
this.log.info('🔄 Attempting to recover active sessions from database...');
|
|
126
128
|
try {
|
|
127
129
|
// Find all sessions that are still "running" (not ended)
|
|
@@ -225,6 +227,13 @@ let SessionManager = class SessionManager {
|
|
|
225
227
|
if (!device.cloud && dbSession.is_profiling_available) {
|
|
226
228
|
yield this.resumeNodeMetrics(dbSession.id, device, recoveredSession, sessionResponse);
|
|
227
229
|
}
|
|
230
|
+
// Its device log too, where the hub records sessions (the dashboard
|
|
231
|
+
// on) and the session didn't turn it off, after the newest line stored.
|
|
232
|
+
if (!device.cloud &&
|
|
233
|
+
((_c = typedi_2.Container.get(PluginContext_1.PluginContext).pluginArgs) === null || _c === void 0 ? void 0 : _c.enableDashboard) &&
|
|
234
|
+
xenonCapabilities[XenonCapabilityManager_1.XENON_CAPABILITIES.SAVE_DEVICE_LOGS] !== false) {
|
|
235
|
+
yield this.resumeNodeDeviceLogs(dbSession.id, device, recoveredSession);
|
|
236
|
+
}
|
|
228
237
|
recoveredCount++;
|
|
229
238
|
}
|
|
230
239
|
catch (sessionErr) {
|
|
@@ -266,6 +275,24 @@ let SessionManager = class SessionManager {
|
|
|
266
275
|
}
|
|
267
276
|
});
|
|
268
277
|
}
|
|
278
|
+
/** Goes on collecting a node session's device log after a hub restart. */
|
|
279
|
+
resumeNodeDeviceLogs(sessionId, device, session) {
|
|
280
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
281
|
+
try {
|
|
282
|
+
// Loaded here: SessionDeviceLogs imports the device managers.
|
|
283
|
+
const { SessionDeviceLogs } = yield Promise.resolve().then(() => __importStar(require('../services/logcat/SessionDeviceLogs')));
|
|
284
|
+
void typedi_2.Container.get(SessionDeviceLogs).start({
|
|
285
|
+
sessionId,
|
|
286
|
+
device,
|
|
287
|
+
source: (0, nodeDeviceLogs_1.nodeDeviceLogsSourceOf)(session),
|
|
288
|
+
resume: true,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
this.log.warn(`Session ${sessionId}: device log not resumed: ${err.message}`);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
269
296
|
/**
|
|
270
297
|
* A recovered session is this process's from now on: its row's heartbeat
|
|
271
298
|
* is stamped with this process, now. The row still carried the heartbeat
|