Skip to content

Commit 1574679

Browse files
ArturWierzbickiryantxuAgnesToulet
authored
Rendering: Support new concurrency mode: contextPerRenderKey (#314)
* allow scaled thumbnails * added support for full page images * add `acceptBeforeUnload` handler * use png files * produce requested thumbnail size * use browser per domain concurrency * browser per render key * browser per render key * poolpeteer! * merge conflict fixes * add debugging options * update yarnlock * fix: disable `headed` by default * reduce the default worker shutdownTimeout * yarn lock update * add the ability to emulate network conditions * add domain to the groupId * #44449: rollback `mode` changes in default.json/dev.json * #44449: use poolpeteer only for `contextPerRenderKey` * Update package.json Co-authored-by: Agnès Toulet <[email protected]> * Update src/config.ts Co-authored-by: Agnès Toulet <[email protected]> * Update src/config.ts Co-authored-by: Agnès Toulet <[email protected]> * fix: remove `workerCreationDelay` * yarn lock update Co-authored-by: Ryan McKinley <[email protected]> Co-authored-by: Agnès Toulet <[email protected]>
1 parent 614709c commit 1574679

File tree

7 files changed

+840
-804
lines changed

7 files changed

+840
-804
lines changed

default.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
"headed": false,
3434

3535
"mode": "default",
36+
"emulateNetworkConditions": false,
3637
"clustering": {
38+
"monitor": false,
3739
"mode": "browser",
3840
"maxConcurrency": 5,
3941
"timeout": 30

dev.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,16 @@
3333
"headed": false,
3434

3535
"mode": "default",
36+
"emulateNetworkConditions": false,
37+
"networkConditions": {
38+
"downloadThroughput": 4000000,
39+
"uploadThroughput": 4000000,
40+
"latency": 100,
41+
"offline": false
42+
},
43+
3644
"clustering": {
45+
"monitor": true,
3746
"mode": "browser",
3847
"maxConcurrency": 5,
3948
"timeout": 30

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"prettier:write": "prettier --list-different \"**/*.ts\" --write",
1515
"precommit": "npm run eslint & npm run typecheck",
1616
"watch": "tsc-watch --onSuccess \"node build/app.js server --config=dev.json\"",
17+
"watch:debug": "tsc-watch --onSuccess \"cross-env DEBUG=puppeteer-cluster:* node build/app.js server --config=dev.json\"",
1718
"build": "tsc",
1819
"start": "node build/app.js --config=dev.json"
1920
},
@@ -30,8 +31,9 @@
3031
"morgan": "^1.9.0",
3132
"on-finished": "^2.3.0",
3233
"prom-client": "^11.5.3",
33-
"puppeteer": "^10.0.0",
34+
"puppeteer": "^13.1.3",
3435
"puppeteer-cluster": "^0.22.0",
36+
"poolpeteer": "^0.22.0",
3537
"sharp": "0.29.3",
3638
"unique-filename": "^1.1.0",
3739
"winston": "^3.2.1"
@@ -40,6 +42,7 @@
4042
"@grafana/eslint-config": "^2.5.0",
4143
"@types/express": "^4.11.1",
4244
"@types/node": "^14.14.41",
45+
"cross-env": "7.0.3",
4346
"@typescript-eslint/eslint-plugin": "^4.32.0",
4447
"@typescript-eslint/parser": "^4.32.0",
4548
"eslint": "^7.32.0",

src/browser/browser.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@ export class Browser {
148148
}
149149

150150
async preparePage(page: puppeteer.Page, options: RenderOptions) {
151+
if (this.config.emulateNetworkConditions && this.config.networkConditions) {
152+
const client = await page.target().createCDPSession();
153+
await client.send('Network.emulateNetworkConditions', this.config.networkConditions);
154+
}
155+
151156
if (options.renderKey) {
152157
if (this.config.verboseLogging) {
153158
this.log.debug('Setting cookie for page', 'renderKey', options.renderKey, 'domain', options.domain);

src/browser/clustered.ts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Cluster } from 'puppeteer-cluster';
1+
import { Cluster as PoolpeteerCluster } from 'poolpeteer';
2+
import { Cluster as PuppeteerCluster } from 'puppeteer-cluster';
23
import { ImageRenderOptions, RenderOptions } from '../types';
34
import { Browser, RenderResponse, RenderCSVResponse, Metrics } from './browser';
45
import { Logger } from '../logger';
@@ -10,12 +11,17 @@ enum RenderType {
1011
}
1112

1213
interface ClusterOptions {
14+
groupId?: string;
1315
options: RenderOptions | ImageRenderOptions;
1416
renderType: RenderType;
1517
}
1618

1719
type ClusterResponse = RenderResponse | RenderCSVResponse;
1820

21+
const contextPerRenderKey = 'contextPerRenderKey';
22+
23+
type Cluster<JobData = any, ReturnData = any> = PuppeteerCluster<JobData, ReturnData> | PoolpeteerCluster<JobData, ReturnData>;
24+
1925
export class ClusteredBrowser extends Browser {
2026
cluster: Cluster<ClusterOptions, ClusterResponse>;
2127
clusteringConfig: ClusteringConfig;
@@ -25,21 +31,45 @@ export class ClusteredBrowser extends Browser {
2531
super(config, log, metrics);
2632

2733
this.clusteringConfig = config.clustering;
28-
this.concurrency = Cluster.CONCURRENCY_BROWSER;
34+
this.concurrency = PuppeteerCluster.CONCURRENCY_BROWSER;
2935

3036
if (this.clusteringConfig.mode === 'context') {
31-
this.concurrency = Cluster.CONCURRENCY_CONTEXT;
37+
this.concurrency = PuppeteerCluster.CONCURRENCY_CONTEXT;
38+
}
39+
40+
if (this.clusteringConfig.mode === contextPerRenderKey) {
41+
this.concurrency = PoolpeteerCluster.CONCURRENCY_CONTEXT_PER_REQUEST_GROUP;
3242
}
3343
}
3444

35-
async start(): Promise<void> {
45+
shouldUsePoolpeteer(): boolean {
46+
return this.clusteringConfig.mode === contextPerRenderKey;
47+
}
48+
49+
async createCluster(): Promise<Cluster<ClusterOptions, ClusterResponse>> {
3650
const launcherOptions = this.getLauncherOptions({});
37-
this.cluster = await Cluster.launch({
51+
52+
const clusterOptions = {
3853
concurrency: this.concurrency,
54+
workerShutdownTimeout: 5000,
55+
monitor: this.clusteringConfig.monitor,
3956
maxConcurrency: this.clusteringConfig.maxConcurrency,
4057
timeout: this.clusteringConfig.timeout * 1000,
4158
puppeteerOptions: launcherOptions,
42-
});
59+
};
60+
61+
// TODO use poolpeteer by default after initial release and testing (8.5?)
62+
if (this.shouldUsePoolpeteer()) {
63+
this.log.debug('Launching Browser cluster with poolpeteer');
64+
return PoolpeteerCluster.launch(clusterOptions);
65+
}
66+
67+
this.log.debug('Launching Browser cluster with puppeteer-cluster');
68+
return PuppeteerCluster.launch(clusterOptions);
69+
}
70+
71+
async start(): Promise<void> {
72+
this.cluster = await this.createCluster();
4373
await this.cluster.task(async ({ page, data }) => {
4474
if (data.options.timezone) {
4575
// set timezone
@@ -61,13 +91,21 @@ export class ClusteredBrowser extends Browser {
6191
});
6292
}
6393

94+
private getGroupId = (options: ImageRenderOptions | RenderOptions) => {
95+
if (this.clusteringConfig.mode === contextPerRenderKey) {
96+
return `${options.domain}${options.renderKey}`;
97+
}
98+
99+
return undefined;
100+
};
101+
64102
async render(options: ImageRenderOptions): Promise<RenderResponse> {
65103
this.validateImageOptions(options);
66-
return this.cluster.execute({ options, renderType: RenderType.PNG });
104+
return this.cluster.execute({ groupId: this.getGroupId(options), options, renderType: RenderType.PNG });
67105
}
68106

69107
async renderCSV(options: RenderOptions): Promise<RenderCSVResponse> {
70108
this.validateRenderOptions(options);
71-
return this.cluster.execute({ options, renderType: RenderType.CSV });
109+
return this.cluster.execute({ groupId: this.getGroupId(options), options, renderType: RenderType.CSV });
72110
}
73111
}

src/config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import * as fs from 'fs';
22

33
export interface ClusteringConfig {
4+
monitor: boolean;
45
mode: string;
56
maxConcurrency: number;
67
timeout: number;
78
}
89

10+
// https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-emulateNetworkConditions
11+
type NetworkConditions = {
12+
offline: boolean;
13+
downloadThroughput: number;
14+
uploadThroughput: number;
15+
latency: number;
16+
};
17+
918
export interface RenderingConfig {
1019
chromeBin?: string;
1120
args: string[];
@@ -24,6 +33,8 @@ export interface RenderingConfig {
2433
dumpio: boolean;
2534
timingMetrics: boolean;
2635
headed?: boolean;
36+
networkConditions?: NetworkConditions;
37+
emulateNetworkConditions: boolean;
2738
}
2839

2940
export interface MetricsConfig {
@@ -78,10 +89,12 @@ const defaultRenderingConfig: RenderingConfig = {
7889
maxDeviceScaleFactor: 4,
7990
mode: 'default',
8091
clustering: {
92+
monitor: false,
8193
mode: 'browser',
8294
maxConcurrency: 5,
8395
timeout: 30,
8496
},
97+
emulateNetworkConditions: false,
8598
verboseLogging: false,
8699
dumpio: false,
87100
timingMetrics: false,

0 commit comments

Comments
 (0)