@percy/core 1.30.7-beta.2 → 1.30.7-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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-beta.2",
3
+ "version": "1.30.7-beta.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -43,11 +43,12 @@
43
43
  "test:types": "tsd"
44
44
  },
45
45
  "dependencies": {
46
- "@percy/client": "1.30.7-beta.2",
47
- "@percy/config": "1.30.7-beta.2",
48
- "@percy/dom": "1.30.7-beta.2",
49
- "@percy/logger": "1.30.7-beta.2",
50
- "@percy/webdriver-utils": "1.30.7-beta.2",
46
+ "@percy/client": "1.30.7-beta.3",
47
+ "@percy/config": "1.30.7-beta.3",
48
+ "@percy/dom": "1.30.7-beta.3",
49
+ "@percy/logger": "1.30.7-beta.3",
50
+ "@percy/monitoring": "1.30.7-beta.3",
51
+ "@percy/webdriver-utils": "1.30.7-beta.3",
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": "01d95d0569e70d0291d36b3c5d8da224d3014ebf"
64
+ "gitHead": "06141a5d15278e9cb14550a96c745336acf8bafd"
64
65
  }