@xenon-device-management/xenon 2.11.0 → 2.11.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/lib/package.json +1 -1
- package/lib/public/assets/{AnnotationOverlay-BQPQ5bPF.js → AnnotationOverlay-Dt5AmXCR.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-Bd7Pumwv.js → ApiKeyGate-CS05jXd7.js} +1 -1
- package/lib/public/assets/{BugReportButton-iC7fxPxl.js → BugReportButton-ejWk5lof.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-CNdES7nm.js → DeviceMosaicView-C9AypMcW.js} +1 -1
- package/lib/public/assets/{EmptyState-wZeJbGhN.js → EmptyState-DyojFa86.js} +1 -1
- package/lib/public/assets/{FieldGroup-tpRfmhlE.js → FieldGroup-Dm-U3Ucr.js} +1 -1
- package/lib/public/assets/{FilterMenu-BZwOppId.js → FilterMenu-B3hgDfy_.js} +1 -1
- package/lib/public/assets/{Menu-CahoAx8L.js → Menu-DFNsTx2i.js} +1 -1
- package/lib/public/assets/{Modal-mcM5W_Ji.js → Modal-BdWYrwfK.js} +1 -1
- package/lib/public/assets/{RecordingPage-B4dFBndn.js → RecordingPage-FIRd8b5d.js} +1 -1
- package/lib/public/assets/{RecordingsPage-BOez3f_9.js → RecordingsPage-FAUS-RwG.js} +1 -1
- package/lib/public/assets/{SegmentedControl-qMSiMN1d.js → SegmentedControl-EBzIUjnM.js} +1 -1
- package/lib/public/assets/{SettingCard-rcPtJ7HG.js → SettingCard-BQ8wMHrz.js} +1 -1
- package/lib/public/assets/{Table-J3uognu0.js → Table-DDahAwGb.js} +1 -1
- package/lib/public/assets/{activity-_5pNSfKT.js → activity-CFEfukVD.js} +1 -1
- package/lib/public/assets/{ai-settings-DTPRbkei.js → ai-settings-iPPNZMnS.js} +1 -1
- package/lib/public/assets/{api-keys-CxkIUo76.js → api-keys-DS979X_e.js} +1 -1
- package/lib/public/assets/{apps-Bv0pAfxz.js → apps-DQnxhScG.js} +1 -1
- package/lib/public/assets/{arrow-left-uY7F6z1K.js → arrow-left-CIqcStpJ.js} +1 -1
- package/lib/public/assets/{arrow-right-BbAm_2Wa.js → arrow-right-Pw0XgjBJ.js} +1 -1
- package/lib/public/assets/{arrow-up-right-x4H5NuvE.js → arrow-up-right-DWaEiSQf.js} +1 -1
- package/lib/public/assets/{auth-shell-CtMFhbnC.js → auth-shell-DvJ8qKdR.js} +2 -2
- package/lib/public/assets/{builds-page-B_O5uBSZ.js → builds-page-BIWuhGph.js} +1 -1
- package/lib/public/assets/{button-CQoMQEj_.js → button-B1XDl_TT.js} +1 -1
- package/lib/public/assets/{calendar-BZkpCqlL.js → calendar-BPrY2NZL.js} +1 -1
- package/lib/public/assets/{check-CAB6RYuc.js → check-DjbzNVbd.js} +1 -1
- package/lib/public/assets/{chevron-right-ec0unu3u.js → chevron-right-J_WfelGu.js} +1 -1
- package/lib/public/assets/{circle-check-Dfg-gpjg.js → circle-check-CGwn46BE.js} +1 -1
- package/lib/public/assets/{circle-x-cbPq6Btp.js → circle-x-Rfn2Zmq3.js} +1 -1
- package/lib/public/assets/{clock-DU9cBLi8.js → clock-BAD4KA9j.js} +1 -1
- package/lib/public/assets/{copy-VM9kWPpE.js → copy-CcNlsrXH.js} +1 -1
- package/lib/public/assets/{device-explorer-ai0usQ8m.js → device-explorer-Dl1QGKQN.js} +1 -1
- package/lib/public/assets/{download-WgW4AVid.js → download-CoBA2mT_.js} +1 -1
- package/lib/public/assets/{forgot-password-BGRpjTtt.js → forgot-password-BrV3pKRK.js} +1 -1
- package/lib/public/assets/{index-DNDdHt3h.js → index-BHI_dBXi.js} +1 -1
- package/lib/public/assets/{index-C33GjwPr.js → index-DklX18ye.js} +2 -2
- package/lib/public/assets/{input-DwiVcO3Q.js → input-Bo2b9153.js} +1 -1
- package/lib/public/assets/{keycodes-CvCtpVJD.js → keycodes-B5mWBGMd.js} +1 -1
- package/lib/public/assets/{line-chart-PE5e9PeN.js → line-chart-X7ZCpjWB.js} +1 -1
- package/lib/public/assets/{list-checks-Bgn9GEEO.js → list-checks-Cqso6ROH.js} +1 -1
- package/lib/public/assets/{lock-Cb7My6hc.js → lock-D9WyaPyU.js} +1 -1
- package/lib/public/assets/{login-CDmyzSpl.js → login-DqIYeZ2q.js} +1 -1
- package/lib/public/assets/{maintenance-settings-Bq6pKoBt.js → maintenance-settings-C7pWE0--.js} +1 -1
- package/lib/public/assets/{monitor-BZSwrnPV.js → monitor-ZC2MoScA.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-BbFj8y8i.js → mouse-pointer-2-DXh-LIn9.js} +1 -1
- package/lib/public/assets/{network-DpwYGB0-.js → network-CivILLOW.js} +1 -1
- package/lib/public/assets/{overview-B4_IO-X8.js → overview-ClDfJNL5.js} +1 -1
- package/lib/public/assets/{page-header-BpLEuo7i.js → page-header-CV-WOQ7B.js} +1 -1
- package/lib/public/assets/{plus-DHZS0EGU.js → plus-DOp8RUCs.js} +1 -1
- package/lib/public/assets/{profile-page-DsHDIq-Y.js → profile-page-Ckx1bcSx.js} +1 -1
- package/lib/public/assets/{reset-password-DuUjhw_l.js → reset-password-CZDSfFZ6.js} +1 -1
- package/lib/public/assets/{runbook-page-BI_ayvLd.js → runbook-page-BXe7SL5K.js} +1 -1
- package/lib/public/assets/{select-xiba9PSx.js → select-Bo_2fqDM.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-Emz2obK_.js → selector-detail-redirect-D_aNBNim.js} +1 -1
- package/lib/public/assets/{selector-health-page-zpwrgalC.js → selector-health-page-BP4XhSI4.js} +1 -1
- package/lib/public/assets/{session-detail-page-BhlTJ1Oq.js → session-detail-page-DN8MPDsL.js} +1 -1
- package/lib/public/assets/{settings-B-mabJFm.js → settings-rFQ6qW9Q.js} +1 -1
- package/lib/public/assets/{stat-tile-lT8WcPgu.js → stat-tile-D8taj4dL.js} +1 -1
- package/lib/public/assets/{tablet-DM8dp75P.js → tablet-Cz9pTLGh.js} +1 -1
- package/lib/public/assets/{teams-CRkb5-Fe.js → teams-DkFhPU6N.js} +1 -1
- package/lib/public/assets/{trash-2-Dxgryn4o.js → trash-2-DMkIlO0b.js} +1 -1
- package/lib/public/assets/{upload-Ci0vz0BP.js → upload-Dg76vQUo.js} +1 -1
- package/lib/public/assets/{use-builds-data-DaaqMjjL.js → use-builds-data-iSxxYh9G.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-BvIyZtaF.js → use-password-reset-mode-DsNcDXiv.js} +1 -1
- package/lib/public/assets/{users-BT8qx3ea.js → users-BShiNYd9.js} +1 -1
- package/lib/public/assets/{users-CWYHk75q.js → users-Bu1HulwN.js} +1 -1
- package/lib/public/assets/{video-off-BvY08g6I.js → video-off-BJ_s_mLK.js} +1 -1
- package/lib/public/assets/{webhook-settings-aHIlSJFP.js → webhook-settings-CJxMc7-Q.js} +1 -1
- package/lib/public/assets/{zap-qG2Li6rI.js → zap-BXZlQKN8.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/gateway/nodeSessionMetrics.js +5 -2
- package/lib/src/services/metrics/NodeMetricsCollector.js +4 -2
- package/lib/src/services/metrics/SessionMetricsService.js +5 -1
- package/lib/src/services/metrics/nodeMetrics.js +33 -3
- package/lib/src/sessions/RemoteSession.js +1 -1
- package/lib/test/unit/dashboard-events-metrics.spec.js +1 -0
- package/lib/test/unit/hub-node-session-metrics.spec.js +83 -13
- package/lib/test/unit/node-metrics-collector.spec.js +53 -0
- package/lib/test/unit/node-session-metrics-route.spec.js +2 -1
- package/package.json +1 -1
|
@@ -44,6 +44,7 @@ const appiumUmbrella_1 = require("../../src/sessions/appiumUmbrella");
|
|
|
44
44
|
const config_1 = require("../../src/config");
|
|
45
45
|
const container_registration_1 = require("../helpers/container-registration");
|
|
46
46
|
const scratch_database_1 = require("../helpers/scratch-database");
|
|
47
|
+
const selector_health_fixture_1 = require("../helpers/selector-health-fixture");
|
|
47
48
|
const appium_umbrella_1 = require("../helpers/appium-umbrella");
|
|
48
49
|
/**
|
|
49
50
|
* A hub collects CPU and memory for a session on a node's phone from the
|
|
@@ -67,6 +68,19 @@ class NodeSideMetrics extends SessionMetricsService_1.SessionMetricsService {
|
|
|
67
68
|
return { start: () => undefined, stop: () => __awaiter(this, void 0, void 0, function* () { return void (this.stopped += 1); }) };
|
|
68
69
|
}
|
|
69
70
|
}
|
|
71
|
+
const hubContext = () => ({
|
|
72
|
+
pluginArgs: Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { bindHostOrIp: '127.0.0.1' }),
|
|
73
|
+
port: 4799,
|
|
74
|
+
nodeId: 'hub-1',
|
|
75
|
+
});
|
|
76
|
+
const fastCollector = (source, hooks, after) => new NodeMetricsCollector_1.NodeMetricsCollector({
|
|
77
|
+
source,
|
|
78
|
+
hooks,
|
|
79
|
+
after,
|
|
80
|
+
support: typedi_1.Container.get(nodeMetrics_1.NodeMetricsSupport),
|
|
81
|
+
logger: quiet,
|
|
82
|
+
intervalMs: 50,
|
|
83
|
+
});
|
|
70
84
|
/** The hub's sampler service: this hub's own phones are none, it asks the node every 50 ms. */
|
|
71
85
|
class HubSideMetrics extends SessionMetricsService_1.SessionMetricsService {
|
|
72
86
|
constructor() {
|
|
@@ -74,11 +88,7 @@ class HubSideMetrics extends SessionMetricsService_1.SessionMetricsService {
|
|
|
74
88
|
this.written = [];
|
|
75
89
|
}
|
|
76
90
|
context() {
|
|
77
|
-
return
|
|
78
|
-
pluginArgs: Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { bindHostOrIp: '127.0.0.1' }),
|
|
79
|
-
port: 4799,
|
|
80
|
-
nodeId: 'hub-1',
|
|
81
|
-
};
|
|
91
|
+
return hubContext();
|
|
82
92
|
}
|
|
83
93
|
writeSamples(_id, samples) {
|
|
84
94
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -86,16 +96,29 @@ class HubSideMetrics extends SessionMetricsService_1.SessionMetricsService {
|
|
|
86
96
|
});
|
|
87
97
|
}
|
|
88
98
|
collectorFor(source, hooks, after) {
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
return fastCollector(source, hooks, after);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** The same, writing to the hub's database every 100 ms, as the page reads it. */
|
|
103
|
+
class HubStoringMetrics extends SessionMetricsService_1.SessionMetricsService {
|
|
104
|
+
context() {
|
|
105
|
+
return hubContext();
|
|
106
|
+
}
|
|
107
|
+
flushIntervalMs() {
|
|
108
|
+
return 100;
|
|
109
|
+
}
|
|
110
|
+
collectorFor(source, hooks, after) {
|
|
111
|
+
return fastCollector(source, hooks, after);
|
|
97
112
|
}
|
|
98
113
|
}
|
|
114
|
+
const untilAsync = (ok_1, ...args_1) => __awaiter(void 0, [ok_1, ...args_1], void 0, function* (ok, ms = 5000) {
|
|
115
|
+
const end = Date.now() + ms;
|
|
116
|
+
while (!(yield ok())) {
|
|
117
|
+
if (Date.now() > end)
|
|
118
|
+
throw new Error('timed out');
|
|
119
|
+
yield new Promise((r) => setTimeout(r, 50));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
99
122
|
const until = (ok_1, ...args_1) => __awaiter(void 0, [ok_1, ...args_1], void 0, function* (ok, ms = 5000) {
|
|
100
123
|
const end = Date.now() + ms;
|
|
101
124
|
while (!ok()) {
|
|
@@ -310,6 +333,53 @@ describe('a hub collects a node session’s CPU and memory', function () {
|
|
|
310
333
|
yield hub.stop(sessionId);
|
|
311
334
|
(0, chai_1.expect)(hub.written).to.deep.equal([1000, 2000, 3000]);
|
|
312
335
|
}));
|
|
336
|
+
it("shows a node session's figures on the hub's own metrics route while it runs", () => __awaiter(this, void 0, void 0, function* () {
|
|
337
|
+
yield boot();
|
|
338
|
+
const sessionId = yield nodeSession();
|
|
339
|
+
nodeMetrics.hooks[0].onSample(at(1000));
|
|
340
|
+
nodeMetrics.hooks[0].onSample(at(2000));
|
|
341
|
+
// The hub's row for the session, as onSessionStarted writes it.
|
|
342
|
+
yield scratch.db.session.create({
|
|
343
|
+
data: {
|
|
344
|
+
id: sessionId,
|
|
345
|
+
device_udid: 'phone-1',
|
|
346
|
+
device_platform: 'android',
|
|
347
|
+
device_version: '14',
|
|
348
|
+
desired_capabilities: '{}',
|
|
349
|
+
session_capabilities: '{}',
|
|
350
|
+
node_id: NODE_ID,
|
|
351
|
+
has_live_video: false,
|
|
352
|
+
status: 'running',
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
const hub = new HubStoringMetrics();
|
|
356
|
+
const device = {
|
|
357
|
+
udid: 'phone-1',
|
|
358
|
+
host: nodeOrigin,
|
|
359
|
+
nodeId: NODE_ID,
|
|
360
|
+
platform: 'android',
|
|
361
|
+
realDevice: true,
|
|
362
|
+
};
|
|
363
|
+
hub.start({ sessionId, device, capabilities: {}, source: hubSide(sessionId) });
|
|
364
|
+
// The route asks the server's own service whether the session is sampled.
|
|
365
|
+
typedi_1.Container.set(SessionMetricsService_1.SessionMetricsService, hub);
|
|
366
|
+
try {
|
|
367
|
+
const page = (0, selector_health_fixture_1.selectorHealthApp)(selector_health_fixture_1.ADMIN);
|
|
368
|
+
let body;
|
|
369
|
+
yield untilAsync(() => __awaiter(this, void 0, void 0, function* () {
|
|
370
|
+
var _a;
|
|
371
|
+
body = (yield (0, loopbackRequest_1.default)(page).get(`/session/${sessionId}/metrics`)).body;
|
|
372
|
+
return ((_a = body === null || body === void 0 ? void 0 : body.samples) === null || _a === void 0 ? void 0 : _a.length) === 2;
|
|
373
|
+
}));
|
|
374
|
+
(0, chai_1.expect)(body.recording).to.equal('sampling');
|
|
375
|
+
(0, chai_1.expect)(body.samples.map((x) => x.t)).to.deep.equal([1000, 2000]);
|
|
376
|
+
(0, chai_1.expect)(body.samples[1]).to.include({ deviceCpu: 12, app: 'com.android.settings' });
|
|
377
|
+
}
|
|
378
|
+
finally {
|
|
379
|
+
typedi_1.Container.set(SessionMetricsService_1.SessionMetricsService, nodeMetrics);
|
|
380
|
+
yield hub.stop(sessionId);
|
|
381
|
+
}
|
|
382
|
+
}));
|
|
313
383
|
it("an older node's sessions say they aren't recorded", () => __awaiter(this, void 0, void 0, function* () {
|
|
314
384
|
yield boot();
|
|
315
385
|
const sessionId = yield nodeSession();
|
|
@@ -17,6 +17,8 @@ const chai_1 = require("chai");
|
|
|
17
17
|
const sinon_1 = __importDefault(require("sinon"));
|
|
18
18
|
const NodeMetricsCollector_1 = require("../../src/services/metrics/NodeMetricsCollector");
|
|
19
19
|
const nodeMetrics_1 = require("../../src/services/metrics/nodeMetrics");
|
|
20
|
+
const RemoteSession_1 = require("../../src/sessions/RemoteSession");
|
|
21
|
+
const CloudSession_1 = require("../../src/sessions/CloudSession");
|
|
20
22
|
const sample = (at) => ({
|
|
21
23
|
at,
|
|
22
24
|
deviceCpuPct: 5,
|
|
@@ -163,6 +165,16 @@ describe('NodeMetricsCollector: the hub collects a node session’s figures', ()
|
|
|
163
165
|
(0, chai_1.expect)(warned).to.have.length(2);
|
|
164
166
|
(0, chai_1.expect)(c.state()).to.equal('sampling');
|
|
165
167
|
}));
|
|
168
|
+
it("doesn't wait on a last ask when the node already wasn't answering", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
+
// A dark node's sessions are ended one after another by the heartbeat;
|
|
170
|
+
// each would wait up to 5 s more for an answer that won't come.
|
|
171
|
+
replies = [{ kind: 'unavailable', reason: 'ECONNREFUSED' }];
|
|
172
|
+
const c = make();
|
|
173
|
+
c.start();
|
|
174
|
+
yield clock.tickAsync(10000);
|
|
175
|
+
yield c.stop();
|
|
176
|
+
(0, chai_1.expect)(asked).to.have.length(1);
|
|
177
|
+
}));
|
|
166
178
|
it('finishes stopping when the node is unreachable', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
167
179
|
replies = [{ kind: 'unavailable', reason: 'timeout' }];
|
|
168
180
|
const c = make();
|
|
@@ -188,6 +200,27 @@ describe('NodeMetricsCollector: the hub collects a node session’s figures', ()
|
|
|
188
200
|
});
|
|
189
201
|
describe("readNodeMetricsReply: what a node's answer means", () => {
|
|
190
202
|
const h = { [nodeMetrics_1.NODE_METRICS_HEADER]: '1' };
|
|
203
|
+
it('keeps only well-formed samples: one bad sample would block every later write', () => {
|
|
204
|
+
const bad = [
|
|
205
|
+
null,
|
|
206
|
+
'x',
|
|
207
|
+
Object.assign(Object.assign({}, sample(5)), { at: '6' }),
|
|
208
|
+
Object.assign(Object.assign({}, sample(7)), { at: Number.NaN }),
|
|
209
|
+
Object.assign(Object.assign({}, sample(8)), { deviceCpuPct: '12' }),
|
|
210
|
+
Object.assign(Object.assign({}, sample(9)), { appId: 3 }),
|
|
211
|
+
];
|
|
212
|
+
const reply = (0, nodeMetrics_1.readNodeMetricsReply)(200, h, {
|
|
213
|
+
value: {
|
|
214
|
+
platform: 'android',
|
|
215
|
+
state: 'sampling',
|
|
216
|
+
samples: [sample(1), ...bad, Object.assign(Object.assign({}, sample(2)), { extra: 1 })],
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
(0, chai_1.expect)(reply).to.deep.equal({
|
|
220
|
+
kind: 'answer',
|
|
221
|
+
answer: { platform: 'android', state: 'sampling', samples: [sample(1), sample(2)] },
|
|
222
|
+
});
|
|
223
|
+
});
|
|
191
224
|
it('reads each answer', () => {
|
|
192
225
|
(0, chai_1.expect)((0, nodeMetrics_1.readNodeMetricsReply)(404, {}, undefined)).to.deep.equal({
|
|
193
226
|
kind: 'unsupported',
|
|
@@ -224,3 +257,23 @@ describe("readNodeMetricsReply: what a node's answer means", () => {
|
|
|
224
257
|
});
|
|
225
258
|
});
|
|
226
259
|
});
|
|
260
|
+
describe('nodeMetricsSourceOf: only a session a node runs is collected from', () => {
|
|
261
|
+
const options = (cloud) => ({
|
|
262
|
+
sessionId: 's1',
|
|
263
|
+
device: { udid: 'p1', host: 'http://node:4723', platform: 'android', cloud },
|
|
264
|
+
sessionResponse: {},
|
|
265
|
+
xenonOption: {},
|
|
266
|
+
baseUrl: 'http://node:4723/wd/hub',
|
|
267
|
+
});
|
|
268
|
+
it("takes a node's session, never a cloud provider's or this server's own", () => {
|
|
269
|
+
const remote = new RemoteSession_1.RemoteSession(options());
|
|
270
|
+
(0, chai_1.expect)((0, nodeMetrics_1.nodeMetricsSourceOf)(remote)).to.equal(remote);
|
|
271
|
+
// Both inherit nodeMetrics from RemoteSession; neither runs on a node.
|
|
272
|
+
(0, chai_1.expect)((0, nodeMetrics_1.nodeMetricsSourceOf)(new CloudSession_1.CloudSession(options('browserstack')))).to.equal(undefined);
|
|
273
|
+
const local = Object.assign(Object.create(RemoteSession_1.RemoteSession.prototype), {
|
|
274
|
+
getType: () => 'local',
|
|
275
|
+
});
|
|
276
|
+
(0, chai_1.expect)((0, nodeMetrics_1.nodeMetricsSourceOf)(local)).to.equal(undefined);
|
|
277
|
+
(0, chai_1.expect)((0, nodeMetrics_1.nodeMetricsSourceOf)({ nodeOrigin: () => 'x', nodeMetrics: () => __awaiter(void 0, void 0, void 0, function* () { return null; }) })).to.equal(undefined);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
@@ -76,7 +76,8 @@ describe("a node's session metrics route", () => {
|
|
|
76
76
|
it('reads anything but a time as no `after`', () => {
|
|
77
77
|
(0, chai_1.expect)((0, nodeSessionMetrics_1.parseAfter)('1500')).to.equal(1500);
|
|
78
78
|
(0, chai_1.expect)((0, nodeSessionMetrics_1.parseAfter)('1500.5')).to.equal(1500.5);
|
|
79
|
-
|
|
79
|
+
// A 400-digit number is Infinity: it would drop every sample the node holds.
|
|
80
|
+
for (const bad of [undefined, '', 'abc', '-5', '1e3', ['1'], '9'.repeat(400)]) {
|
|
80
81
|
(0, chai_1.expect)((0, nodeSessionMetrics_1.parseAfter)(bad), String(bad)).to.equal(null);
|
|
81
82
|
}
|
|
82
83
|
});
|
package/package.json
CHANGED