@crawlee/core 4.0.0-beta.87 → 4.0.0-beta.89

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.
@@ -1,62 +1,72 @@
1
- import ow from 'ow';
2
1
  import { evaluateLoadSignalSample } from './load_signal.js';
3
- import { Snapshotter } from './snapshotter.js';
4
- /** The four built-in signal names that map to typed `SystemInfo` fields. */
5
- const BUILTIN_SIGNAL_NAMES = new Set(['memInfo', 'eventLoopInfo', 'cpuInfo', 'clientInfo']);
6
2
  /**
7
- * Provides a simple interface to reading system status from a {@link Snapshotter} instance.
8
- * It only exposes two functions {@link SystemStatus.getCurrentStatus}
9
- * and {@link SystemStatus.getHistoricalStatus}.
10
- * The system status is calculated using a weighted average of overloaded
11
- * messages in the snapshots, with the weights being the time intervals
12
- * between the snapshots. Each resource is calculated separately
13
- * and the system is overloaded whenever at least one resource is overloaded.
14
- * The class is used by the {@link AutoscaledPool} class.
3
+ * How far back the *current* system status looks by default the window that gates task dispatch.
4
+ * @internal
5
+ */
6
+ export const DEFAULT_CURRENT_HISTORY_SECS = 5;
7
+ /**
8
+ * How far back the *historical* system status looks by default — the window autoscaling decisions are based on, and
9
+ * therefore how much history the signals are asked to retain.
10
+ * @internal
11
+ */
12
+ export const DEFAULT_SNAPSHOT_HISTORY_SECS = 30;
13
+ /** The four built-in signal names that map to typed `SystemInfo` fields, and the option that switches each off. */
14
+ const BUILTIN_SIGNAL_OPTION_KEYS = {
15
+ memInfo: 'memory',
16
+ eventLoopInfo: 'eventLoop',
17
+ cpuInfo: 'cpu',
18
+ clientInfo: 'client',
19
+ };
20
+ const BUILTIN_SIGNAL_NAMES = new Set(Object.keys(BUILTIN_SIGNAL_OPTION_KEYS));
21
+ /**
22
+ * Reads the overload verdict of every signal — the {@link Snapshotter}'s built-in four plus any custom ones — and
23
+ * combines them into a {@link SystemInfo}: each signal is a time-weighted average of its snapshots, and the system
24
+ * is overloaded whenever at least one of them is.
15
25
  *
16
- * {@link SystemStatus.getCurrentStatus}
17
- * returns a boolean that represents the current status of the system.
18
- * The length of the current timeframe in seconds is configurable
19
- * by the `currentHistorySecs` option and represents the max age
20
- * of snapshots to be considered for the calculation.
26
+ * Evaluated over two windows, both requested explicitly from every signal so that a signal's private retention cannot
27
+ * widen what it contributes: a short `currentHistorySecs` one ({@link SystemStatus.getCurrentStatus}, gating task
28
+ * dispatch) and a longer `historySecs` one ({@link SystemStatus.getHistoricalStatus}, driving autoscaling).
21
29
  *
22
- * {@link SystemStatus.getHistoricalStatus}
23
- * returns a boolean that represents the long-term status
24
- * of the system. It considers the full snapshot history available
25
- * in the {@link Snapshotter} instance.
26
- * @category Scaling
30
+ * An implementation detail of the {@link ConcurrencySystem}, configured through
31
+ * {@link ConcurrencySystemOptions}.
32
+ * @internal
27
33
  */
