livedesk 0.1.457 → 0.1.458
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/client/package.json +5 -5
- package/diagnostics/livedesk-mac-physical-gate-core.mjs +389 -36
- package/diagnostics/livedesk-mode3-capture-smoke.mjs +140 -21
- package/hub/package.json +1 -1
- package/hub/src/remote-hub.js +28 -10
- package/package.json +6 -6
- package/web/dist/assets/index-JqqQ5DKN.js +25 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-Bv9NFUjC.js +0 -25
package/client/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.212",
|
|
4
4
|
"description": "LiveDesk local remote client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"ws": "^8.18.3"
|
|
41
41
|
},
|
|
42
42
|
"optionalDependencies": {
|
|
43
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
44
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
45
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
46
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
43
|
+
"@livedesk/fast-linux-x64": "0.1.416",
|
|
44
|
+
"@livedesk/fast-osx-arm64": "0.1.416",
|
|
45
|
+
"@livedesk/fast-osx-x64": "0.1.416",
|
|
46
|
+
"@livedesk/fast-win-x64": "0.1.416"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|
|
@@ -25,6 +25,36 @@ function parseMacProcessRecords(psOutput) {
|
|
|
25
25
|
return records;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function parseMacProcessResourceRecords(psOutput) {
|
|
29
|
+
const records = [];
|
|
30
|
+
for (const rawLine of String(psOutput || '').split(/\r?\n/)) {
|
|
31
|
+
const match = rawLine.match(/^\s*(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)$/);
|
|
32
|
+
if (!match) continue;
|
|
33
|
+
const pid = Number(match[1]);
|
|
34
|
+
const parentPid = Number(match[2]);
|
|
35
|
+
const cpuPercent = Number(match[4]);
|
|
36
|
+
const rssKiB = Number(match[5]);
|
|
37
|
+
if (
|
|
38
|
+
!Number.isInteger(pid)
|
|
39
|
+
|| pid <= 1
|
|
40
|
+
|| !Number.isInteger(parentPid)
|
|
41
|
+
|| !Number.isFinite(cpuPercent)
|
|
42
|
+
|| !Number.isFinite(rssKiB)
|
|
43
|
+
) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
records.push({
|
|
47
|
+
pid,
|
|
48
|
+
parentPid,
|
|
49
|
+
commandName: basenameWithoutQuotes(match[3]),
|
|
50
|
+
cpuPercent: Math.max(0, cpuPercent),
|
|
51
|
+
rssBytes: Math.max(0, Math.round(rssKiB * 1024)),
|
|
52
|
+
commandLine: String(match[6] || '').trim()
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return records;
|
|
56
|
+
}
|
|
57
|
+
|
|
28
58
|
function isMacCaptureHelperRecord(record) {
|
|
29
59
|
const commandName = String(record?.commandName || '');
|
|
30
60
|
const commandLine = normalizeProcessText(record?.commandLine);
|
|
@@ -46,6 +76,64 @@ function finite(value, fallback = 0) {
|
|
|
46
76
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
47
77
|
}
|
|
48
78
|
|
|
79
|
+
export function createAsyncSingleFlightSampler(sampleImpl) {
|
|
80
|
+
if (typeof sampleImpl !== 'function') {
|
|
81
|
+
throw new TypeError('An async sample function is required.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let inFlight = null;
|
|
85
|
+
let requestedCount = 0;
|
|
86
|
+
let startedCount = 0;
|
|
87
|
+
let skippedCount = 0;
|
|
88
|
+
|
|
89
|
+
const start = args => {
|
|
90
|
+
startedCount += 1;
|
|
91
|
+
const current = Promise.resolve().then(() => sampleImpl(...args));
|
|
92
|
+
const tracked = current.finally(() => {
|
|
93
|
+
if (inFlight === tracked) {
|
|
94
|
+
inFlight = null;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
inFlight = tracked;
|
|
98
|
+
return tracked;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
request(...args) {
|
|
103
|
+
requestedCount += 1;
|
|
104
|
+
if (inFlight) {
|
|
105
|
+
skippedCount += 1;
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return start(args);
|
|
109
|
+
},
|
|
110
|
+
async run(...args) {
|
|
111
|
+
requestedCount += 1;
|
|
112
|
+
while (inFlight) {
|
|
113
|
+
const pending = inFlight;
|
|
114
|
+
try {
|
|
115
|
+
await pending;
|
|
116
|
+
} catch {
|
|
117
|
+
// A required sample gets its own attempt after a failed periodic one.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return start(args);
|
|
121
|
+
},
|
|
122
|
+
async waitForIdle() {
|
|
123
|
+
if (!inFlight) return;
|
|
124
|
+
await inFlight;
|
|
125
|
+
},
|
|
126
|
+
getState() {
|
|
127
|
+
return {
|
|
128
|
+
inFlight: inFlight !== null,
|
|
129
|
+
requestedCount,
|
|
130
|
+
startedCount,
|
|
131
|
+
skippedCount
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
49
137
|
function basenameWithoutQuotes(value) {
|
|
50
138
|
return path.basename(String(value || '').trim().replace(/^["']|["']$/g, ''));
|
|
51
139
|
}
|
|
@@ -54,47 +142,56 @@ export function parseMacHelperProcesses(psOutput) {
|
|
|
54
142
|
return parseMacProcessRecords(psOutput).filter(isMacCaptureHelperRecord);
|
|
55
143
|
}
|
|
56
144
|
|
|
57
|
-
|
|
145
|
+
function classifyMacLiveDeskRuntimeRecord(record) {
|
|
146
|
+
const commandName = String(record?.commandName || '');
|
|
147
|
+
const commandLine = normalizeProcessText(record?.commandLine);
|
|
148
|
+
if (isMacCaptureHelperRecord(record)) {
|
|
149
|
+
return 'capture-helper';
|
|
150
|
+
}
|
|
151
|
+
if (
|
|
152
|
+
/^livedesk-client-fast$/i.test(commandName)
|
|
153
|
+
|| /(?:^|\/|[\s"'])livedesk-client-fast(?:\.dll)?(?:\s|$|["'])/i.test(commandLine)
|
|
154
|
+
) {
|
|
155
|
+
return 'client-agent';
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
/(?:node_modules\/(?:livedesk\/client|@livedesk\/client)|packages\/(?:livedesk\/client|client))\/bin\/livedesk-client(?:-node)?\.js(?:\s|$|["'])/i
|
|
159
|
+
.test(commandLine)
|
|
160
|
+
) {
|
|
161
|
+
return 'client-runtime';
|
|
162
|
+
}
|
|
163
|
+
if (
|
|
164
|
+
/(?:node_modules\/livedesk|packages\/livedesk)\/bin\/livedesk\.js(?:\s|$|["'])/i
|
|
165
|
+
.test(commandLine)
|
|
166
|
+
|| /(?:^|[\s"'])[^"'\s]*node_modules\/\.bin\/livedesk(?:\s|$|["'])/i
|
|
167
|
+
.test(commandLine)
|
|
168
|
+
) {
|
|
169
|
+
return 'unified-runtime';
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
/(?:node_modules\/(?:livedesk\/hub|@livedesk\/hub)|packages\/(?:livedesk\/hub|hub))\/src\/server\.js(?:\s|$|["'])/i
|
|
173
|
+
.test(commandLine)
|
|
174
|
+
) {
|
|
175
|
+
return 'hub-runtime';
|
|
176
|
+
}
|
|
177
|
+
if (
|
|
178
|
+
/(?:^|\/)LiveDesk\.app\/Contents\/MacOS\/LiveDesk(?:\s|$|["'])/i.test(commandLine)
|
|
179
|
+
) {
|
|
180
|
+
return 'desktop-runtime';
|
|
181
|
+
}
|
|
182
|
+
return '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function identifyMacLiveDeskRuntimeRecords(records, { ignorePids = [] } = {}) {
|
|
58
186
|
const ignored = new Set(
|
|
59
187
|
(Array.isArray(ignorePids) ? ignorePids : [])
|
|
60
188
|
.map(Number)
|
|
61
189
|
.filter(pid => Number.isInteger(pid) && pid > 1)
|
|
62
190
|
);
|
|
63
191
|
const results = [];
|
|
64
|
-
for (const record of
|
|
192
|
+
for (const record of records) {
|
|
65
193
|
if (ignored.has(record.pid)) continue;
|
|
66
|
-
const
|
|
67
|
-
const commandLine = normalizeProcessText(record.commandLine);
|
|
68
|
-
let kind = '';
|
|
69
|
-
if (isMacCaptureHelperRecord(record)) {
|
|
70
|
-
kind = 'capture-helper';
|
|
71
|
-
} else if (
|
|
72
|
-
/^livedesk-client-fast$/i.test(commandName)
|
|
73
|
-
|| /(?:^|\/|[\s"'])livedesk-client-fast(?:\.dll)?(?:\s|$|["'])/i.test(commandLine)
|
|
74
|
-
) {
|
|
75
|
-
kind = 'client-agent';
|
|
76
|
-
} else if (
|
|
77
|
-
/(?:node_modules\/(?:livedesk\/client|@livedesk\/client)|packages\/(?:livedesk\/client|client))\/bin\/livedesk-client(?:-node)?\.js(?:\s|$|["'])/i
|
|
78
|
-
.test(commandLine)
|
|
79
|
-
) {
|
|
80
|
-
kind = 'client-runtime';
|
|
81
|
-
} else if (
|
|
82
|
-
/(?:node_modules\/livedesk|packages\/livedesk)\/bin\/livedesk\.js(?:\s|$|["'])/i
|
|
83
|
-
.test(commandLine)
|
|
84
|
-
|| /(?:^|[\s"'])[^"'\s]*node_modules\/\.bin\/livedesk(?:\s|$|["'])/i
|
|
85
|
-
.test(commandLine)
|
|
86
|
-
) {
|
|
87
|
-
kind = 'unified-runtime';
|
|
88
|
-
} else if (
|
|
89
|
-
/(?:node_modules\/(?:livedesk\/hub|@livedesk\/hub)|packages\/(?:livedesk\/hub|hub))\/src\/server\.js(?:\s|$|["'])/i
|
|
90
|
-
.test(commandLine)
|
|
91
|
-
) {
|
|
92
|
-
kind = 'hub-runtime';
|
|
93
|
-
} else if (
|
|
94
|
-
/(?:^|\/)LiveDesk\.app\/Contents\/MacOS\/LiveDesk(?:\s|$|["'])/i.test(commandLine)
|
|
95
|
-
) {
|
|
96
|
-
kind = 'desktop-runtime';
|
|
97
|
-
}
|
|
194
|
+
const kind = classifyMacLiveDeskRuntimeRecord(record);
|
|
98
195
|
if (!kind) continue;
|
|
99
196
|
results.push({
|
|
100
197
|
...record,
|
|
@@ -104,6 +201,191 @@ export function parseMacLiveDeskRuntimeProcesses(psOutput, { ignorePids = [] } =
|
|
|
104
201
|
return results;
|
|
105
202
|
}
|
|
106
203
|
|
|
204
|
+
export function parseMacLiveDeskRuntimeProcesses(psOutput, options = {}) {
|
|
205
|
+
return identifyMacLiveDeskRuntimeRecords(parseMacProcessRecords(psOutput), options);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function hasAncestorPid(record, ancestorPid, recordsByPid) {
|
|
209
|
+
const expectedAncestor = Number(ancestorPid);
|
|
210
|
+
if (!Number.isInteger(expectedAncestor) || expectedAncestor <= 1) return false;
|
|
211
|
+
let parentPid = Number(record?.parentPid || 0);
|
|
212
|
+
const visited = new Set();
|
|
213
|
+
for (let depth = 0; depth < 16 && parentPid > 1 && !visited.has(parentPid); depth += 1) {
|
|
214
|
+
if (parentPid === expectedAncestor) return true;
|
|
215
|
+
visited.add(parentPid);
|
|
216
|
+
parentPid = Number(recordsByPid.get(parentPid)?.parentPid || 0);
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizedOwnedPids(values) {
|
|
222
|
+
return new Set(
|
|
223
|
+
(Array.isArray(values) ? values : [])
|
|
224
|
+
.map(Number)
|
|
225
|
+
.filter(pid => Number.isInteger(pid) && pid > 1)
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Returns a report-safe process sample. Command lines and executable paths are
|
|
231
|
+
* used only in-memory for strict LiveDesk/helper identity and never returned.
|
|
232
|
+
*/
|
|
233
|
+
export function buildMacDiagnosticResourceSample(
|
|
234
|
+
psOutput,
|
|
235
|
+
{
|
|
236
|
+
diagnosticPid = 0,
|
|
237
|
+
hubPid = 0,
|
|
238
|
+
agentPid = 0,
|
|
239
|
+
decoderPids = []
|
|
240
|
+
} = {}
|
|
241
|
+
) {
|
|
242
|
+
const records = parseMacProcessResourceRecords(psOutput);
|
|
243
|
+
const recordsByPid = new Map(records.map(record => [record.pid, record]));
|
|
244
|
+
const normalizedDiagnosticPid = Number(diagnosticPid);
|
|
245
|
+
const normalizedHubPid = Number(hubPid);
|
|
246
|
+
const normalizedAgentPid = Number(agentPid);
|
|
247
|
+
const ownedDecoderPids = normalizedOwnedPids(decoderPids);
|
|
248
|
+
const helpers = records.filter(isMacCaptureHelperRecord);
|
|
249
|
+
// The public `npx livedesk diagnose mac-physical` coordinator is itself
|
|
250
|
+
// classified as a unified LiveDesk runtime. Exclude only that exact,
|
|
251
|
+
// caller-owned PID from ownership/drain counts. Descendants and neighboring
|
|
252
|
+
// LiveDesk runtimes remain visible and therefore continue to fail closed.
|
|
253
|
+
const liveDeskRecords = identifyMacLiveDeskRuntimeRecords(records, {
|
|
254
|
+
ignorePids: [normalizedDiagnosticPid]
|
|
255
|
+
});
|
|
256
|
+
const hubRecord = recordsByPid.get(normalizedHubPid);
|
|
257
|
+
const agentRecord = recordsByPid.get(normalizedAgentPid);
|
|
258
|
+
const hubOwned = classifyMacLiveDeskRuntimeRecord(hubRecord) === 'hub-runtime';
|
|
259
|
+
const agentOwned = classifyMacLiveDeskRuntimeRecord(agentRecord) === 'client-agent';
|
|
260
|
+
const ownedLiveDeskPids = new Set();
|
|
261
|
+
if (hubOwned) ownedLiveDeskPids.add(normalizedHubPid);
|
|
262
|
+
if (agentOwned) ownedLiveDeskPids.add(normalizedAgentPid);
|
|
263
|
+
const helperOwnership = new Map();
|
|
264
|
+
for (const helper of helpers) {
|
|
265
|
+
const owned = agentOwned && hasAncestorPid(helper, normalizedAgentPid, recordsByPid);
|
|
266
|
+
helperOwnership.set(helper.pid, owned);
|
|
267
|
+
if (owned) ownedLiveDeskPids.add(helper.pid);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const resourceRecords = [];
|
|
271
|
+
const addResource = (record, role, ownedByDiagnostic) => {
|
|
272
|
+
if (!record) return;
|
|
273
|
+
resourceRecords.push({
|
|
274
|
+
role,
|
|
275
|
+
pid: record.pid,
|
|
276
|
+
ownedByDiagnostic: ownedByDiagnostic === true,
|
|
277
|
+
cpuPercent: Math.round(record.cpuPercent * 100) / 100,
|
|
278
|
+
rssBytes: record.rssBytes
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
addResource(hubRecord, 'hub', hubOwned);
|
|
282
|
+
addResource(agentRecord, 'remote-fast', agentOwned);
|
|
283
|
+
for (const decoderPid of [...ownedDecoderPids].sort((left, right) => left - right)) {
|
|
284
|
+
addResource(recordsByPid.get(decoderPid), 'decoder', true);
|
|
285
|
+
}
|
|
286
|
+
for (const helper of helpers.sort((left, right) => left.pid - right.pid)) {
|
|
287
|
+
addResource(helper, 'capture-helper', helperOwnership.get(helper.pid) === true);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
sampledAt: new Date().toISOString(),
|
|
292
|
+
totalLiveDeskProcessCount: liveDeskRecords.length,
|
|
293
|
+
captureHelperCount: helpers.length,
|
|
294
|
+
unownedLiveDeskPids: liveDeskRecords
|
|
295
|
+
.filter(record => !ownedLiveDeskPids.has(record.pid))
|
|
296
|
+
.map(record => record.pid)
|
|
297
|
+
.sort((left, right) => left - right),
|
|
298
|
+
processes: resourceRecords.sort((left, right) =>
|
|
299
|
+
left.role.localeCompare(right.role) || left.pid - right.pid
|
|
300
|
+
)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function percentile95(values) {
|
|
305
|
+
if (values.length === 0) return 0;
|
|
306
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
307
|
+
return sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function summarizeMacDiagnosticResourceSamples(
|
|
311
|
+
samples = [],
|
|
312
|
+
{ expectedProcesses = [] } = {}
|
|
313
|
+
) {
|
|
314
|
+
const normalizedSamples = (Array.isArray(samples) ? samples : [])
|
|
315
|
+
.filter(sample => sample && typeof sample === 'object');
|
|
316
|
+
const grouped = new Map();
|
|
317
|
+
for (const record of Array.isArray(expectedProcesses) ? expectedProcesses : []) {
|
|
318
|
+
const pid = Number(record?.pid);
|
|
319
|
+
const role = String(record?.role || '').trim();
|
|
320
|
+
if (!Number.isInteger(pid) || pid <= 1 || !role) continue;
|
|
321
|
+
grouped.set(`${role}:${pid}`, {
|
|
322
|
+
role,
|
|
323
|
+
pid,
|
|
324
|
+
ownedByDiagnostic: record?.ownedByDiagnostic === true,
|
|
325
|
+
cpuPercentSamples: [],
|
|
326
|
+
rssBytesPeak: 0
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
for (const sample of normalizedSamples) {
|
|
330
|
+
for (const record of Array.isArray(sample.processes) ? sample.processes : []) {
|
|
331
|
+
const pid = Number(record?.pid);
|
|
332
|
+
const role = String(record?.role || '').trim();
|
|
333
|
+
if (!Number.isInteger(pid) || pid <= 1 || !role) continue;
|
|
334
|
+
const key = `${role}:${pid}`;
|
|
335
|
+
let summary = grouped.get(key);
|
|
336
|
+
if (!summary) {
|
|
337
|
+
summary = {
|
|
338
|
+
role,
|
|
339
|
+
pid,
|
|
340
|
+
ownedByDiagnostic: true,
|
|
341
|
+
cpuPercentSamples: [],
|
|
342
|
+
rssBytesPeak: 0
|
|
343
|
+
};
|
|
344
|
+
grouped.set(key, summary);
|
|
345
|
+
}
|
|
346
|
+
summary.ownedByDiagnostic = summary.ownedByDiagnostic && record.ownedByDiagnostic === true;
|
|
347
|
+
summary.cpuPercentSamples.push(Math.max(0, finite(record.cpuPercent)));
|
|
348
|
+
summary.rssBytesPeak = Math.max(summary.rssBytesPeak, Math.max(0, finite(record.rssBytes)));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const processes = [...grouped.values()]
|
|
352
|
+
.map(summary => {
|
|
353
|
+
const cpuPercentSamples = summary.cpuPercentSamples
|
|
354
|
+
.slice(-120)
|
|
355
|
+
.map(value => Math.round(value * 100) / 100);
|
|
356
|
+
return {
|
|
357
|
+
role: summary.role,
|
|
358
|
+
pid: summary.pid,
|
|
359
|
+
ownedByDiagnostic: summary.ownedByDiagnostic,
|
|
360
|
+
sampleCount: summary.cpuPercentSamples.length,
|
|
361
|
+
cpuPercentSamples,
|
|
362
|
+
cpuPercentP95: Math.round(percentile95(summary.cpuPercentSamples) * 100) / 100,
|
|
363
|
+
cpuPercentPeak: Math.round(Math.max(0, ...summary.cpuPercentSamples) * 100) / 100,
|
|
364
|
+
rssBytesPeak: Math.round(summary.rssBytesPeak)
|
|
365
|
+
};
|
|
366
|
+
})
|
|
367
|
+
.sort((left, right) => left.role.localeCompare(right.role) || left.pid - right.pid);
|
|
368
|
+
const totalLiveDeskProcessCountSamples = normalizedSamples
|
|
369
|
+
.map(sample => Math.max(0, Math.round(finite(sample.totalLiveDeskProcessCount))));
|
|
370
|
+
const captureHelperCountSamples = normalizedSamples
|
|
371
|
+
.map(sample => Math.max(0, Math.round(finite(sample.captureHelperCount))));
|
|
372
|
+
const unownedLiveDeskPids = [...new Set(
|
|
373
|
+
normalizedSamples.flatMap(sample =>
|
|
374
|
+
(Array.isArray(sample.unownedLiveDeskPids) ? sample.unownedLiveDeskPids : [])
|
|
375
|
+
.map(Number)
|
|
376
|
+
.filter(pid => Number.isInteger(pid) && pid > 1)
|
|
377
|
+
)
|
|
378
|
+
)].sort((left, right) => left - right);
|
|
379
|
+
return {
|
|
380
|
+
sampleCount: normalizedSamples.length,
|
|
381
|
+
totalLiveDeskProcessCount: totalLiveDeskProcessCountSamples.at(-1) || 0,
|
|
382
|
+
totalLiveDeskProcessCountPeak: Math.max(0, ...totalLiveDeskProcessCountSamples),
|
|
383
|
+
captureHelperCountPeak: Math.max(0, ...captureHelperCountSamples),
|
|
384
|
+
unownedLiveDeskPids,
|
|
385
|
+
processes
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
107
389
|
const MODIFIERS = Object.freeze([
|
|
108
390
|
{ code: 'ShiftLeft', key: 'Shift', keyCode: 16, location: 1, flag: 'shiftKey' },
|
|
109
391
|
{ code: 'ControlLeft', key: 'Control', keyCode: 17, location: 1, flag: 'ctrlKey' },
|
|
@@ -243,6 +525,49 @@ export function classifyMacPhysicalGate(snapshot = {}) {
|
|
|
243
525
|
const helperPids = Array.isArray(helper.observedPids)
|
|
244
526
|
? helper.observedPids.map(Number).filter(pid => Number.isInteger(pid) && pid > 1)
|
|
245
527
|
: [];
|
|
528
|
+
const resources = snapshot.resources || {};
|
|
529
|
+
const baselineResources = resources.baseline || {};
|
|
530
|
+
const steadyResources = resources.steadyCapture || {};
|
|
531
|
+
const cleanupResources = resources.afterCleanup || {};
|
|
532
|
+
const steadyProcesses = Array.isArray(steadyResources.processes)
|
|
533
|
+
? steadyResources.processes
|
|
534
|
+
: [];
|
|
535
|
+
const sampledSteadyProcesses = steadyProcesses
|
|
536
|
+
.filter(processInfo => finite(processInfo?.sampleCount) > 0);
|
|
537
|
+
const sampledSteadyRoles = new Set(
|
|
538
|
+
sampledSteadyProcesses.map(processInfo => String(processInfo?.role || ''))
|
|
539
|
+
);
|
|
540
|
+
const resourceTelemetryAvailable = finite(baselineResources.sampleCount) > 0
|
|
541
|
+
&& finite(steadyResources.sampleCount) > 0
|
|
542
|
+
&& finite(cleanupResources.sampleCount) > 0;
|
|
543
|
+
const resourceOwnershipCurrent = finite(baselineResources.totalLiveDeskProcessCountPeak) === 0
|
|
544
|
+
&& sampledSteadyRoles.has('hub')
|
|
545
|
+
&& sampledSteadyRoles.has('remote-fast')
|
|
546
|
+
&& sampledSteadyRoles.has('capture-helper')
|
|
547
|
+
&& sampledSteadyProcesses.every(processInfo => processInfo?.ownedByDiagnostic === true)
|
|
548
|
+
&& (!Array.isArray(steadyResources.unownedLiveDeskPids)
|
|
549
|
+
|| steadyResources.unownedLiveDeskPids.length === 0);
|
|
550
|
+
const resourceDrained = finite(cleanupResources.totalLiveDeskProcessCount) === 0
|
|
551
|
+
&& finite(cleanupResources.totalLiveDeskProcessCountPeak) === 0
|
|
552
|
+
&& (!Array.isArray(cleanupResources.processes) || cleanupResources.processes.length === 0)
|
|
553
|
+
&& (!Array.isArray(cleanupResources.unownedLiveDeskPids)
|
|
554
|
+
|| cleanupResources.unownedLiveDeskPids.length === 0);
|
|
555
|
+
const resourceHelperPeak = Math.max(
|
|
556
|
+
finite(helper.maxConcurrent),
|
|
557
|
+
finite(steadyResources.captureHelperCountPeak)
|
|
558
|
+
);
|
|
559
|
+
const highCpuProcesses = steadyProcesses
|
|
560
|
+
.filter(processInfo =>
|
|
561
|
+
finite(processInfo?.cpuPercentP95) >= 80
|
|
562
|
+
|| finite(processInfo?.cpuPercentPeak) >= 100
|
|
563
|
+
)
|
|
564
|
+
.map(processInfo => ({
|
|
565
|
+
role: String(processInfo?.role || ''),
|
|
566
|
+
pid: Math.max(0, Math.round(finite(processInfo?.pid))),
|
|
567
|
+
cpuPercentP95: Math.max(0, finite(processInfo?.cpuPercentP95)),
|
|
568
|
+
cpuPercentPeak: Math.max(0, finite(processInfo?.cpuPercentPeak)),
|
|
569
|
+
rssBytesPeak: Math.max(0, Math.round(finite(processInfo?.rssBytesPeak)))
|
|
570
|
+
}));
|
|
246
571
|
const expectedModifierAcks = Math.max(1, finite(modifier.expectedAcks, 8));
|
|
247
572
|
const checks = [
|
|
248
573
|
check(
|
|
@@ -301,10 +626,30 @@ export function classifyMacPhysicalGate(snapshot = {}) {
|
|
|
301
626
|
),
|
|
302
627
|
check(
|
|
303
628
|
'single-helper',
|
|
304
|
-
|
|
305
|
-
`max=${
|
|
629
|
+
resourceHelperPeak <= 1,
|
|
630
|
+
`max=${resourceHelperPeak} pids=${helperPids.join(',') || '-'}`
|
|
306
631
|
),
|
|
307
632
|
check('helper-drained', finite(helper.finalCount) === 0, `final=${finite(helper.finalCount)}`),
|
|
633
|
+
check(
|
|
634
|
+
'process-telemetry',
|
|
635
|
+
resourceTelemetryAvailable,
|
|
636
|
+
resourceTelemetryAvailable
|
|
637
|
+
? `baseline=${finite(baselineResources.sampleCount)} steady=${finite(steadyResources.sampleCount)} cleanup=${finite(cleanupResources.sampleCount)}`
|
|
638
|
+
: 'baseline, steady-capture, and after-cleanup samples are required'
|
|
639
|
+
),
|
|
640
|
+
check(
|
|
641
|
+
'resource-ownership',
|
|
642
|
+
resourceTelemetryAvailable && resourceOwnershipCurrent,
|
|
643
|
+
`baseline=${finite(baselineResources.totalLiveDeskProcessCountPeak)} `
|
|
644
|
+
+ `steady=${finite(steadyResources.totalLiveDeskProcessCountPeak)} `
|
|
645
|
+
+ `unowned=${Array.isArray(steadyResources.unownedLiveDeskPids) ? steadyResources.unownedLiveDeskPids.join(',') || '-' : '-'}`
|
|
646
|
+
),
|
|
647
|
+
check(
|
|
648
|
+
'resource-drained',
|
|
649
|
+
resourceTelemetryAvailable && resourceDrained,
|
|
650
|
+
`final=${finite(cleanupResources.totalLiveDeskProcessCount)} `
|
|
651
|
+
+ `peak=${finite(cleanupResources.totalLiveDeskProcessCountPeak)}`
|
|
652
|
+
),
|
|
308
653
|
check('two-monitors', finite(monitor.count) >= 2, `count=${finite(monitor.count)}`),
|
|
309
654
|
check(
|
|
310
655
|
'monitor-generation',
|
|
@@ -350,6 +695,9 @@ export function classifyMacPhysicalGate(snapshot = {}) {
|
|
|
350
695
|
'delivered-fps': 'agent-or-transport',
|
|
351
696
|
'single-helper': 'duplicate-helper',
|
|
352
697
|
'helper-drained': 'stale-helper',
|
|
698
|
+
'process-telemetry': 'process-telemetry',
|
|
699
|
+
'resource-ownership': 'resource-ownership',
|
|
700
|
+
'resource-drained': 'resource-not-drained',
|
|
353
701
|
'two-monitors': 'monitor-topology',
|
|
354
702
|
'monitor-generation': 'monitor-generation',
|
|
355
703
|
'monitor-pixels': 'monitor-binding',
|
|
@@ -360,6 +708,11 @@ export function classifyMacPhysicalGate(snapshot = {}) {
|
|
|
360
708
|
ok: failed.length === 0,
|
|
361
709
|
idleOptimized,
|
|
362
710
|
bottleneck: failed.length > 0 ? bottleneckByCheck[failed[0].id] || failed[0].id : 'none',
|
|
711
|
+
processTelemetry: {
|
|
712
|
+
available: resourceTelemetryAvailable,
|
|
713
|
+
highLoad: highCpuProcesses.length > 0,
|
|
714
|
+
highLoadProcesses: highCpuProcesses
|
|
715
|
+
},
|
|
363
716
|
checks
|
|
364
717
|
};
|
|
365
718
|
}
|