@percy/core 1.30.7-beta.2 → 1.30.7
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/discovery.js +6 -1
- package/dist/percy.js +95 -0
- package/package.json +9 -8
package/dist/discovery.js
CHANGED
|
@@ -401,7 +401,8 @@ export async function* discoverSnapshotResources(queue, options, callback) {
|
|
|
401
401
|
let {
|
|
402
402
|
snapshots,
|
|
403
403
|
skipDiscovery,
|
|
404
|
-
dryRun
|
|
404
|
+
dryRun,
|
|
405
|
+
checkAndUpdateConcurrency
|
|
405
406
|
} = options;
|
|
406
407
|
yield* yieldAll(snapshots.reduce((all, snapshot) => {
|
|
407
408
|
debugSnapshotOptions(snapshot);
|
|
@@ -415,6 +416,10 @@ export async function* discoverSnapshotResources(queue, options, callback) {
|
|
|
415
416
|
callback(dryRun ? snap : processSnapshotResources(snap));
|
|
416
417
|
}
|
|
417
418
|
} else {
|
|
419
|
+
// update concurrency before pushing new job in discovery queue
|
|
420
|
+
// if case of monitoring is stopped due to in-activity,
|
|
421
|
+
// it can take upto 1 sec to execute this fun
|
|
422
|
+
checkAndUpdateConcurrency();
|
|
418
423
|
all.push(queue.push(snapshot, callback));
|
|
419
424
|
}
|
|
420
425
|
return all;
|
package/dist/percy.js
CHANGED
|
@@ -17,9 +17,15 @@ import { base64encode, generatePromise, yieldAll, yieldTo, redactSecrets, detect
|
|
|
17
17
|
import { createPercyServer, createStaticServer } from './api.js';
|
|
18
18
|
import { gatherSnapshots, createSnapshotsQueue, validateSnapshotOptions } from './snapshot.js';
|
|
19
19
|
import { discoverSnapshotResources, createDiscoveryQueue } from './discovery.js';
|
|
20
|
+
import Monitoring from '@percy/monitoring';
|
|
20
21
|
import { WaitForJob } from './wait-for-job.js';
|
|
21
22
|
const MAX_SUGGESTION_CALLS = 10;
|
|
22
23
|
|
|
24
|
+
// If no activity is done for 5 mins, we will stop monitoring
|
|
25
|
+
// system metric eg: (cpu load && memory usage)
|
|
26
|
+
const MONITOR_ACTIVITY_TIMEOUT = 300000;
|
|
27
|
+
const MONITORING_INTERVAL_MS = 5000; // 5 sec
|
|
28
|
+
|
|
23
29
|
// A Percy instance will create a new build when started, handle snapshot creation, asset discovery,
|
|
24
30
|
// and resource uploads, and will finalize the build when stopped. Snapshots are processed
|
|
25
31
|
// concurrently and the build is not finalized until all snapshots have been handled.
|
|
@@ -100,7 +106,13 @@ export class Percy {
|
|
|
100
106
|
if (server) this.server = createPercyServer(this, port);
|
|
101
107
|
this.browser = new Browser(this);
|
|
102
108
|
_classPrivateFieldSet(_discovery, this, createDiscoveryQueue(this));
|
|
109
|
+
this.discoveryMaxConcurrency = _classPrivateFieldGet(_discovery, this).concurrency;
|
|
103
110
|
_classPrivateFieldSet(_snapshots, this, createSnapshotsQueue(this));
|
|
111
|
+
this.monitoring = new Monitoring();
|
|
112
|
+
// used continue monitoring if there is activity going on
|
|
113
|
+
// if there is none, stop it
|
|
114
|
+
this.resetMonitoringId = null;
|
|
115
|
+
this.monitoringCheckLastExecutedAt = null;
|
|
104
116
|
|
|
105
117
|
// generator methods are wrapped to autorun and return promises
|
|
106
118
|
for (let m of ['start', 'stop', 'flush', 'idle', 'snapshot', 'upload']) {
|
|
@@ -109,6 +121,28 @@ export class Percy {
|
|
|
109
121
|
this[m] = (...args) => generatePromise(method(...args));
|
|
110
122
|
}
|
|
111
123
|
}
|
|
124
|
+
systemMonitoringEnabled() {
|
|
125
|
+
return process.env.PERCY_DISABLE_SYSTEM_MONITORING !== 'true';
|
|
126
|
+
}
|
|
127
|
+
async configureSystemMonitor() {
|
|
128
|
+
await this.monitoring.startMonitoring({
|
|
129
|
+
interval: MONITORING_INTERVAL_MS
|
|
130
|
+
});
|
|
131
|
+
this.resetSystemMonitor();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Debouncing logic to only stop Monitoring system
|
|
135
|
+
// if there is no any activity for 5 mins
|
|
136
|
+
// means, no job is pushed in queue from 5 mins
|
|
137
|
+
resetSystemMonitor() {
|
|
138
|
+
if (this.resetMonitoringId) {
|
|
139
|
+
clearTimeout(this.resetMonitoringId);
|
|
140
|
+
this.resetMonitoringId = null;
|
|
141
|
+
}
|
|
142
|
+
this.resetMonitoringId = setTimeout(() => {
|
|
143
|
+
this.monitoring.stopMonitoring();
|
|
144
|
+
}, MONITOR_ACTIVITY_TIMEOUT);
|
|
145
|
+
}
|
|
112
146
|
|
|
113
147
|
// Shortcut for controlling the global logger's log level.
|
|
114
148
|
loglevel(level) {
|
|
@@ -169,6 +203,12 @@ export class Percy {
|
|
|
169
203
|
this.readyState = 0;
|
|
170
204
|
this.cliStartTime = new Date().toISOString();
|
|
171
205
|
try {
|
|
206
|
+
// started monitoring system metrics
|
|
207
|
+
|
|
208
|
+
if (this.systemMonitoringEnabled()) {
|
|
209
|
+
await this.configureSystemMonitor();
|
|
210
|
+
await this.monitoring.logSystemInfo();
|
|
211
|
+
}
|
|
172
212
|
if (process.env.PERCY_CLIENT_ERROR_LOGS !== 'false') {
|
|
173
213
|
this.log.warn('Notice: Percy collects CI logs to improve service and enhance your experience. These logs help us debug issues and provide insights on your dashboards, making it easier to optimize the product experience. Logs are stored securely for 30 days. You can opt out anytime with export PERCY_CLIENT_ERROR_LOGS=false, but keeping this enabled helps us offer the best support and features.');
|
|
174
214
|
}
|
|
@@ -296,12 +336,66 @@ export class Percy {
|
|
|
296
336
|
this.log.error(err);
|
|
297
337
|
throw err;
|
|
298
338
|
} finally {
|
|
339
|
+
// stop monitoring system metric, if not already stopped
|
|
340
|
+
this.monitoring.stopMonitoring();
|
|
341
|
+
clearTimeout(this.resetMonitoringId);
|
|
342
|
+
|
|
299
343
|
// This issue doesn't comes under regular error logs,
|
|
300
344
|
// it's detected if we just and stop percy server
|
|
301
345
|
await this.checkForNoSnapshotCommandError();
|
|
302
346
|
await this.sendBuildLogs();
|
|
303
347
|
}
|
|
304
348
|
}
|
|
349
|
+
checkAndUpdateConcurrency() {
|
|
350
|
+
// early exit if monitoring is disabled
|
|
351
|
+
if (!this.systemMonitoringEnabled()) return;
|
|
352
|
+
|
|
353
|
+
// early exit if asset discovery concurrency change is disabled
|
|
354
|
+
// NOTE: system monitoring will still be running as only concurrency
|
|
355
|
+
// change is disabled
|
|
356
|
+
if (process.env.PERCY_DISABLE_CONCURRENCY_CHANGE === 'true') return;
|
|
357
|
+
|
|
358
|
+
// start system monitoring if not already doing...
|
|
359
|
+
// this doesn't handle cases where there is suggest cpu spikes
|
|
360
|
+
// in less 1 sec range and if monitoring is not in running state
|
|
361
|
+
if (this.monitoringCheckLastExecutedAt && Date.now() - this.monitoringCheckLastExecutedAt < MONITORING_INTERVAL_MS) return;
|
|
362
|
+
if (!this.monitoring.running) this.configureSystemMonitor();else this.resetSystemMonitor();
|
|
363
|
+
|
|
364
|
+
// early return if last executed was less than 5 seconds
|
|
365
|
+
// as we will get the same cpu/mem info under 5 sec interval
|
|
366
|
+
const {
|
|
367
|
+
cpuInfo,
|
|
368
|
+
memoryUsageInfo
|
|
369
|
+
} = this.monitoring.getMonitoringInfo();
|
|
370
|
+
this.log.debug(`cpuInfo: ${JSON.stringify(cpuInfo)}`);
|
|
371
|
+
this.log.debug(`memoryInfo: ${JSON.stringify(memoryUsageInfo)}`);
|
|
372
|
+
if (cpuInfo.currentUsagePercent >= 80 || memoryUsageInfo.currentUsagePercent >= 80) {
|
|
373
|
+
let currentConcurrent = _classPrivateFieldGet(_discovery, this).concurrency;
|
|
374
|
+
|
|
375
|
+
// concurrency must be betweeen [1, (default/user defined value)]
|
|
376
|
+
let newConcurrency = Math.max(1, parseInt(currentConcurrent / 2));
|
|
377
|
+
newConcurrency = Math.min(this.discoveryMaxConcurrency, newConcurrency);
|
|
378
|
+
this.log.debug(`Downscaling discovery browser concurrency from ${_classPrivateFieldGet(_discovery, this).concurrency} to ${newConcurrency}`);
|
|
379
|
+
_classPrivateFieldGet(_discovery, this).set({
|
|
380
|
+
concurrency: newConcurrency
|
|
381
|
+
});
|
|
382
|
+
} else if (cpuInfo.currentUsagePercent <= 50 && memoryUsageInfo.currentUsagePercent <= 50) {
|
|
383
|
+
let currentConcurrent = _classPrivateFieldGet(_discovery, this).concurrency;
|
|
384
|
+
let newConcurrency = currentConcurrent + 2;
|
|
385
|
+
|
|
386
|
+
// concurrency must be betweeen [1, (default/user-defined value)]
|
|
387
|
+
newConcurrency = Math.min(this.discoveryMaxConcurrency, newConcurrency);
|
|
388
|
+
newConcurrency = Math.max(1, newConcurrency);
|
|
389
|
+
this.log.debug(`Upscaling discovery browser concurrency from ${_classPrivateFieldGet(_discovery, this).concurrency} to ${newConcurrency}`);
|
|
390
|
+
_classPrivateFieldGet(_discovery, this).set({
|
|
391
|
+
concurrency: newConcurrency
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// reset timeout to stop monitoring after no-activity of 5 mins
|
|
396
|
+
this.resetSystemMonitor();
|
|
397
|
+
this.monitoringCheckLastExecutedAt = Date.now();
|
|
398
|
+
}
|
|
305
399
|
|
|
306
400
|
// Takes one or more snapshots of a page while discovering resources to upload with the resulting
|
|
307
401
|
// snapshots. Once asset discovery has completed for the provided snapshots, the queued task will
|
|
@@ -354,6 +448,7 @@ export class Percy {
|
|
|
354
448
|
yield* discoverSnapshotResources(_classPrivateFieldGet(_discovery, this), {
|
|
355
449
|
skipDiscovery: this.skipDiscovery,
|
|
356
450
|
dryRun: this.dryRun,
|
|
451
|
+
checkAndUpdateConcurrency: this.checkAndUpdateConcurrency.bind(this),
|
|
357
452
|
snapshots: yield* gatherSnapshots(options, {
|
|
358
453
|
meta: {
|
|
359
454
|
build: this.build
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/core",
|
|
3
|
-
"version": "1.30.7
|
|
3
|
+
"version": "1.30.7",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public",
|
|
12
|
-
"tag": "
|
|
12
|
+
"tag": "latest"
|
|
13
13
|
},
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=14"
|
|
@@ -43,11 +43,12 @@
|
|
|
43
43
|
"test:types": "tsd"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@percy/client": "1.30.7
|
|
47
|
-
"@percy/config": "1.30.7
|
|
48
|
-
"@percy/dom": "1.30.7
|
|
49
|
-
"@percy/logger": "1.30.7
|
|
50
|
-
"@percy/
|
|
46
|
+
"@percy/client": "1.30.7",
|
|
47
|
+
"@percy/config": "1.30.7",
|
|
48
|
+
"@percy/dom": "1.30.7",
|
|
49
|
+
"@percy/logger": "1.30.7",
|
|
50
|
+
"@percy/monitoring": "1.30.7",
|
|
51
|
+
"@percy/webdriver-utils": "1.30.7",
|
|
51
52
|
"content-disposition": "^0.5.4",
|
|
52
53
|
"cross-spawn": "^7.0.3",
|
|
53
54
|
"extract-zip": "^2.0.1",
|
|
@@ -60,5 +61,5 @@
|
|
|
60
61
|
"ws": "^8.17.1",
|
|
61
62
|
"yaml": "^2.4.1"
|
|
62
63
|
},
|
|
63
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "a76ee38901384e71ef6d1be0e7aadd55f23ff003"
|
|
64
65
|
}
|