28
34
  export class SystemStatus {
29
35
  currentHistoryMillis;
30
- snapshotter;
36
+ historyMillis;
31
37
  signals;
38
+ constructor(options) {
39
+ const { currentHistorySecs = DEFAULT_CURRENT_HISTORY_SECS, historySecs = DEFAULT_SNAPSHOT_HISTORY_SECS, snapshotter, loadSignals = [], } = options;
40
+ this.currentHistoryMillis = currentHistorySecs * 1000;
41
+ this.historyMillis = historySecs * 1000;
42
+ this.signals = [...snapshotter.getLoadSignals(), ...loadSignals];
43
+ this.assertUniqueSignalNames();
44
+ }
32
45
  /**
33
- * Per-signal ratio overrides. The built-in four get their overrides from
34
- * the legacy `max*OverloadedRatio` options; custom signals use their own
35
- * `overloadedRatio`.
46
+ * The widest window any signal will be queried with, and therefore exactly how much history the signals are asked
47
+ * to retain when they start. Derived here, where the windows are resolved, so nothing has to reapply their
48
+ * defaults.
36
49
  */
37
- ratioOverrides;
38
- constructor(options = {}) {
39
- ow(options, ow.object.exactShape({
40
- currentHistorySecs: ow.optional.number,
41
- maxMemoryOverloadedRatio: ow.optional.number,
42
- maxEventLoopOverloadedRatio: ow.optional.number,
43
- maxCpuOverloadedRatio: ow.optional.number,
44
- maxClientOverloadedRatio: ow.optional.number,
45
- snapshotter: ow.optional.object,
46
- loadSignals: ow.optional.array,
47
- }));
48
- const { currentHistorySecs = 5, maxMemoryOverloadedRatio = 0.2, maxEventLoopOverloadedRatio = 0.6, maxCpuOverloadedRatio = 0.4, maxClientOverloadedRatio = 0.3, snapshotter, loadSignals = [], } = options;
49
- this.currentHistoryMillis = currentHistorySecs * 1000;
50
- this.snapshotter = snapshotter || new Snapshotter();
51
- // Built-in signals from the snapshotter + any custom signals
52
- this.signals = [...this.snapshotter.getLoadSignals(), ...loadSignals];
53
- // Allow legacy options to override the built-in signal ratios
54
- this.ratioOverrides = {
55
- memInfo: maxMemoryOverloadedRatio,
56
- eventLoopInfo: maxEventLoopOverloadedRatio,
57
- cpuInfo: maxCpuOverloadedRatio,
58
- clientInfo: maxClientOverloadedRatio,
59
- };
50
+ get maxSampleWindowMillis() {
51
+ return Math.max(this.currentHistoryMillis, this.historyMillis);
52
+ }
53
+ /**
54
+ * Signal names are the keys of the reported {@link SystemInfo}, so a duplicate would leave a status object that
55
+ * contradicts actual behavior: both signals are still evaluated (any overloaded one holds concurrency down), but
56
+ * only the last is reported.
57
+ */
58
+ assertUniqueSignalNames() {
59
+ const seen = new Set();
60
+ for (const { name } of this.signals) {
61
+ if (!seen.has(name)) {
62
+ seen.add(name);
63
+ continue;
64
+ }
65
+ const hint = BUILTIN_SIGNAL_NAMES.has(name)
66
+ ? `it is the name of a built-in signal. To replace that signal, switch it off with \`loadSignals: { ${BUILTIN_SIGNAL_OPTION_KEYS[name]}: false }\` and keep your implementation in \`loadSignals.custom\`; to run yours alongside it, give it a different name.`
67
+ : 'two custom signals cannot share a name - rename one of them.';
68
+ throw new Error(`Duplicate load signal name ${JSON.stringify(name)}: ${hint}`);
69
+ }
60
70
  }
61
71
  /**
62
72
  * Returns an {@link SystemInfo} object with the following structure:
@@ -89,12 +99,11 @@ export class SystemStatus {
89
99
  * }
90
100
  * ```
91
101
  *
92
- * Where the `isSystemIdle` property is set to `false` if the system
93
- * has been overloaded in the full history of the {@link Snapshotter}
94
- * (which is configurable in the {@link Snapshotter}) and `true` otherwise.
102
+ * Where the `isSystemIdle` property is set to `false` if the system has been overloaded within the last
103
+ * `historySecs` seconds and `true` otherwise.
95
104
  */
96
105
  getHistoricalStatus() {
97
- return this.isSystemIdle();
106
+ return this.isSystemIdle(this.historyMillis);
98
107
  }
99
108
  /**
100
109
  * Returns a system status object.
@@ -109,9 +118,8 @@ export class SystemStatus {
109
118
  };
110
119
  let loadSignalInfo;
111
120
  for (const signal of this.signals) {
112
- const ratio = this.ratioOverrides[signal.name] ?? signal.overloadedRatio;
113
121
  const sample = signal.getSample(sampleDurationMillis);
114
- const info = evaluateLoadSignalSample(sample, ratio);
122
+ const info = evaluateLoadSignalSample(sample, signal.overloadedRatio);
115
123
  if (info.isOverloaded) {
116
124
  result.isSystemIdle = false;
117
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/core",
3
- "version": "4.0.0-beta.87",
3
+ "version": "4.0.0-beta.89",
4
4
  "description": "The scalable web crawling and scraping library for JavaScript/Node.js. Enables development of data extraction and web automation jobs (not only) with headless Chrome and Puppeteer.",
5
5
  "engines": {
6
6
  "node": ">=22.0.0"
@@ -53,9 +53,9 @@
53
53
  "@apify/pseudo_url": "^2.0.59",
54
54
  "@apify/timeout": "^0.3.2",
55
55
  "@apify/utilities": "^2.15.5",
56
- "@crawlee/fs-storage": "4.0.0-beta.87",
57
- "@crawlee/types": "4.0.0-beta.87",
58
- "@crawlee/utils": "4.0.0-beta.87",
56
+ "@crawlee/fs-storage": "4.0.0-beta.89",
57
+ "@crawlee/types": "4.0.0-beta.89",
58
+ "@crawlee/utils": "4.0.0-beta.89",
59
59
  "@sapphire/async-queue": "^1.5.5",
60
60
  "@sapphire/shapeshift": "^4.0.0",
61
61
  "@vladfrangu/async_event_emitter": "^2.4.6",
@@ -79,5 +79,5 @@
79
79
  }
80
80
  }
81
81
  },
82
- "gitHead": "1b7604cd77b694460aac4448b4555444fa1b2bdb"
82
+ "gitHead": "9279941162eb4be0a9113f768b5bd79d27e66eba"
83
83
  }