@browserless.io/browserless 2.9.0 → 2.11.0
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/CHANGELOG.md +10 -1
- package/build/browserless.js +4 -2
- package/build/browsers/chromium.cdp.d.ts +1 -0
- package/build/browsers/chromium.cdp.js +3 -0
- package/build/browsers/chromium.playwright.d.ts +1 -0
- package/build/browsers/chromium.playwright.js +3 -0
- package/build/browsers/firefox.playwright.d.ts +1 -0
- package/build/browsers/firefox.playwright.js +3 -0
- package/build/browsers/index.d.ts +0 -1
- package/build/browsers/index.js +3 -5
- package/build/browsers/webkit.playwright.d.ts +1 -0
- package/build/browsers/webkit.playwright.js +3 -0
- package/build/http.d.ts +1 -0
- package/build/http.js +1 -0
- package/build/routes/chrome/http/content.post.body.json +8 -8
- package/build/routes/chrome/http/pdf.post.body.json +8 -8
- package/build/routes/chrome/http/scrape.post.body.json +8 -8
- package/build/routes/chrome/http/screenshot.post.body.json +8 -8
- package/build/routes/chromium/http/content.post.body.json +8 -8
- package/build/routes/chromium/http/pdf.post.body.json +8 -8
- package/build/routes/chromium/http/scrape.post.body.json +8 -8
- package/build/routes/chromium/http/screenshot.post.body.json +8 -8
- package/build/routes/management/http/pressure.get.d.ts +65 -0
- package/build/routes/management/http/pressure.get.js +58 -0
- package/build/routes/management/http/pressure.get.response.json +85 -0
- package/build/routes/management/tests/management.spec.js +7 -0
- package/build/types.d.ts +10 -1
- package/build/types.js +10 -1
- package/package.json +6 -6
- package/src/browserless.ts +4 -0
- package/src/browsers/chromium.cdp.ts +4 -0
- package/src/browsers/chromium.playwright.ts +4 -0
- package/src/browsers/firefox.playwright.ts +4 -0
- package/src/browsers/index.ts +3 -5
- package/src/browsers/webkit.playwright.ts +4 -0
- package/src/http.ts +1 -0
- package/src/routes/management/http/pressure.get.ts +139 -0
- package/src/routes/management/tests/management.spec.ts +19 -8
- package/src/types.ts +9 -0
- package/static/docs/swagger.json +147 -10
- package/static/docs/swagger.min.json +146 -9
- package/static/function/client.js +2749 -2730
- package/static/function/index.html +2749 -2730
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { APITags, HTTPManagementRoutes, HTTPRoute, Methods, Request, contentTypes } from '@browserless.io/browserless';
|
|
3
|
+
import { ServerResponse } from 'http';
|
|
4
|
+
export type ResponseSchema = {
|
|
5
|
+
pressure: {
|
|
6
|
+
/**
|
|
7
|
+
* An integer representing the percentage of CPU being used. For instance 92 means 92%
|
|
8
|
+
*/
|
|
9
|
+
cpu: number | null;
|
|
10
|
+
/**
|
|
11
|
+
* A number of milliseconds since epoch, or "Date.now()" equivalent.
|
|
12
|
+
*/
|
|
13
|
+
date: number;
|
|
14
|
+
/**
|
|
15
|
+
* Whether or not a session can be connected and immediately ran on a health instance.
|
|
16
|
+
*/
|
|
17
|
+
isAvailable: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The maximum amount of browsers that can be ran at a single time.
|
|
20
|
+
*/
|
|
21
|
+
maxConcurrent: number;
|
|
22
|
+
/**
|
|
23
|
+
* The maximum amount of queued connections allowed at a single time.
|
|
24
|
+
*/
|
|
25
|
+
maxQueued: number;
|
|
26
|
+
/**
|
|
27
|
+
* An integer representing the percentage of Memory being used. For instance 95 means 95%
|
|
28
|
+
*/
|
|
29
|
+
memory: number | null;
|
|
30
|
+
/**
|
|
31
|
+
* A human-readable message as the overall status of the instance.
|
|
32
|
+
*/
|
|
33
|
+
message: string;
|
|
34
|
+
/**
|
|
35
|
+
* The current number of connect or API calls pending to run.
|
|
36
|
+
*/
|
|
37
|
+
queued: number;
|
|
38
|
+
/**
|
|
39
|
+
* A simple single-word reason as to why an instance may or may not be available.
|
|
40
|
+
*/
|
|
41
|
+
reason: 'full' | 'cpu' | 'memory' | '';
|
|
42
|
+
/**
|
|
43
|
+
* The number of recent connections that were rejected due to the queue and concurrency
|
|
44
|
+
* limits having been filled.
|
|
45
|
+
*/
|
|
46
|
+
recentlyRejected: number;
|
|
47
|
+
/**
|
|
48
|
+
* The current number of running connections or API calls.
|
|
49
|
+
*/
|
|
50
|
+
running: number;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export default class PressureGetRoute extends HTTPRoute {
|
|
54
|
+
name: string;
|
|
55
|
+
accepts: contentTypes[];
|
|
56
|
+
auth: boolean;
|
|
57
|
+
browser: null;
|
|
58
|
+
concurrency: boolean;
|
|
59
|
+
contentTypes: contentTypes[];
|
|
60
|
+
description: string;
|
|
61
|
+
method: Methods;
|
|
62
|
+
path: HTTPManagementRoutes;
|
|
63
|
+
tags: APITags[];
|
|
64
|
+
handler: (_req: Request, res: ServerResponse) => Promise<void>;
|
|
65
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { APITags, BrowserlessRoutes, HTTPManagementRoutes, HTTPRoute, Methods, contentTypes, jsonResponse, } from '@browserless.io/browserless';
|
|
2
|
+
export default class PressureGetRoute extends HTTPRoute {
|
|
3
|
+
name = BrowserlessRoutes.PressureGetRoute;
|
|
4
|
+
accepts = [contentTypes.any];
|
|
5
|
+
auth = true;
|
|
6
|
+
browser = null;
|
|
7
|
+
concurrency = false;
|
|
8
|
+
contentTypes = [contentTypes.json];
|
|
9
|
+
description = `Returns a JSON body of stats related to the pressure being created on the instance.`;
|
|
10
|
+
method = Methods.get;
|
|
11
|
+
path = HTTPManagementRoutes.pressure;
|
|
12
|
+
tags = [APITags.management];
|
|
13
|
+
handler = async (_req, res) => {
|
|
14
|
+
const monitoring = this.monitoring();
|
|
15
|
+
const config = this.config();
|
|
16
|
+
const limiter = this.limiter();
|
|
17
|
+
const metrics = this.metrics();
|
|
18
|
+
const { cpuInt: cpu, memoryInt: memory, cpuOverloaded, memoryOverloaded, } = await monitoring.overloaded();
|
|
19
|
+
const date = Date.now();
|
|
20
|
+
const hasCapacity = limiter.hasCapacity;
|
|
21
|
+
const queued = limiter.waiting;
|
|
22
|
+
const isAvailable = hasCapacity && !cpuOverloaded && !memoryOverloaded;
|
|
23
|
+
const running = limiter.executing;
|
|
24
|
+
const recentlyRejected = metrics.get().rejected;
|
|
25
|
+
const maxConcurrent = config.getConcurrent();
|
|
26
|
+
const maxQueued = config.getQueued();
|
|
27
|
+
const reason = !hasCapacity
|
|
28
|
+
? 'full'
|
|
29
|
+
: cpuOverloaded
|
|
30
|
+
? 'cpu'
|
|
31
|
+
: memoryOverloaded
|
|
32
|
+
? 'memory'
|
|
33
|
+
: '';
|
|
34
|
+
const message = !hasCapacity
|
|
35
|
+
? 'Concurrency and queue are full'
|
|
36
|
+
: cpuOverloaded
|
|
37
|
+
? 'CPU is over the configured maximum for cpu percent'
|
|
38
|
+
: memoryOverloaded
|
|
39
|
+
? 'Memory is over the configured maximum for memory percent'
|
|
40
|
+
: '';
|
|
41
|
+
const response = {
|
|
42
|
+
pressure: {
|
|
43
|
+
cpu,
|
|
44
|
+
date,
|
|
45
|
+
isAvailable,
|
|
46
|
+
maxConcurrent,
|
|
47
|
+
maxQueued,
|
|
48
|
+
memory,
|
|
49
|
+
message,
|
|
50
|
+
queued,
|
|
51
|
+
reason,
|
|
52
|
+
recentlyRejected,
|
|
53
|
+
running,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
return jsonResponse(res, 200, response);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"pressure": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"cpu": {
|
|
8
|
+
"description": "An integer representing the percentage of CPU being used. For instance 92 means 92%",
|
|
9
|
+
"type": [
|
|
10
|
+
"null",
|
|
11
|
+
"number"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"date": {
|
|
15
|
+
"description": "A number of milliseconds since epoch, or \"Date.now()\" equivalent.",
|
|
16
|
+
"type": "number"
|
|
17
|
+
},
|
|
18
|
+
"isAvailable": {
|
|
19
|
+
"description": "Whether or not a session can be connected and immediately ran on a health instance.",
|
|
20
|
+
"type": "boolean"
|
|
21
|
+
},
|
|
22
|
+
"maxConcurrent": {
|
|
23
|
+
"description": "The maximum amount of browsers that can be ran at a single time.",
|
|
24
|
+
"type": "number"
|
|
25
|
+
},
|
|
26
|
+
"maxQueued": {
|
|
27
|
+
"description": "The maximum amount of queued connections allowed at a single time.",
|
|
28
|
+
"type": "number"
|
|
29
|
+
},
|
|
30
|
+
"memory": {
|
|
31
|
+
"description": "An integer representing the percentage of Memory being used. For instance 95 means 95%",
|
|
32
|
+
"type": [
|
|
33
|
+
"null",
|
|
34
|
+
"number"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"message": {
|
|
38
|
+
"description": "A human-readable message as the overall status of the instance.",
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
"queued": {
|
|
42
|
+
"description": "The current number of connect or API calls pending to run.",
|
|
43
|
+
"type": "number"
|
|
44
|
+
},
|
|
45
|
+
"reason": {
|
|
46
|
+
"description": "A simple single-word reason as to why an instance may or may not be available.",
|
|
47
|
+
"enum": [
|
|
48
|
+
"",
|
|
49
|
+
"cpu",
|
|
50
|
+
"full",
|
|
51
|
+
"memory"
|
|
52
|
+
],
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
"recentlyRejected": {
|
|
56
|
+
"description": "The number of recent connections that were rejected due to the queue and concurrency\nlimits having been filled.",
|
|
57
|
+
"type": "number"
|
|
58
|
+
},
|
|
59
|
+
"running": {
|
|
60
|
+
"description": "The current number of running connections or API calls.",
|
|
61
|
+
"type": "number"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"additionalProperties": false,
|
|
65
|
+
"required": [
|
|
66
|
+
"cpu",
|
|
67
|
+
"date",
|
|
68
|
+
"isAvailable",
|
|
69
|
+
"maxConcurrent",
|
|
70
|
+
"maxQueued",
|
|
71
|
+
"memory",
|
|
72
|
+
"message",
|
|
73
|
+
"queued",
|
|
74
|
+
"reason",
|
|
75
|
+
"recentlyRejected",
|
|
76
|
+
"running"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"additionalProperties": false,
|
|
81
|
+
"required": [
|
|
82
|
+
"pressure"
|
|
83
|
+
],
|
|
84
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
85
|
+
}
|
|
@@ -31,6 +31,13 @@ describe('Management APIs', function () {
|
|
|
31
31
|
expect(res.status).to.equal(200);
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
|
+
it('allows requests to /pressure', async () => {
|
|
35
|
+
await start();
|
|
36
|
+
await fetch('http://localhost:3000/pressure?token=6R0W53R135510').then(async (res) => {
|
|
37
|
+
expect(res.headers.get('content-type')).to.equal('application/json; charset=UTF-8');
|
|
38
|
+
expect(res.status).to.equal(200);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
34
41
|
it('allows requests to /sessions', async () => {
|
|
35
42
|
await start();
|
|
36
43
|
await fetch('http://localhost:3000/sessions?token=6R0W53R135510').then(async (res) => {
|
package/build/types.d.ts
CHANGED
|
@@ -54,7 +54,8 @@ declare abstract class Route {
|
|
|
54
54
|
protected _metrics: Browserless['metrics'];
|
|
55
55
|
protected _monitoring: Browserless['monitoring'];
|
|
56
56
|
protected _staticSDKDir: Browserless['staticSDKDir'];
|
|
57
|
-
|
|
57
|
+
protected _limiter: Browserless['limiter'];
|
|
58
|
+
constructor(_browserManager: Browserless['browserManager'], _config: Browserless['config'], _fileSystem: Browserless['fileSystem'], _metrics: Browserless['metrics'], _monitoring: Browserless['monitoring'], _staticSDKDir: Browserless['staticSDKDir'], _limiter: Browserless['limiter']);
|
|
58
59
|
/**
|
|
59
60
|
* A unique name to identify this route. Used in downstream
|
|
60
61
|
* SDKs to potentially remove or disable.
|
|
@@ -133,6 +134,12 @@ declare abstract class Route {
|
|
|
133
134
|
* @returns {string | null} The full path location of the SDK's static directory
|
|
134
135
|
*/
|
|
135
136
|
staticSDKDir: () => string | null;
|
|
137
|
+
/**
|
|
138
|
+
* Helper function that loads the limiter module into the router's
|
|
139
|
+
* handler scope.
|
|
140
|
+
* @returns Limiter
|
|
141
|
+
*/
|
|
142
|
+
limiter: () => import("@browserless.io/browserless").Limiter;
|
|
136
143
|
/**
|
|
137
144
|
* The HTTP path that this route handles, eg '/my-route' OR an
|
|
138
145
|
* array of paths that this route can handle.
|
|
@@ -510,6 +517,7 @@ export declare const BrowserlessManagementRoutes: {
|
|
|
510
517
|
ConfigGetRoute: string;
|
|
511
518
|
MetricsGetRoute: string;
|
|
512
519
|
MetricsTotalGetRoute: string;
|
|
520
|
+
PressureGetRoute: string;
|
|
513
521
|
SessionsGetRoute: string;
|
|
514
522
|
StaticGetRoute: string;
|
|
515
523
|
};
|
|
@@ -518,6 +526,7 @@ export declare const BrowserlessRoutes: {
|
|
|
518
526
|
ConfigGetRoute: string;
|
|
519
527
|
MetricsGetRoute: string;
|
|
520
528
|
MetricsTotalGetRoute: string;
|
|
529
|
+
PressureGetRoute: string;
|
|
521
530
|
SessionsGetRoute: string;
|
|
522
531
|
StaticGetRoute: string;
|
|
523
532
|
WebKitPlaywrightWebSocketRoute: string;
|
package/build/types.js
CHANGED
|
@@ -5,13 +5,15 @@ class Route {
|
|
|
5
5
|
_metrics;
|
|
6
6
|
_monitoring;
|
|
7
7
|
_staticSDKDir;
|
|
8
|
-
|
|
8
|
+
_limiter;
|
|
9
|
+
constructor(_browserManager, _config, _fileSystem, _metrics, _monitoring, _staticSDKDir, _limiter) {
|
|
9
10
|
this._browserManager = _browserManager;
|
|
10
11
|
this._config = _config;
|
|
11
12
|
this._fileSystem = _fileSystem;
|
|
12
13
|
this._metrics = _metrics;
|
|
13
14
|
this._monitoring = _monitoring;
|
|
14
15
|
this._staticSDKDir = _staticSDKDir;
|
|
16
|
+
this._limiter = _limiter;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* A boolean, or a function that returns a boolean, on
|
|
@@ -86,6 +88,12 @@ class Route {
|
|
|
86
88
|
* @returns {string | null} The full path location of the SDK's static directory
|
|
87
89
|
*/
|
|
88
90
|
staticSDKDir = () => this._staticSDKDir;
|
|
91
|
+
/**
|
|
92
|
+
* Helper function that loads the limiter module into the router's
|
|
93
|
+
* handler scope.
|
|
94
|
+
* @returns Limiter
|
|
95
|
+
*/
|
|
96
|
+
limiter = () => this._limiter;
|
|
89
97
|
}
|
|
90
98
|
/**
|
|
91
99
|
* A primitive HTTP-based route that doesn't require a
|
|
@@ -184,6 +192,7 @@ export const BrowserlessManagementRoutes = {
|
|
|
184
192
|
ConfigGetRoute: 'ConfigGetRoute',
|
|
185
193
|
MetricsGetRoute: 'MetricsGetRoute',
|
|
186
194
|
MetricsTotalGetRoute: 'MetricsTotalGetRoute',
|
|
195
|
+
PressureGetRoute: 'PressureGetRoute',
|
|
187
196
|
SessionsGetRoute: 'SessionsGetRoute',
|
|
188
197
|
StaticGetRoute: 'StaticGetRoute',
|
|
189
198
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserless.io/browserless",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"license": "SSPL",
|
|
5
5
|
"description": "The browserless platform",
|
|
6
6
|
"author": "browserless.io",
|
|
@@ -59,11 +59,11 @@
|
|
|
59
59
|
"lighthouse": "^12.0.0",
|
|
60
60
|
"micromatch": "^4.0.4",
|
|
61
61
|
"playwright-core": "1.44.0",
|
|
62
|
-
"puppeteer-core": "^22.8.
|
|
62
|
+
"puppeteer-core": "^22.8.1",
|
|
63
63
|
"puppeteer-extra": "^3.3.6",
|
|
64
64
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
65
65
|
"queue": "^7.0.0",
|
|
66
|
-
"systeminformation": "^5.22.
|
|
66
|
+
"systeminformation": "^5.22.9",
|
|
67
67
|
"tar-fs": "^3.0.6"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"@types/http-proxy": "^1.17.14",
|
|
74
74
|
"@types/micromatch": "^4.0.7",
|
|
75
75
|
"@types/mocha": "^10.0.6",
|
|
76
|
-
"@types/node": "^20.12.
|
|
76
|
+
"@types/node": "^20.12.12",
|
|
77
77
|
"@types/sinon": "^17.0.3",
|
|
78
|
-
"@typescript-eslint/eslint-plugin": "^7.
|
|
79
|
-
"@typescript-eslint/parser": "^7.
|
|
78
|
+
"@typescript-eslint/eslint-plugin": "^7.9.0",
|
|
79
|
+
"@typescript-eslint/parser": "^7.9.0",
|
|
80
80
|
"assert": "^2.0.0",
|
|
81
81
|
"chai": "^5.1.1",
|
|
82
82
|
"cross-env": "^7.0.3",
|
package/src/browserless.ts
CHANGED
|
@@ -272,12 +272,14 @@ export class Browserless extends EventEmitter {
|
|
|
272
272
|
this.metrics,
|
|
273
273
|
this.monitoring,
|
|
274
274
|
this.staticSDKDir,
|
|
275
|
+
this.limiter,
|
|
275
276
|
);
|
|
276
277
|
|
|
277
278
|
if (!this.routeIsDisabled(route)) {
|
|
278
279
|
route.bodySchema = safeParse(bodySchema);
|
|
279
280
|
route.querySchema = safeParse(querySchema);
|
|
280
281
|
route.config = () => this.config;
|
|
282
|
+
route.limiter = () => this.limiter;
|
|
281
283
|
route.metrics = () => this.metrics;
|
|
282
284
|
route.monitoring = () => this.monitoring;
|
|
283
285
|
route.fileSystem = () => this.fileSystem;
|
|
@@ -321,11 +323,13 @@ export class Browserless extends EventEmitter {
|
|
|
321
323
|
this.metrics,
|
|
322
324
|
this.monitoring,
|
|
323
325
|
this.staticSDKDir,
|
|
326
|
+
this.limiter,
|
|
324
327
|
);
|
|
325
328
|
|
|
326
329
|
if (!this.routeIsDisabled(route)) {
|
|
327
330
|
route.querySchema = safeParse(querySchema);
|
|
328
331
|
route.config = () => this.config;
|
|
332
|
+
route.limiter = () => this.limiter;
|
|
329
333
|
route.metrics = () => this.metrics;
|
|
330
334
|
route.monitoring = () => this.monitoring;
|
|
331
335
|
route.fileSystem = () => this.fileSystem;
|
package/src/browsers/index.ts
CHANGED
|
@@ -36,7 +36,6 @@ import path from 'path';
|
|
|
36
36
|
|
|
37
37
|
export class BrowserManager {
|
|
38
38
|
protected browsers: Map<BrowserInstance, BrowserlessSession> = new Map();
|
|
39
|
-
protected launching: Map<string, Promise<unknown>> = new Map();
|
|
40
39
|
protected timers: Map<string, number> = new Map();
|
|
41
40
|
protected log = new Logger('browser-manager');
|
|
42
41
|
protected chromeBrowsers = [ChromiumCDP, ChromeCDP];
|
|
@@ -272,7 +271,7 @@ export class BrowserManager {
|
|
|
272
271
|
this.log.info(`${session.numbConnected} Client(s) are currently connected`);
|
|
273
272
|
|
|
274
273
|
// Don't close if there's clients still connected
|
|
275
|
-
if (session.numbConnected > 0) {
|
|
274
|
+
if (session.numbConnected > 0 || browser.keepAlive()) {
|
|
276
275
|
return;
|
|
277
276
|
}
|
|
278
277
|
|
|
@@ -314,7 +313,6 @@ export class BrowserManager {
|
|
|
314
313
|
|
|
315
314
|
if (id && resolver) {
|
|
316
315
|
resolver(null);
|
|
317
|
-
this.launching.delete(id);
|
|
318
316
|
}
|
|
319
317
|
|
|
320
318
|
--session.numbConnected;
|
|
@@ -457,7 +455,7 @@ export class BrowserManager {
|
|
|
457
455
|
userDataDir,
|
|
458
456
|
});
|
|
459
457
|
|
|
460
|
-
const
|
|
458
|
+
const session: BrowserlessSession = {
|
|
461
459
|
id: null,
|
|
462
460
|
initialConnectURL:
|
|
463
461
|
path.join(req.parsed.pathname, req.parsed.search) || '',
|
|
@@ -471,7 +469,7 @@ export class BrowserManager {
|
|
|
471
469
|
userDataDir,
|
|
472
470
|
};
|
|
473
471
|
|
|
474
|
-
this.browsers.set(browser,
|
|
472
|
+
this.browsers.set(browser, session);
|
|
475
473
|
|
|
476
474
|
await browser.launch(launchOptions as object);
|
|
477
475
|
await this.hooks.browser({ browser, meta: req.parsed });
|
package/src/http.ts
CHANGED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APITags,
|
|
3
|
+
BrowserlessRoutes,
|
|
4
|
+
HTTPManagementRoutes,
|
|
5
|
+
HTTPRoute,
|
|
6
|
+
Methods,
|
|
7
|
+
Request,
|
|
8
|
+
contentTypes,
|
|
9
|
+
jsonResponse,
|
|
10
|
+
} from '@browserless.io/browserless';
|
|
11
|
+
import { ServerResponse } from 'http';
|
|
12
|
+
|
|
13
|
+
export type ResponseSchema = {
|
|
14
|
+
pressure: {
|
|
15
|
+
/**
|
|
16
|
+
* An integer representing the percentage of CPU being used. For instance 92 means 92%
|
|
17
|
+
*/
|
|
18
|
+
cpu: number | null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A number of milliseconds since epoch, or "Date.now()" equivalent.
|
|
22
|
+
*/
|
|
23
|
+
date: number;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether or not a session can be connected and immediately ran on a health instance.
|
|
27
|
+
*/
|
|
28
|
+
isAvailable: boolean;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The maximum amount of browsers that can be ran at a single time.
|
|
32
|
+
*/
|
|
33
|
+
maxConcurrent: number;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The maximum amount of queued connections allowed at a single time.
|
|
37
|
+
*/
|
|
38
|
+
maxQueued: number;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* An integer representing the percentage of Memory being used. For instance 95 means 95%
|
|
42
|
+
*/
|
|
43
|
+
memory: number | null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A human-readable message as the overall status of the instance.
|
|
47
|
+
*/
|
|
48
|
+
message: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The current number of connect or API calls pending to run.
|
|
52
|
+
*/
|
|
53
|
+
queued: number;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A simple single-word reason as to why an instance may or may not be available.
|
|
57
|
+
*/
|
|
58
|
+
reason: 'full' | 'cpu' | 'memory' | '';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The number of recent connections that were rejected due to the queue and concurrency
|
|
62
|
+
* limits having been filled.
|
|
63
|
+
*/
|
|
64
|
+
recentlyRejected: number;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The current number of running connections or API calls.
|
|
68
|
+
*/
|
|
69
|
+
running: number;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default class PressureGetRoute extends HTTPRoute {
|
|
74
|
+
name = BrowserlessRoutes.PressureGetRoute;
|
|
75
|
+
accepts = [contentTypes.any];
|
|
76
|
+
auth = true;
|
|
77
|
+
browser = null;
|
|
78
|
+
concurrency = false;
|
|
79
|
+
contentTypes = [contentTypes.json];
|
|
80
|
+
description = `Returns a JSON body of stats related to the pressure being created on the instance.`;
|
|
81
|
+
method = Methods.get;
|
|
82
|
+
path = HTTPManagementRoutes.pressure;
|
|
83
|
+
tags = [APITags.management];
|
|
84
|
+
handler = async (_req: Request, res: ServerResponse): Promise<void> => {
|
|
85
|
+
const monitoring = this.monitoring();
|
|
86
|
+
const config = this.config();
|
|
87
|
+
const limiter = this.limiter();
|
|
88
|
+
const metrics = this.metrics();
|
|
89
|
+
|
|
90
|
+
const {
|
|
91
|
+
cpuInt: cpu,
|
|
92
|
+
memoryInt: memory,
|
|
93
|
+
cpuOverloaded,
|
|
94
|
+
memoryOverloaded,
|
|
95
|
+
} = await monitoring.overloaded();
|
|
96
|
+
const date = Date.now();
|
|
97
|
+
const hasCapacity = limiter.hasCapacity;
|
|
98
|
+
const queued = limiter.waiting;
|
|
99
|
+
const isAvailable = hasCapacity && !cpuOverloaded && !memoryOverloaded;
|
|
100
|
+
const running = limiter.executing;
|
|
101
|
+
const recentlyRejected = metrics.get().rejected;
|
|
102
|
+
const maxConcurrent = config.getConcurrent();
|
|
103
|
+
const maxQueued = config.getQueued();
|
|
104
|
+
|
|
105
|
+
const reason = !hasCapacity
|
|
106
|
+
? 'full'
|
|
107
|
+
: cpuOverloaded
|
|
108
|
+
? 'cpu'
|
|
109
|
+
: memoryOverloaded
|
|
110
|
+
? 'memory'
|
|
111
|
+
: '';
|
|
112
|
+
|
|
113
|
+
const message = !hasCapacity
|
|
114
|
+
? 'Concurrency and queue are full'
|
|
115
|
+
: cpuOverloaded
|
|
116
|
+
? 'CPU is over the configured maximum for cpu percent'
|
|
117
|
+
: memoryOverloaded
|
|
118
|
+
? 'Memory is over the configured maximum for memory percent'
|
|
119
|
+
: '';
|
|
120
|
+
|
|
121
|
+
const response: ResponseSchema = {
|
|
122
|
+
pressure: {
|
|
123
|
+
cpu,
|
|
124
|
+
date,
|
|
125
|
+
isAvailable,
|
|
126
|
+
maxConcurrent,
|
|
127
|
+
maxQueued,
|
|
128
|
+
memory,
|
|
129
|
+
message,
|
|
130
|
+
queued,
|
|
131
|
+
reason,
|
|
132
|
+
recentlyRejected,
|
|
133
|
+
running,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return jsonResponse(res, 200, response);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -56,6 +56,19 @@ describe('Management APIs', function () {
|
|
|
56
56
|
);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
it('allows requests to /pressure', async () => {
|
|
60
|
+
await start();
|
|
61
|
+
|
|
62
|
+
await fetch('http://localhost:3000/pressure?token=6R0W53R135510').then(
|
|
63
|
+
async (res) => {
|
|
64
|
+
expect(res.headers.get('content-type')).to.equal(
|
|
65
|
+
'application/json; charset=UTF-8',
|
|
66
|
+
);
|
|
67
|
+
expect(res.status).to.equal(200);
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
59
72
|
it('allows requests to /sessions', async () => {
|
|
60
73
|
await start();
|
|
61
74
|
|
|
@@ -87,13 +100,11 @@ describe('Management APIs', function () {
|
|
|
87
100
|
|
|
88
101
|
await fetch('http://localhost:3000/active?token=6R0W53R135510', {
|
|
89
102
|
method: 'HEAD',
|
|
90
|
-
}).then(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
},
|
|
97
|
-
);
|
|
103
|
+
}).then(async (res) => {
|
|
104
|
+
expect(res.headers.get('content-type')).to.equal(
|
|
105
|
+
'text/plain; charset=UTF-8',
|
|
106
|
+
);
|
|
107
|
+
expect(res.status).to.equal(204);
|
|
108
|
+
});
|
|
98
109
|
});
|
|
99
110
|
});
|
package/src/types.ts
CHANGED
|
@@ -103,6 +103,7 @@ abstract class Route {
|
|
|
103
103
|
protected _metrics: Browserless['metrics'],
|
|
104
104
|
protected _monitoring: Browserless['monitoring'],
|
|
105
105
|
protected _staticSDKDir: Browserless['staticSDKDir'],
|
|
106
|
+
protected _limiter: Browserless['limiter'],
|
|
106
107
|
) {}
|
|
107
108
|
|
|
108
109
|
/**
|
|
@@ -196,6 +197,13 @@ abstract class Route {
|
|
|
196
197
|
*/
|
|
197
198
|
staticSDKDir = () => this._staticSDKDir;
|
|
198
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Helper function that loads the limiter module into the router's
|
|
202
|
+
* handler scope.
|
|
203
|
+
* @returns Limiter
|
|
204
|
+
*/
|
|
205
|
+
limiter = () => this._limiter;
|
|
206
|
+
|
|
199
207
|
/**
|
|
200
208
|
* The HTTP path that this route handles, eg '/my-route' OR an
|
|
201
209
|
* array of paths that this route can handle.
|
|
@@ -650,6 +658,7 @@ export const BrowserlessManagementRoutes = {
|
|
|
650
658
|
ConfigGetRoute: 'ConfigGetRoute',
|
|
651
659
|
MetricsGetRoute: 'MetricsGetRoute',
|
|
652
660
|
MetricsTotalGetRoute: 'MetricsTotalGetRoute',
|
|
661
|
+
PressureGetRoute: 'PressureGetRoute',
|
|
653
662
|
SessionsGetRoute: 'SessionsGetRoute',
|
|
654
663
|
StaticGetRoute: 'StaticGetRoute',
|
|
655
664
|
};
|