@crawlee/core 4.0.0-beta.13 → 4.0.0-beta.131
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/README.md +17 -13
- package/autoscaling/autoscaled_pool.d.ts +67 -172
- package/autoscaling/autoscaled_pool.js +182 -329
- package/autoscaling/concurrency_system.d.ts +268 -0
- package/autoscaling/concurrency_system.js +362 -0
- package/autoscaling/cpu_load_signal.d.ts +43 -0
- package/autoscaling/cpu_load_signal.js +47 -0
- package/autoscaling/event_loop_load_signal.d.ts +51 -0
- package/autoscaling/event_loop_load_signal.js +60 -0
- package/autoscaling/index.d.ts +6 -1
- package/autoscaling/index.js +6 -1
- package/autoscaling/load_signal.d.ts +100 -0
- package/autoscaling/load_signal.js +105 -0
- package/autoscaling/memory_load_signal.d.ts +47 -0
- package/autoscaling/memory_load_signal.js +106 -0
- package/autoscaling/snapshotter.d.ts +58 -163
- package/autoscaling/snapshotter.js +45 -263
- package/autoscaling/storage_backend_load_signal.d.ts +56 -0
- package/autoscaling/storage_backend_load_signal.js +73 -0
- package/autoscaling/system_status.d.ts +67 -89
- package/autoscaling/system_status.js +92 -122
- package/autoscaling/weighted_avg.d.ts +5 -0
- package/autoscaling/weighted_avg.js +14 -0
- package/byte_utils.d.ts +17 -0
- package/byte_utils.js +42 -0
- package/configuration.d.ts +96 -223
- package/configuration.js +170 -222
- package/cookie_utils.d.ts +3 -2
- package/cookie_utils.js +18 -7
- package/crawlers/context_pipeline.d.ts +10 -1
- package/crawlers/context_pipeline.js +31 -8
- package/crawlers/crawler_commons.d.ts +90 -129
- package/crawlers/crawler_commons.js +1 -116
- package/crawlers/error_snapshotter.d.ts +2 -5
- package/crawlers/error_snapshotter.js +7 -8
- package/crawlers/error_tracker.d.ts +0 -1
- package/crawlers/error_tracker.js +0 -1
- package/crawlers/index.d.ts +1 -3
- package/crawlers/index.js +0 -3
- package/crawlers/internals/types.d.ts +0 -1
- package/crawlers/internals/types.js +0 -1
- package/crawlers/statistics.d.ts +187 -63
- package/crawlers/statistics.js +354 -164
- package/debug.d.ts +36 -0
- package/debug.js +70 -0
- package/enqueue_links/enqueue_links.d.ts +68 -154
- package/enqueue_links/enqueue_links.js +38 -210
- package/enqueue_links/index.d.ts +0 -1
- package/enqueue_links/index.js +0 -1
- package/enqueue_links/shared.d.ts +52 -26
- package/enqueue_links/shared.js +115 -66
- package/errors.d.ts +72 -4
- package/errors.js +89 -5
- package/events/event_manager.d.ts +35 -9
- package/events/event_manager.js +12 -13
- package/events/index.d.ts +0 -1
- package/events/index.js +0 -1
- package/events/local_event_manager.d.ts +15 -3
- package/events/local_event_manager.js +39 -13
- package/http.d.ts +9 -0
- package/http.js +28 -0
- package/index.d.ts +7 -4
- package/index.js +6 -3
- package/iterables.d.ts +79 -0
- package/iterables.js +134 -0
- package/log.d.ts +82 -3
- package/log.js +106 -1
- package/memory-storage/consts.d.ts +4 -0
- package/memory-storage/consts.js +4 -0
- package/memory-storage/index.d.ts +1 -0
- package/memory-storage/index.js +1 -0
- package/memory-storage/memory-storage.d.ts +38 -0
- package/memory-storage/memory-storage.js +130 -0
- package/memory-storage/resource-clients/common/base-client.d.ts +4 -0
- package/memory-storage/resource-clients/common/base-client.js +6 -0
- package/memory-storage/resource-clients/dataset.d.ts +40 -0
- package/memory-storage/resource-clients/dataset.js +108 -0
- package/memory-storage/resource-clients/key-value-store.d.ts +63 -0
- package/memory-storage/resource-clients/key-value-store.js +201 -0
- package/memory-storage/resource-clients/request-queue.d.ts +77 -0
- package/memory-storage/resource-clients/request-queue.js +409 -0
- package/memory-storage/utils.d.ts +16 -0
- package/memory-storage/utils.js +41 -0
- package/owned_or_injected.d.ts +58 -0
- package/owned_or_injected.js +98 -0
- package/package.json +14 -13
- package/proxy_configuration.d.ts +24 -132
- package/proxy_configuration.js +32 -147
- package/recoverable_state.d.ts +155 -0
- package/recoverable_state.js +231 -0
- package/request.d.ts +80 -16
- package/request.js +147 -68
- package/router.d.ts +193 -21
- package/router.js +188 -43
- package/serialization.d.ts +0 -1
- package/serialization.js +15 -15
- package/service_locator.d.ts +165 -0
- package/service_locator.js +253 -0
- package/session_pool/consts.d.ts +1 -2
- package/session_pool/consts.js +1 -2
- package/session_pool/errors.d.ts +0 -1
- package/session_pool/errors.js +0 -1
- package/session_pool/fingerprint.d.ts +9 -0
- package/session_pool/fingerprint.js +30 -0
- package/session_pool/index.d.ts +0 -2
- package/session_pool/index.js +0 -2
- package/session_pool/session.d.ts +35 -88
- package/session_pool/session.js +101 -159
- package/session_pool/session_pool.d.ts +73 -91
- package/session_pool/session_pool.js +172 -165
- package/storages/batched_adds.d.ts +37 -0
- package/storages/batched_adds.js +73 -0
- package/storages/dataset.d.ts +114 -54
- package/storages/dataset.js +291 -148
- package/storages/index.d.ts +10 -8
- package/storages/index.js +8 -8
- package/storages/key_value_store.d.ts +185 -42
- package/storages/key_value_store.js +445 -169
- package/storages/key_value_store_codec.d.ts +32 -0
- package/storages/key_value_store_codec.js +113 -0
- package/storages/request_dedup_cache.d.ts +22 -0
- package/storages/request_dedup_cache.js +48 -0
- package/storages/request_list.d.ts +52 -115
- package/storages/request_list.js +183 -152
- package/storages/request_loader.d.ts +101 -0
- package/storages/request_loader.js +1 -0
- package/storages/request_manager.d.ts +33 -0
- package/storages/request_manager.js +1 -0
- package/storages/request_manager_tandem.d.ts +97 -0
- package/storages/request_manager_tandem.js +197 -0
- package/storages/request_queue.d.ts +290 -47
- package/storages/request_queue.js +762 -216
- package/storages/{sitemap_request_list.d.ts → sitemap_request_loader.d.ts} +45 -89
- package/storages/sitemap_request_loader.js +438 -0
- package/storages/storage_instance_manager.d.ts +87 -0
- package/storages/storage_instance_manager.js +256 -0
- package/storages/storage_stats.d.ts +48 -0
- package/storages/storage_stats.js +29 -0
- package/storages/throttling_request_manager.d.ts +239 -0
- package/storages/throttling_request_manager.js +646 -0
- package/storages/transaction.d.ts +252 -0
- package/storages/transaction.js +251 -0
- package/storages/utils.d.ts +59 -11
- package/storages/utils.js +75 -15
- package/system-info/cpu-info.d.ts +67 -0
- package/system-info/cpu-info.js +216 -0
- package/system-info/memory-info.d.ts +31 -0
- package/system-info/memory-info.js +115 -0
- package/system-info/ps-tree.d.ts +17 -0
- package/system-info/ps-tree.js +144 -0
- package/system-info/runtime.d.ts +14 -0
- package/system-info/runtime.js +80 -0
- package/typedefs.d.ts +0 -6
- package/typedefs.js +0 -1
- package/url.d.ts +9 -0
- package/url.js +11 -0
- package/validators.d.ts +22 -18
- package/validators.js +13 -18
- package/autoscaling/autoscaled_pool.d.ts.map +0 -1
- package/autoscaling/autoscaled_pool.js.map +0 -1
- package/autoscaling/index.d.ts.map +0 -1
- package/autoscaling/index.js.map +0 -1
- package/autoscaling/snapshotter.d.ts.map +0 -1
- package/autoscaling/snapshotter.js.map +0 -1
- package/autoscaling/system_status.d.ts.map +0 -1
- package/autoscaling/system_status.js.map +0 -1
- package/configuration.d.ts.map +0 -1
- package/configuration.js.map +0 -1
- package/cookie_utils.d.ts.map +0 -1
- package/cookie_utils.js.map +0 -1
- package/crawlers/context_pipeline.d.ts.map +0 -1
- package/crawlers/context_pipeline.js.map +0 -1
- package/crawlers/crawler_commons.d.ts.map +0 -1
- package/crawlers/crawler_commons.js.map +0 -1
- package/crawlers/crawler_utils.d.ts +0 -10
- package/crawlers/crawler_utils.d.ts.map +0 -1
- package/crawlers/crawler_utils.js +0 -12
- package/crawlers/crawler_utils.js.map +0 -1
- package/crawlers/error_snapshotter.d.ts.map +0 -1
- package/crawlers/error_snapshotter.js.map +0 -1
- package/crawlers/error_tracker.d.ts.map +0 -1
- package/crawlers/error_tracker.js.map +0 -1
- package/crawlers/index.d.ts.map +0 -1
- package/crawlers/index.js.map +0 -1
- package/crawlers/internals/types.d.ts.map +0 -1
- package/crawlers/internals/types.js.map +0 -1
- package/crawlers/statistics.d.ts.map +0 -1
- package/crawlers/statistics.js.map +0 -1
- package/enqueue_links/enqueue_links.d.ts.map +0 -1
- package/enqueue_links/enqueue_links.js.map +0 -1
- package/enqueue_links/index.d.ts.map +0 -1
- package/enqueue_links/index.js.map +0 -1
- package/enqueue_links/shared.d.ts.map +0 -1
- package/enqueue_links/shared.js.map +0 -1
- package/errors.d.ts.map +0 -1
- package/errors.js.map +0 -1
- package/events/event_manager.d.ts.map +0 -1
- package/events/event_manager.js.map +0 -1
- package/events/index.d.ts.map +0 -1
- package/events/index.js.map +0 -1
- package/events/local_event_manager.d.ts.map +0 -1
- package/events/local_event_manager.js.map +0 -1
- package/http_clients/base-http-client.d.ts +0 -140
- package/http_clients/base-http-client.d.ts.map +0 -1
- package/http_clients/base-http-client.js +0 -40
- package/http_clients/base-http-client.js.map +0 -1
- package/http_clients/form-data-like.d.ts +0 -67
- package/http_clients/form-data-like.d.ts.map +0 -1
- package/http_clients/form-data-like.js +0 -5
- package/http_clients/form-data-like.js.map +0 -1
- package/http_clients/got-scraping-http-client.d.ts +0 -20
- package/http_clients/got-scraping-http-client.d.ts.map +0 -1
- package/http_clients/got-scraping-http-client.js +0 -82
- package/http_clients/got-scraping-http-client.js.map +0 -1
- package/http_clients/index.d.ts +0 -3
- package/http_clients/index.d.ts.map +0 -1
- package/http_clients/index.js +0 -3
- package/http_clients/index.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/log.d.ts.map +0 -1
- package/log.js.map +0 -1
- package/proxy_configuration.d.ts.map +0 -1
- package/proxy_configuration.js.map +0 -1
- package/request.d.ts.map +0 -1
- package/request.js.map +0 -1
- package/router.d.ts.map +0 -1
- package/router.js.map +0 -1
- package/serialization.d.ts.map +0 -1
- package/serialization.js.map +0 -1
- package/session_pool/consts.d.ts.map +0 -1
- package/session_pool/consts.js.map +0 -1
- package/session_pool/errors.d.ts.map +0 -1
- package/session_pool/errors.js.map +0 -1
- package/session_pool/events.d.ts +0 -3
- package/session_pool/events.d.ts.map +0 -1
- package/session_pool/events.js +0 -3
- package/session_pool/events.js.map +0 -1
- package/session_pool/index.d.ts.map +0 -1
- package/session_pool/index.js.map +0 -1
- package/session_pool/session.d.ts.map +0 -1
- package/session_pool/session.js.map +0 -1
- package/session_pool/session_pool.d.ts.map +0 -1
- package/session_pool/session_pool.js.map +0 -1
- package/storages/access_checking.d.ts +0 -13
- package/storages/access_checking.d.ts.map +0 -1
- package/storages/access_checking.js +0 -14
- package/storages/access_checking.js.map +0 -1
- package/storages/dataset.d.ts.map +0 -1
- package/storages/dataset.js.map +0 -1
- package/storages/index.d.ts.map +0 -1
- package/storages/index.js.map +0 -1
- package/storages/key_value_store.d.ts.map +0 -1
- package/storages/key_value_store.js.map +0 -1
- package/storages/request_list.d.ts.map +0 -1
- package/storages/request_list.js.map +0 -1
- package/storages/request_provider.d.ts +0 -308
- package/storages/request_provider.d.ts.map +0 -1
- package/storages/request_provider.js +0 -555
- package/storages/request_provider.js.map +0 -1
- package/storages/request_queue.d.ts.map +0 -1
- package/storages/request_queue.js.map +0 -1
- package/storages/request_queue_v2.d.ts +0 -87
- package/storages/request_queue_v2.d.ts.map +0 -1
- package/storages/request_queue_v2.js +0 -438
- package/storages/request_queue_v2.js.map +0 -1
- package/storages/sitemap_request_list.d.ts.map +0 -1
- package/storages/sitemap_request_list.js +0 -430
- package/storages/sitemap_request_list.js.map +0 -1
- package/storages/storage_manager.d.ts +0 -58
- package/storages/storage_manager.d.ts.map +0 -1
- package/storages/storage_manager.js +0 -105
- package/storages/storage_manager.js.map +0 -1
- package/storages/utils.d.ts.map +0 -1
- package/storages/utils.js.map +0 -1
- package/tsconfig.build.tsbuildinfo +0 -1
- package/typedefs.d.ts.map +0 -1
- package/typedefs.js.map +0 -1
- package/validators.d.ts.map +0 -1
- package/validators.js.map +0 -1
package/storages/utils.js
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
2
|
import { Configuration } from '../configuration.js';
|
|
3
|
+
import { serviceLocator } from '../service_locator.js';
|
|
3
4
|
import { KeyValueStore } from './key_value_store.js';
|
|
4
|
-
export async function purgeDefaultStorages(
|
|
5
|
-
const options =
|
|
5
|
+
export async function purgeDefaultStorages(configurationOrOptions, storageBackend) {
|
|
6
|
+
const options = configurationOrOptions instanceof Configuration
|
|
6
7
|
? {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
storageBackend,
|
|
9
|
+
configuration: configurationOrOptions,
|
|
9
10
|
}
|
|
10
|
-
: (
|
|
11
|
-
const {
|
|
12
|
-
({
|
|
13
|
-
const casted =
|
|
11
|
+
: (configurationOrOptions ?? {});
|
|
12
|
+
const { configuration = serviceLocator.getConfiguration(), onlyPurgeOnce = false } = options;
|
|
13
|
+
({ storageBackend = serviceLocator.getStorageBackend() } = options);
|
|
14
|
+
const casted = storageBackend;
|
|
15
|
+
const runPurge = async () => {
|
|
16
|
+
try {
|
|
17
|
+
await casted.purge?.();
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
casted.__purged = undefined;
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
14
24
|
// if `onlyPurgeOnce` is true, will purge anytime this function is called, otherwise - only on start
|
|
15
|
-
if (!onlyPurgeOnce || (
|
|
16
|
-
casted.__purged =
|
|
17
|
-
await casted.purge?.();
|
|
25
|
+
if (!onlyPurgeOnce || (configuration.purgeOnStart && !casted.__purged)) {
|
|
26
|
+
casted.__purged = runPurge();
|
|
18
27
|
}
|
|
28
|
+
await casted.__purged;
|
|
19
29
|
}
|
|
20
30
|
/**
|
|
21
31
|
* Easily create and manage state values. All state values are automatically persisted.
|
|
@@ -24,11 +34,11 @@ export async function purgeDefaultStorages(configOrOptions, client) {
|
|
|
24
34
|
*
|
|
25
35
|
* @param name The name of the store to use.
|
|
26
36
|
* @param defaultValue If the store does not yet have a value in it, the value will be initialized with the `defaultValue` you provide.
|
|
27
|
-
* @param options An optional object parameter where a custom `keyValueStoreName` and `
|
|
37
|
+
* @param options An optional object parameter where a custom `keyValueStoreName` and `configuration` can be passed in.
|
|
28
38
|
*/
|
|
29
39
|
export async function useState(name, defaultValue = {}, options) {
|
|
30
|
-
const kvStore = await KeyValueStore.open(options?.keyValueStoreName, {
|
|
31
|
-
|
|
40
|
+
const kvStore = await KeyValueStore.open(options?.keyValueStoreName ? { name: options.keyValueStoreName } : null, {
|
|
41
|
+
configuration: options?.configuration || serviceLocator.getConfiguration(),
|
|
32
42
|
});
|
|
33
43
|
return kvStore.getAutoSavedValue(name || 'CRAWLEE_GLOBAL_STATE', defaultValue);
|
|
34
44
|
}
|
|
@@ -69,4 +79,54 @@ export const API_PROCESSED_REQUESTS_DELAY_MILLIS = 10_000;
|
|
|
69
79
|
* @internal
|
|
70
80
|
*/
|
|
71
81
|
export const MAX_QUERIES_FOR_CONSISTENCY = 6;
|
|
72
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Creates an object that is both an `AsyncIterable<TItem>` (for `for await...of`)
|
|
84
|
+
* and a `Promise<TItem[]>` (for `await`) from a single async page generator.
|
|
85
|
+
*
|
|
86
|
+
* - `await result` drains all pages from a fresh generator and returns every
|
|
87
|
+
* item as a flat array.
|
|
88
|
+
* - `for await (const item of result)` streams all items across all pages,
|
|
89
|
+
* yielding them one by one without buffering everything in memory.
|
|
90
|
+
*
|
|
91
|
+
* Each usage path creates its own generator instance, so `await` and
|
|
92
|
+
* `for await...of` never interfere with each other.
|
|
93
|
+
*
|
|
94
|
+
* @internal
|
|
95
|
+
*/
|
|
96
|
+
export function createDualIterable(options) {
|
|
97
|
+
const { createPages, extractItems } = options;
|
|
98
|
+
let cached = null;
|
|
99
|
+
function getOrCreate() {
|
|
100
|
+
if (!cached) {
|
|
101
|
+
cached = (async () => {
|
|
102
|
+
const items = [];
|
|
103
|
+
for await (const page of createPages()) {
|
|
104
|
+
items.push(...extractItems(page));
|
|
105
|
+
}
|
|
106
|
+
return items;
|
|
107
|
+
})();
|
|
108
|
+
}
|
|
109
|
+
return cached;
|
|
110
|
+
}
|
|
111
|
+
async function* iterateAll() {
|
|
112
|
+
for await (const page of createPages()) {
|
|
113
|
+
yield* extractItems(page);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const result = {
|
|
117
|
+
[Symbol.asyncIterator]() {
|
|
118
|
+
return iterateAll();
|
|
119
|
+
},
|
|
120
|
+
then(onfulfilled, onrejected) {
|
|
121
|
+
return getOrCreate().then(onfulfilled, onrejected);
|
|
122
|
+
},
|
|
123
|
+
catch(onrejected) {
|
|
124
|
+
return getOrCreate().catch(onrejected);
|
|
125
|
+
},
|
|
126
|
+
finally(onfinally) {
|
|
127
|
+
return getOrCreate().finally(onfinally);
|
|
128
|
+
},
|
|
129
|
+
[Symbol.toStringTag]: 'DualIterable',
|
|
130
|
+
};
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { CrawleeLogger } from '@crawlee/types';
|
|
2
|
+
/**
|
|
3
|
+
* Gets the "bare metal" cpu load.
|
|
4
|
+
* Used in
|
|
5
|
+
* - AWS Lambda
|
|
6
|
+
* - Containers without a cGroup quota
|
|
7
|
+
* - Uncontainerized environments
|
|
8
|
+
* @returns a number between 0 and 1 for the cpu load
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare function getCurrentCpuTicks(): number;
|
|
12
|
+
/**
|
|
13
|
+
* Reads the cgroup cpu quota.
|
|
14
|
+
* In V1, a quota of -1 means “unlimited.”
|
|
15
|
+
* In V2, a first field of "max" means unlimited.
|
|
16
|
+
* @param cgroupsVersion the cGroup version
|
|
17
|
+
* @returns The Cpu Quota
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function getCpuQuota(cgroupsVersion: string): Promise<number | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Reads the cgroup cpu period.
|
|
23
|
+
* @param cgroupsVersion the cGroup version
|
|
24
|
+
* @returns The Cpu quota period
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export declare function getCpuPeriod(cgroupsVersion: string): Promise<number>;
|
|
28
|
+
/**
|
|
29
|
+
* Reads the cgroup cpu usage of the container
|
|
30
|
+
*
|
|
31
|
+
* @param cgroupsVersion the cGroup version
|
|
32
|
+
* @returns the cpu usage
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function getContainerCpuUsage(cgroupsVersion: string): Promise<number>;
|
|
36
|
+
/**
|
|
37
|
+
* Reads the cgroup cpu usage of the system from cgroup
|
|
38
|
+
*
|
|
39
|
+
* @returns the cpu usage
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export declare function getSystemCpuUsage(): Promise<number>;
|
|
43
|
+
/**
|
|
44
|
+
* a cpu sample with the container usage and system usage
|
|
45
|
+
*/
|
|
46
|
+
export interface CpuSample {
|
|
47
|
+
containerUsage: number;
|
|
48
|
+
systemUsage: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Takes a CPU usage sample for both the container and the system.
|
|
52
|
+
*
|
|
53
|
+
* @returns An object containing the container and system CPU usage.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
export declare function sampleCpuUsage(cGroupsVersion: string): Promise<CpuSample>;
|
|
57
|
+
/**
|
|
58
|
+
* Gets the cpu usage of the system.
|
|
59
|
+
* If the crawler is running in a containerized environment, crawlee will check for a cgroup enforced cpu limit.
|
|
60
|
+
* If a cgroup limit is found, it will be taken as the maximum load against which the current load will be gauged.
|
|
61
|
+
* @returns a number between 0 and 1 for the cpu load
|
|
62
|
+
* @internal
|
|
63
|
+
*/
|
|
64
|
+
export declare function getCurrentCpuTicksV2(options?: {
|
|
65
|
+
containerized?: boolean;
|
|
66
|
+
logger?: CrawleeLogger;
|
|
67
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { getCgroupsVersion } from './runtime.js';
|
|
5
|
+
const CPU_FILE_PATHS = {
|
|
6
|
+
STAT: {
|
|
7
|
+
V1: '/sys/fs/cgroup/cpuacct/cpuacct.usage',
|
|
8
|
+
V2: '/sys/fs/cgroup/cpu.stat',
|
|
9
|
+
},
|
|
10
|
+
QUOTA: {
|
|
11
|
+
V1: '/sys/fs/cgroup/cpu/cpu.cfs_quota_us',
|
|
12
|
+
V2: '/sys/fs/cgroup/cpu.max',
|
|
13
|
+
},
|
|
14
|
+
PERIOD: {
|
|
15
|
+
V1: '/sys/fs/cgroup/cpu/cpu.cfs_period_us',
|
|
16
|
+
V2: '/sys/fs/cgroup/cpu.max',
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
let CLOCK_TICKS_PER_SECOND = 100;
|
|
20
|
+
let CLOCK_TICKS_CHECKED = false;
|
|
21
|
+
const NANOSECONDS_PER_SECOND = 1e9;
|
|
22
|
+
const previousTicks = { idle: 0, total: 0 };
|
|
23
|
+
/**
|
|
24
|
+
* Gets the "bare metal" cpu load.
|
|
25
|
+
* Used in
|
|
26
|
+
* - AWS Lambda
|
|
27
|
+
* - Containers without a cGroup quota
|
|
28
|
+
* - Uncontainerized environments
|
|
29
|
+
* @returns a number between 0 and 1 for the cpu load
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export function getCurrentCpuTicks() {
|
|
33
|
+
const cpusCores = os.cpus();
|
|
34
|
+
const ticks = cpusCores.reduce((acc, cpu) => {
|
|
35
|
+
const cpuTimes = Object.values(cpu.times);
|
|
36
|
+
return {
|
|
37
|
+
idle: acc.idle + cpu.times.idle,
|
|
38
|
+
total: acc.total + cpuTimes.reduce((sum, num) => sum + num),
|
|
39
|
+
};
|
|
40
|
+
}, { idle: 0, total: 0 });
|
|
41
|
+
const idleTicksDelta = ticks.idle - previousTicks.idle;
|
|
42
|
+
const totalTicksDelta = ticks.total - previousTicks.total;
|
|
43
|
+
Object.assign(previousTicks, ticks);
|
|
44
|
+
return totalTicksDelta ? 1 - idleTicksDelta / totalTicksDelta : 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Reads the linux tick rate.
|
|
48
|
+
* @returns the number of ticks per second, or `null` if detection failed
|
|
49
|
+
*/
|
|
50
|
+
function getClockTicks() {
|
|
51
|
+
try {
|
|
52
|
+
return parseInt(execSync('getconf CLK_TCK').toString().trim(), 10);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reads the cgroup cpu quota.
|
|
60
|
+
* In V1, a quota of -1 means “unlimited.”
|
|
61
|
+
* In V2, a first field of "max" means unlimited.
|
|
62
|
+
* @param cgroupsVersion the cGroup version
|
|
63
|
+
* @returns The Cpu Quota
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
export async function getCpuQuota(cgroupsVersion) {
|
|
67
|
+
if (cgroupsVersion === 'V1') {
|
|
68
|
+
const quotaStr = await readFile(CPU_FILE_PATHS.QUOTA.V1, 'utf8');
|
|
69
|
+
const quota = parseInt(quotaStr.trim(), 10);
|
|
70
|
+
return quota === -1 ? null : quota;
|
|
71
|
+
}
|
|
72
|
+
// cgroup v2
|
|
73
|
+
const maxStr = await readFile(CPU_FILE_PATHS.QUOTA.V2, 'utf8');
|
|
74
|
+
const parts = maxStr.trim().split(/\s+/);
|
|
75
|
+
if (parts[0] === 'max') {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return parseInt(parts[0], 10);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Reads the cgroup cpu period.
|
|
82
|
+
* @param cgroupsVersion the cGroup version
|
|
83
|
+
* @returns The Cpu quota period
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
export async function getCpuPeriod(cgroupsVersion) {
|
|
87
|
+
if (cgroupsVersion === 'V1') {
|
|
88
|
+
const quotaStr = await readFile(CPU_FILE_PATHS.PERIOD.V1, 'utf8');
|
|
89
|
+
const quota = parseInt(quotaStr.trim(), 10);
|
|
90
|
+
return quota;
|
|
91
|
+
}
|
|
92
|
+
// cgroup v2
|
|
93
|
+
const maxStr = await readFile(CPU_FILE_PATHS.PERIOD.V2, 'utf8');
|
|
94
|
+
const parts = maxStr.trim().split(/\s+/);
|
|
95
|
+
return parseInt(parts[1], 10);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Reads the cgroup cpu usage of the container
|
|
99
|
+
*
|
|
100
|
+
* @param cgroupsVersion the cGroup version
|
|
101
|
+
* @returns the cpu usage
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
export async function getContainerCpuUsage(cgroupsVersion) {
|
|
105
|
+
if (cgroupsVersion === 'V1') {
|
|
106
|
+
const data = await readFile(CPU_FILE_PATHS.STAT.V1, 'utf8');
|
|
107
|
+
return Number(data.trim());
|
|
108
|
+
}
|
|
109
|
+
// cgroup v2
|
|
110
|
+
const data = await readFile(CPU_FILE_PATHS.STAT.V2, 'utf8');
|
|
111
|
+
const lines = data.split('\n');
|
|
112
|
+
let usageUsec = 0;
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
const parts = line.trim().split(/\s+/);
|
|
115
|
+
if (parts[0] === 'usage_usec') {
|
|
116
|
+
usageUsec = Number(parts[1]);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Convert microseconds to nanoseconds.
|
|
121
|
+
return usageUsec * 1000;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reads the cgroup cpu usage of the system from cgroup
|
|
125
|
+
*
|
|
126
|
+
* @returns the cpu usage
|
|
127
|
+
* @internal
|
|
128
|
+
*/
|
|
129
|
+
export async function getSystemCpuUsage() {
|
|
130
|
+
const statData = await readFile('/proc/stat', 'utf8');
|
|
131
|
+
const lines = statData.split('\n');
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
if (line.startsWith('cpu ')) {
|
|
134
|
+
// Split the line and extract the first seven numeric fields:
|
|
135
|
+
// user, nice, system, idle, iowait, irq, softirq
|
|
136
|
+
const parts = line.split(/\s+/).slice(1, 8);
|
|
137
|
+
let totalTicks = 0;
|
|
138
|
+
for (const part of parts) {
|
|
139
|
+
totalTicks += Number(part);
|
|
140
|
+
}
|
|
141
|
+
// Convert clock ticks to nanoseconds.
|
|
142
|
+
return (totalTicks * NANOSECONDS_PER_SECOND) / CLOCK_TICKS_PER_SECOND;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
throw new Error('no cpu line'); // shouldnt ever happen
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Takes a CPU usage sample for both the container and the system.
|
|
149
|
+
*
|
|
150
|
+
* @returns An object containing the container and system CPU usage.
|
|
151
|
+
* @internal
|
|
152
|
+
*/
|
|
153
|
+
export async function sampleCpuUsage(cGroupsVersion) {
|
|
154
|
+
const [containerUsage, systemUsage] = await Promise.all([
|
|
155
|
+
getContainerCpuUsage(cGroupsVersion),
|
|
156
|
+
getSystemCpuUsage(),
|
|
157
|
+
]);
|
|
158
|
+
return { containerUsage, systemUsage };
|
|
159
|
+
}
|
|
160
|
+
let previousSample = { containerUsage: 0, systemUsage: 0 };
|
|
161
|
+
/**
|
|
162
|
+
* Gets the cpu usage of the system.
|
|
163
|
+
* If the crawler is running in a containerized environment, crawlee will check for a cgroup enforced cpu limit.
|
|
164
|
+
* If a cgroup limit is found, it will be taken as the maximum load against which the current load will be gauged.
|
|
165
|
+
* @returns a number between 0 and 1 for the cpu load
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
export async function getCurrentCpuTicksV2(options = {}) {
|
|
169
|
+
const { containerized = false, logger } = options;
|
|
170
|
+
try {
|
|
171
|
+
// if not containerized
|
|
172
|
+
if (!containerized) {
|
|
173
|
+
// bare metal cpu limit
|
|
174
|
+
return getCurrentCpuTicks();
|
|
175
|
+
}
|
|
176
|
+
if (!CLOCK_TICKS_CHECKED) {
|
|
177
|
+
const ticks = getClockTicks();
|
|
178
|
+
if (ticks === null) {
|
|
179
|
+
CLOCK_TICKS_PER_SECOND = 100;
|
|
180
|
+
logger?.warning('Failed to get clock ticks; defaulting to 100. CPU metrics may be inaccurate.');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
CLOCK_TICKS_PER_SECOND = ticks;
|
|
184
|
+
}
|
|
185
|
+
CLOCK_TICKS_CHECKED = true;
|
|
186
|
+
}
|
|
187
|
+
const cgroupsVersion = await getCgroupsVersion();
|
|
188
|
+
// if cgroup is not detected, return bare metal cpu limit
|
|
189
|
+
if (cgroupsVersion === null) {
|
|
190
|
+
logger?.warningOnce('Your environment is containerized, but your system does not support cgroups.\n' +
|
|
191
|
+
"If you're running containers with limited cpu, cpu auto-scaling will not work properly.");
|
|
192
|
+
return getCurrentCpuTicks();
|
|
193
|
+
}
|
|
194
|
+
// cgroup aware cpu limit. If no limits are set, default to returning getCurrentCpuTicks.
|
|
195
|
+
const quota = await getCpuQuota(cgroupsVersion);
|
|
196
|
+
if (quota === null) {
|
|
197
|
+
// no cgroup limit, return host cpu load
|
|
198
|
+
return getCurrentCpuTicks();
|
|
199
|
+
}
|
|
200
|
+
const period = await getCpuPeriod(cgroupsVersion);
|
|
201
|
+
// eg. having a 200000us quots per 100000us means the cGroup can fully use 2 cores
|
|
202
|
+
const cpuAllowance = quota / period;
|
|
203
|
+
const sample = await sampleCpuUsage(cgroupsVersion);
|
|
204
|
+
const containerDelta = sample.containerUsage - previousSample.containerUsage;
|
|
205
|
+
const systemDelta = sample.systemUsage - previousSample.systemUsage;
|
|
206
|
+
previousSample = sample;
|
|
207
|
+
const numCpus = os.cpus().length;
|
|
208
|
+
// Calculate the CPU usage percentage.
|
|
209
|
+
return ((containerDelta / systemDelta) * numCpus) / cpuAllowance;
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
// if anything fails, default to bare metal metrics
|
|
213
|
+
logger?.warning('Cpu snapshot failed, falling back to bare-metal metrics.', { error: err });
|
|
214
|
+
return getCurrentCpuTicks();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CrawleeLogger } from '@crawlee/types';
|
|
2
|
+
/**
|
|
3
|
+
* Describes memory usage of the process.
|
|
4
|
+
*/
|
|
5
|
+
export interface MemoryInfo {
|
|
6
|
+
/** Total memory available in the system or container */
|
|
7
|
+
totalBytes: number;
|
|
8
|
+
/** Amount of free memory in the system or container */
|
|
9
|
+
freeBytes: number;
|
|
10
|
+
/** Amount of memory used (= totalBytes - freeBytes) */
|
|
11
|
+
usedBytes: number;
|
|
12
|
+
/** Amount of memory used the current Node.js process */
|
|
13
|
+
mainProcessBytes: number;
|
|
14
|
+
/** Amount of memory used by child processes of the current Node.js process */
|
|
15
|
+
childProcessesBytes: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns memory statistics of the process and the system, see {@link MemoryInfo}.
|
|
19
|
+
*
|
|
20
|
+
* If the process runs inside of a container, the `getMemoryInfo` gets container memory limits,
|
|
21
|
+
* otherwise it gets system memory limits.
|
|
22
|
+
*
|
|
23
|
+
* Beware that the function is quite inefficient because it spawns a new process.
|
|
24
|
+
* Therefore you shouldn't call it too often, like more than once per second.
|
|
25
|
+
* @returns An object containing the free and used memory metrics.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export declare function getMemoryInfo(options?: {
|
|
29
|
+
containerized?: boolean;
|
|
30
|
+
logger?: CrawleeLogger;
|
|
31
|
+
}): Promise<MemoryInfo>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { freemem, totalmem } from 'node:os';
|
|
4
|
+
import { getCgroupsVersion, isLambda } from './runtime.js';
|
|
5
|
+
import { psTree } from './ps-tree.js';
|
|
6
|
+
const MEMORY_FILE_PATHS = {
|
|
7
|
+
TOTAL: {
|
|
8
|
+
V1: '/sys/fs/cgroup/memory/memory.limit_in_bytes',
|
|
9
|
+
V2: '/sys/fs/cgroup/memory.max',
|
|
10
|
+
},
|
|
11
|
+
USED: {
|
|
12
|
+
V1: '/sys/fs/cgroup/memory/memory.usage_in_bytes',
|
|
13
|
+
V2: '/sys/fs/cgroup/memory.current',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Returns memory statistics of the process and the system, see {@link MemoryInfo}.
|
|
18
|
+
*
|
|
19
|
+
* If the process runs inside of a container, the `getMemoryInfo` gets container memory limits,
|
|
20
|
+
* otherwise it gets system memory limits.
|
|
21
|
+
*
|
|
22
|
+
* Beware that the function is quite inefficient because it spawns a new process.
|
|
23
|
+
* Therefore you shouldn't call it too often, like more than once per second.
|
|
24
|
+
* @returns An object containing the free and used memory metrics.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export async function getMemoryInfo(options = {}) {
|
|
28
|
+
const { containerized = false, logger } = options;
|
|
29
|
+
let mainProcessBytes = -1;
|
|
30
|
+
let childProcessesBytes = 0;
|
|
31
|
+
// lambda does *not* have `ps` and other command line tools
|
|
32
|
+
// required to extract memory usage.
|
|
33
|
+
if (isLambda()) {
|
|
34
|
+
// reported in bytes
|
|
35
|
+
mainProcessBytes = process.memoryUsage().rss;
|
|
36
|
+
// https://stackoverflow.com/a/55914335/129415
|
|
37
|
+
const memInfo = execSync('cat /proc/meminfo').toString();
|
|
38
|
+
const values = memInfo.split(/[\n: ]/).filter((val) => val.trim());
|
|
39
|
+
// /proc/meminfo reports in kb, not bytes, the total used memory is reported by meminfo
|
|
40
|
+
// subtract memory used by the main node process in order to infer memory used by any child processes
|
|
41
|
+
childProcessesBytes = +values[19] * 1000 - mainProcessBytes;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// Query both root and child processes
|
|
45
|
+
const processes = await psTree(process.pid, true);
|
|
46
|
+
processes.forEach((rec) => {
|
|
47
|
+
// Obtain main process' memory separately
|
|
48
|
+
if (rec.PID === `${process.pid}`) {
|
|
49
|
+
mainProcessBytes = rec.RSS;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
childProcessesBytes += rec.RSS;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
let totalBytes;
|
|
56
|
+
let usedBytes;
|
|
57
|
+
let freeBytes;
|
|
58
|
+
if (isLambda()) {
|
|
59
|
+
// memory size is defined in megabytes
|
|
60
|
+
totalBytes = parseInt(process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE, 10) * 1000000;
|
|
61
|
+
usedBytes = mainProcessBytes + childProcessesBytes;
|
|
62
|
+
freeBytes = totalBytes - usedBytes;
|
|
63
|
+
}
|
|
64
|
+
else if (containerized) {
|
|
65
|
+
// When running inside a container, use container memory limits
|
|
66
|
+
const cgroupsVersion = await getCgroupsVersion();
|
|
67
|
+
try {
|
|
68
|
+
if (cgroupsVersion === null) {
|
|
69
|
+
throw new Error('cgroup not available');
|
|
70
|
+
}
|
|
71
|
+
let [totalBytesStr, usedBytesStr] = await Promise.all([
|
|
72
|
+
readFile(MEMORY_FILE_PATHS.TOTAL[cgroupsVersion], 'utf8'),
|
|
73
|
+
readFile(MEMORY_FILE_PATHS.USED[cgroupsVersion], 'utf8'),
|
|
74
|
+
]);
|
|
75
|
+
// Cgroups V2 files contains newline character. Getting rid of it for better handling in later part of the code.
|
|
76
|
+
totalBytesStr = totalBytesStr.replace(/[^a-zA-Z0-9 ]/g, '');
|
|
77
|
+
usedBytesStr = usedBytesStr.replace(/[^a-zA-Z0-9 ]/g, '');
|
|
78
|
+
// Cgroups V2 contains 'max' string if memory is not limited
|
|
79
|
+
// See https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git/tree/Documentation/admin-guide/cgroup-v2.rst (see "memory.max")
|
|
80
|
+
if (totalBytesStr === 'max') {
|
|
81
|
+
totalBytes = totalmem();
|
|
82
|
+
// Cgroups V1 is set to number related to platform and page size if memory is not limited
|
|
83
|
+
// See https://unix.stackexchange.com/q/420906
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
totalBytes = parseInt(totalBytesStr, 10);
|
|
87
|
+
const containerRunsWithUnlimitedMemory = totalBytes > Number.MAX_SAFE_INTEGER;
|
|
88
|
+
if (containerRunsWithUnlimitedMemory)
|
|
89
|
+
totalBytes = totalmem();
|
|
90
|
+
}
|
|
91
|
+
usedBytes = parseInt(usedBytesStr, 10);
|
|
92
|
+
freeBytes = totalBytes - usedBytes;
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
logger?.warningOnce('Your environment is containerized, but your system does not support memory cgroups. ' +
|
|
96
|
+
"If you're running containers with limited memory, memory auto-scaling will not work properly.\n\n" +
|
|
97
|
+
`Cause: ${err.message}`);
|
|
98
|
+
totalBytes = totalmem();
|
|
99
|
+
freeBytes = freemem();
|
|
100
|
+
usedBytes = totalBytes - freeBytes;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
totalBytes = totalmem();
|
|
105
|
+
freeBytes = freemem();
|
|
106
|
+
usedBytes = totalBytes - freeBytes;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
totalBytes,
|
|
110
|
+
freeBytes,
|
|
111
|
+
usedBytes,
|
|
112
|
+
mainProcessBytes,
|
|
113
|
+
childProcessesBytes,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ProcessInfo {
|
|
2
|
+
PPID: string;
|
|
3
|
+
PID: string;
|
|
4
|
+
STAT: string | null;
|
|
5
|
+
RSS: number;
|
|
6
|
+
COMMAND: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Returns a promise that resolves with an array of ProcessInfo objects representing
|
|
10
|
+
* the children of the given PID.
|
|
11
|
+
*
|
|
12
|
+
* @param pid - The PID (number or string) for which to list child processes.
|
|
13
|
+
* @param includeRoot - Optional flag. When true, include the process with the given PID if found.
|
|
14
|
+
* Defaults to false.
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare function psTree(pid: number | string, includeRoot?: boolean): Promise<ProcessInfo[]>;
|