@vpalmisano/webrtcperf 4.2.2 → 4.4.1
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/app.min.js +1 -1
- package/build/src/app.d.ts +13 -4
- package/build/src/app.js +161 -124
- package/build/src/app.js.map +1 -1
- package/build/src/config.d.ts +1 -1
- package/build/src/config.js +30 -24
- package/build/src/config.js.map +1 -1
- package/build/src/docker.d.ts +1 -0
- package/build/src/docker.js +123 -0
- package/build/src/docker.js.map +1 -0
- package/build/src/server.js +28 -10
- package/build/src/server.js.map +1 -1
- package/build/src/session.js +12 -5
- package/build/src/session.js.map +1 -1
- package/build/src/stats.d.ts +8 -21
- package/build/src/stats.js +48 -17
- package/build/src/stats.js.map +1 -1
- package/build/src/utils.d.ts +6 -2
- package/build/src/utils.js +31 -24
- package/build/src/utils.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +28 -26
- package/src/app.ts +171 -137
- package/src/config.ts +28 -23
- package/src/docker.ts +131 -0
- package/src/server.ts +33 -13
- package/src/session.ts +12 -5
- package/src/stats.ts +56 -25
- package/src/utils.ts +32 -25
package/src/server.ts
CHANGED
|
@@ -11,10 +11,12 @@ import { WebSocketServer } from 'ws'
|
|
|
11
11
|
import zlib from 'zlib'
|
|
12
12
|
import auth from 'basic-auth'
|
|
13
13
|
|
|
14
|
-
import { loadConfig } from './config'
|
|
14
|
+
import { Config, loadConfig } from './config'
|
|
15
15
|
import { Session, SessionParams } from './session'
|
|
16
16
|
import { Stats } from './stats'
|
|
17
17
|
import { logger, runShellCommand, getDockerLogsPath } from './utils'
|
|
18
|
+
import { getSessionThrottleIndex } from '@vpalmisano/throttler'
|
|
19
|
+
import { MediaPath, prepareFakeMedia } from './media'
|
|
18
20
|
|
|
19
21
|
const log = logger('webrtcperf:server')
|
|
20
22
|
|
|
@@ -272,7 +274,8 @@ export class Server {
|
|
|
272
274
|
private async putSession(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
|
273
275
|
log.debug(`PUT /session`, req.body)
|
|
274
276
|
try {
|
|
275
|
-
const
|
|
277
|
+
const config = req.body as Config
|
|
278
|
+
const id = this.stats.consumeSessionId(config.tabsPerSession)
|
|
276
279
|
await this.startLocalSession(id, req.body)
|
|
277
280
|
res.json({
|
|
278
281
|
message: `Session created`,
|
|
@@ -293,10 +296,10 @@ export class Server {
|
|
|
293
296
|
private async putSessions(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
|
|
294
297
|
log.debug(`PUT /sessions`, req.body)
|
|
295
298
|
try {
|
|
296
|
-
const { sessions } = req.body
|
|
299
|
+
const { sessions, tabsPerSession } = req.body as Config
|
|
297
300
|
const sessionsIds = []
|
|
298
301
|
for (let i = 0; i < sessions; i++) {
|
|
299
|
-
const id = this.stats.
|
|
302
|
+
const id = this.stats.consumeSessionId(tabsPerSession)
|
|
300
303
|
await this.startLocalSession(id, req.body)
|
|
301
304
|
sessionsIds.push(id)
|
|
302
305
|
}
|
|
@@ -471,12 +474,25 @@ export class Server {
|
|
|
471
474
|
* @param config The session configuration.
|
|
472
475
|
*/
|
|
473
476
|
private async startLocalSession(id: number, config: SessionParams): Promise<Session> {
|
|
474
|
-
|
|
475
|
-
const sessionConfig =
|
|
476
|
-
const
|
|
477
|
+
const configs = await loadConfig(undefined, config)
|
|
478
|
+
const sessionConfig = configs[0]
|
|
479
|
+
const throttleIndex = getSessionThrottleIndex(id)
|
|
480
|
+
const spawnPeriod = 1000 / sessionConfig.spawnRate
|
|
481
|
+
|
|
482
|
+
// Prepare fake video and audio.
|
|
483
|
+
const mediaPaths: MediaPath[] = []
|
|
484
|
+
if (sessionConfig.videoPath) {
|
|
485
|
+
for (const videoPath of sessionConfig.videoPath.split(',')) {
|
|
486
|
+
const ret = await prepareFakeMedia({ ...sessionConfig, videoPath })
|
|
487
|
+
mediaPaths.push(ret)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const mediaPath = mediaPaths.length ? mediaPaths[id % mediaPaths.length] : undefined
|
|
491
|
+
|
|
492
|
+
const session = new Session({ ...sessionConfig, throttleIndex, spawnPeriod, mediaPath, id })
|
|
477
493
|
session.once('stop', () => {
|
|
478
494
|
console.warn(`Session ${id} stopped, reloading...`)
|
|
479
|
-
setTimeout(this.startLocalSession.bind(this),
|
|
495
|
+
setTimeout(this.startLocalSession.bind(this), spawnPeriod, id, config)
|
|
480
496
|
})
|
|
481
497
|
this.stats.addSession(session)
|
|
482
498
|
try {
|
|
@@ -510,13 +526,17 @@ export class Server {
|
|
|
510
526
|
log.debug('start')
|
|
511
527
|
if (this.serverUseHttps) {
|
|
512
528
|
const destDir = path.join(os.homedir(), '.webrtcperf/ssl')
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
)
|
|
529
|
+
const keyPath = path.join(destDir, 'domain.key')
|
|
530
|
+
const crtPath = path.join(destDir, 'domain.crt')
|
|
531
|
+
if (!fs.existsSync(keyPath) || !fs.existsSync(crtPath)) {
|
|
532
|
+
await runShellCommand(
|
|
533
|
+
`mkdir -p ${destDir} && openssl req -newkey rsa:2048 -nodes -keyout ${keyPath} -x509 -days 365 -out ${crtPath} -subj "/C=EU/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com"`,
|
|
534
|
+
)
|
|
535
|
+
}
|
|
516
536
|
this.server = _createServer(
|
|
517
537
|
{
|
|
518
|
-
key: fs.readFileSync(
|
|
519
|
-
cert: fs.readFileSync(
|
|
538
|
+
key: fs.readFileSync(keyPath),
|
|
539
|
+
cert: fs.readFileSync(crtPath),
|
|
520
540
|
},
|
|
521
541
|
this.app,
|
|
522
542
|
)
|
package/src/session.ts
CHANGED
|
@@ -676,7 +676,7 @@ export class Session extends EventEmitter {
|
|
|
676
676
|
deviceScaleFactor: this.deviceScaleFactor,
|
|
677
677
|
isMobile: false,
|
|
678
678
|
hasTouch: false,
|
|
679
|
-
isLandscape:
|
|
679
|
+
isLandscape: true,
|
|
680
680
|
},
|
|
681
681
|
})
|
|
682
682
|
} catch (err) {
|
|
@@ -803,7 +803,7 @@ try {
|
|
|
803
803
|
} catch (err) {
|
|
804
804
|
console.error('[webrtcperf] Error parsing scriptParams:', err);
|
|
805
805
|
webrtcperf.params = {};
|
|
806
|
-
}
|
|
806
|
+
};
|
|
807
807
|
`
|
|
808
808
|
|
|
809
809
|
if (this.serverPort) {
|
|
@@ -910,15 +910,22 @@ webrtcperf.config.AUDIO_URL = "http${this.serverUseHttps ? 's' : ''}://localhost
|
|
|
910
910
|
if (this.localStorage) {
|
|
911
911
|
log.debug('Using localStorage:', this.localStorage)
|
|
912
912
|
Object.entries(this.localStorage).map(([key, value]) => {
|
|
913
|
-
cmd += `localStorage.setItem('${key}',
|
|
913
|
+
cmd += `window.localStorage.setItem('${key}', ${JSON.stringify(value)});\n`
|
|
914
914
|
})
|
|
915
915
|
}
|
|
916
916
|
if (this.sessionStorage) {
|
|
917
917
|
log.debug('Using sessionStorage:', this.sessionStorage)
|
|
918
918
|
Object.entries(this.sessionStorage).map(([key, value]) => {
|
|
919
|
-
cmd += `sessionStorage.setItem('${key}',
|
|
919
|
+
cmd += `window.sessionStorage.setItem('${key}', ${JSON.stringify(value)});\n`
|
|
920
920
|
})
|
|
921
921
|
}
|
|
922
|
+
cmd += `
|
|
923
|
+
Object.defineProperty(window.screen, 'width', { value: ${this.windowWidth}, writable: false });
|
|
924
|
+
Object.defineProperty(window.screen, 'height', { value: ${this.windowHeight}, writable: false });
|
|
925
|
+
Object.defineProperty(window.screen, 'availWidth', { value: ${this.windowWidth}, writable: false });
|
|
926
|
+
Object.defineProperty(window.screen, 'availHeight', { value: ${this.windowHeight}, writable: false });
|
|
927
|
+
Object.defineProperty(window.screen.orientation, 'type', { value: 'landscape-primary', writable: false });
|
|
928
|
+
`
|
|
922
929
|
log.debug('init command:', cmd)
|
|
923
930
|
await page.evaluateOnNewDocument(cmd)
|
|
924
931
|
|
|
@@ -1305,7 +1312,7 @@ webrtcperf.config.AUDIO_URL = "http${this.serverUseHttps ? 's' : ''}://localhost
|
|
|
1305
1312
|
if (this.showPageLog || saveFile) {
|
|
1306
1313
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1307
1314
|
page.on('pageerror', async (error: any) => {
|
|
1308
|
-
const text = `pageerror: ${error
|
|
1315
|
+
const text = `pageerror: ${error?.message?.message || error?.message || error} - ${error?.message?.stack || error?.stack}`
|
|
1309
1316
|
await this.onPageMessage(index, 'error', text, saveFile)
|
|
1310
1317
|
})
|
|
1311
1318
|
|
package/src/stats.ts
CHANGED
|
@@ -32,23 +32,19 @@ function calculateFailAmountPercentile(stat: FastStats, percentile = 95): number
|
|
|
32
32
|
class StatsWriter {
|
|
33
33
|
fname: string
|
|
34
34
|
columns: string[]
|
|
35
|
-
private
|
|
35
|
+
private headerWritten = false
|
|
36
36
|
|
|
37
37
|
constructor(fname = 'stats.log', columns: string[]) {
|
|
38
38
|
this.fname = fname
|
|
39
39
|
this.columns = columns
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* @param dataColumns
|
|
45
|
-
*/
|
|
46
|
-
async push(dataColumns: string[]): Promise<void> {
|
|
47
|
-
if (!this._header_written) {
|
|
42
|
+
async push(dataColumns: string[], append = true): Promise<void> {
|
|
43
|
+
if (!this.headerWritten || !append) {
|
|
48
44
|
const data = ['datetime', ...this.columns].join(',') + '\n'
|
|
49
45
|
await fs.promises.mkdir(path.dirname(this.fname), { recursive: true })
|
|
50
46
|
await fs.promises.writeFile(this.fname, data)
|
|
51
|
-
this.
|
|
47
|
+
this.headerWritten = true
|
|
52
48
|
}
|
|
53
49
|
//
|
|
54
50
|
const data = [Date.now(), ...dataColumns].join(',') + '\n'
|
|
@@ -330,6 +326,7 @@ export class Stats extends events.EventEmitter {
|
|
|
330
326
|
nextSessionId: number
|
|
331
327
|
statsWriter: StatsWriter | null
|
|
332
328
|
detailedStatsWriter: StatsWriter | null
|
|
329
|
+
detailedStatsSummaryWriter: StatsWriter | null
|
|
333
330
|
private scheduler?: Scheduler
|
|
334
331
|
|
|
335
332
|
private alertRules: Record<string, AlertRule> | null = null
|
|
@@ -386,18 +383,18 @@ export class Stats extends events.EventEmitter {
|
|
|
386
383
|
|
|
387
384
|
collectedStats: Record<string, CollectedStats>
|
|
388
385
|
|
|
389
|
-
collectedStatsConfig = {
|
|
386
|
+
private collectedStatsConfig = {
|
|
390
387
|
url: '',
|
|
391
388
|
pages: 0,
|
|
392
389
|
startTime: 0,
|
|
393
390
|
}
|
|
394
|
-
externalCollectedStats = new Map<
|
|
391
|
+
private externalCollectedStats = new Map<
|
|
395
392
|
string,
|
|
396
393
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
397
394
|
{ addedTime: number; externalStats: any; config: any }
|
|
398
395
|
>()
|
|
399
|
-
pushStatsInstance: axios.AxiosInstance | null = null
|
|
400
|
-
|
|
396
|
+
private pushStatsInstance: axios.AxiosInstance | null = null
|
|
397
|
+
private detailedStatsSummary: Record<string, Record<string, FastStats>> = {}
|
|
401
398
|
private running = false
|
|
402
399
|
|
|
403
400
|
/**
|
|
@@ -485,6 +482,7 @@ export class Stats extends events.EventEmitter {
|
|
|
485
482
|
|
|
486
483
|
this.statsWriter = null
|
|
487
484
|
this.detailedStatsWriter = null
|
|
485
|
+
this.detailedStatsSummaryWriter = null
|
|
488
486
|
if (alertRules.trim()) {
|
|
489
487
|
this.alertRules = json5.parse(alertRules)
|
|
490
488
|
log.debug(`using alertRules: ${JSON.stringify(this.alertRules, undefined, 2)}`)
|
|
@@ -615,6 +613,11 @@ export class Stats extends events.EventEmitter {
|
|
|
615
613
|
'trackId',
|
|
616
614
|
...this.statsNames,
|
|
617
615
|
])
|
|
616
|
+
this.detailedStatsSummaryWriter = new StatsWriter(this.detailedStatsPath.replace(/\.(.+)$/, '-summary.$1'), [
|
|
617
|
+
'participantName',
|
|
618
|
+
'trackId',
|
|
619
|
+
...this.statsNames,
|
|
620
|
+
])
|
|
618
621
|
}
|
|
619
622
|
|
|
620
623
|
if (this.prometheusPushgateway) {
|
|
@@ -821,8 +824,7 @@ export class Stats extends events.EventEmitter {
|
|
|
821
824
|
collectedStats.all.push(obj)
|
|
822
825
|
} else {
|
|
823
826
|
for (const [key, value] of Object.entries(obj)) {
|
|
824
|
-
|
|
825
|
-
if (typeof value === 'number' && isFinite(value as any)) {
|
|
827
|
+
if (typeof value === 'number' && isFinite(value)) {
|
|
826
828
|
collectedStats.all.push(value)
|
|
827
829
|
// Push host label.
|
|
828
830
|
const { trackId, hostName, participantName } = parseRtStatKey(key)
|
|
@@ -955,7 +957,7 @@ export class Stats extends events.EventEmitter {
|
|
|
955
957
|
|
|
956
958
|
async writeDetailedStats() {
|
|
957
959
|
if (!this.detailedStatsWriter) return
|
|
958
|
-
const participantTrackStats = new Map<string, Record<string,
|
|
960
|
+
const participantTrackStats = new Map<string, Record<string, number>>()
|
|
959
961
|
Object.entries(this.collectedStats).forEach(([name, stats]) => {
|
|
960
962
|
Object.entries(stats.byParticipantAndTrack).forEach(([label, value]) => {
|
|
961
963
|
let stats = participantTrackStats.get(label)
|
|
@@ -963,19 +965,47 @@ export class Stats extends events.EventEmitter {
|
|
|
963
965
|
stats = {}
|
|
964
966
|
participantTrackStats.set(label, stats)
|
|
965
967
|
}
|
|
966
|
-
stats[name] =
|
|
968
|
+
stats[name] = value
|
|
967
969
|
})
|
|
968
970
|
})
|
|
969
971
|
for (const [label, trackStats] of participantTrackStats.entries()) {
|
|
970
972
|
const [participantName, trackId] = label.split(':', 2)
|
|
973
|
+
if (!this.detailedStatsSummary[label]) {
|
|
974
|
+
this.detailedStatsSummary[label] = {}
|
|
975
|
+
}
|
|
976
|
+
const summary = this.detailedStatsSummary[label]
|
|
971
977
|
const values = [participantName, trackId]
|
|
972
978
|
for (const name of this.statsNames) {
|
|
973
|
-
values.push(trackStats[name]
|
|
979
|
+
values.push(trackStats[name] !== undefined ? toPrecision(trackStats[name], 6) : '')
|
|
980
|
+
// Update the summary stats.
|
|
981
|
+
if (!summary[name]) {
|
|
982
|
+
summary[name] = new FastStats({ store_data: false })
|
|
983
|
+
}
|
|
984
|
+
const stat = summary[name]
|
|
985
|
+
if (trackStats[name] !== undefined) {
|
|
986
|
+
stat.push(trackStats[name])
|
|
987
|
+
}
|
|
974
988
|
}
|
|
975
989
|
await this.detailedStatsWriter.push(values)
|
|
976
990
|
}
|
|
977
991
|
}
|
|
978
992
|
|
|
993
|
+
async writeDetailedStatsSummary() {
|
|
994
|
+
if (!this.detailedStatsSummaryWriter) return
|
|
995
|
+
let append = false
|
|
996
|
+
for (const label of Object.keys(this.detailedStatsSummary)) {
|
|
997
|
+
const summary = this.detailedStatsSummary[label]
|
|
998
|
+
const [participantName, trackId] = label.split(':', 2)
|
|
999
|
+
const values = [participantName, trackId]
|
|
1000
|
+
for (const name of this.statsNames) {
|
|
1001
|
+
const stat = summary[name]
|
|
1002
|
+
values.push(stat?.length > 0 ? toPrecision(stat.amean(), 6) : '')
|
|
1003
|
+
}
|
|
1004
|
+
await this.detailedStatsSummaryWriter.push(values, append)
|
|
1005
|
+
append = true
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
979
1009
|
/**
|
|
980
1010
|
* addCollectedStats
|
|
981
1011
|
* @param id
|
|
@@ -1652,17 +1682,13 @@ export class Stats extends events.EventEmitter {
|
|
|
1652
1682
|
}
|
|
1653
1683
|
}
|
|
1654
1684
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
*/
|
|
1658
|
-
async stop(): Promise<void> {
|
|
1659
|
-
if (!this.running) {
|
|
1660
|
-
return
|
|
1661
|
-
}
|
|
1685
|
+
async stop() {
|
|
1686
|
+
if (!this.running) return
|
|
1662
1687
|
this.running = false
|
|
1663
1688
|
log.debug('stop')
|
|
1689
|
+
|
|
1664
1690
|
if (this.scheduler) {
|
|
1665
|
-
this.scheduler.stop()
|
|
1691
|
+
await this.scheduler.stop()
|
|
1666
1692
|
this.scheduler = undefined
|
|
1667
1693
|
}
|
|
1668
1694
|
|
|
@@ -1676,7 +1702,12 @@ export class Stats extends events.EventEmitter {
|
|
|
1676
1702
|
}
|
|
1677
1703
|
this.sessions.clear()
|
|
1678
1704
|
|
|
1705
|
+
await this.writeDetailedStatsSummary()
|
|
1706
|
+
|
|
1679
1707
|
this.statsWriter = null
|
|
1708
|
+
this.detailedStatsWriter = null
|
|
1709
|
+
this.detailedStatsSummaryWriter = null
|
|
1710
|
+
this.detailedStatsSummary = {}
|
|
1680
1711
|
|
|
1681
1712
|
// delete metrics
|
|
1682
1713
|
if (this.gateway) {
|
package/src/utils.ts
CHANGED
|
@@ -291,7 +291,6 @@ export async function randomActivateAudio(
|
|
|
291
291
|
pages = pages.concat(sessionPages)
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
|
-
// Remove pages with no audio tracks.
|
|
295
294
|
for (const [i, page] of pages.entries()) {
|
|
296
295
|
if (!page) {
|
|
297
296
|
continue
|
|
@@ -307,15 +306,8 @@ export async function randomActivateAudio(
|
|
|
307
306
|
}
|
|
308
307
|
}
|
|
309
308
|
const pagesWithAudio: Page[] = pages.filter(p => !!p)
|
|
310
|
-
//
|
|
311
309
|
const index = Math.floor(Math.random() * pagesWithAudio.length)
|
|
312
310
|
const enable = Math.round(100 * Math.random()) <= randomAudioProbability
|
|
313
|
-
log.debug('randomActivateAudio %j', {
|
|
314
|
-
pages: pagesWithAudio.length,
|
|
315
|
-
randomAudioProbability,
|
|
316
|
-
index,
|
|
317
|
-
enable,
|
|
318
|
-
})
|
|
319
311
|
for (const [i, page] of pagesWithAudio.entries()) {
|
|
320
312
|
try {
|
|
321
313
|
if (i === index) {
|
|
@@ -613,7 +605,8 @@ SIGNALS.forEach(event =>
|
|
|
613
605
|
export async function checkChromeExecutable(): Promise<string> {
|
|
614
606
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
615
607
|
const { loadConfig } = require('./config')
|
|
616
|
-
const
|
|
608
|
+
const configs = await loadConfig()
|
|
609
|
+
const config = configs[0]
|
|
617
610
|
const cacheDir = path.join(os.homedir(), '.webrtcperf/chrome')
|
|
618
611
|
|
|
619
612
|
const fixSemVer = (v: string) => v.split('.').slice(0, 3).join('.')
|
|
@@ -658,28 +651,37 @@ export async function runShellCommand(
|
|
|
658
651
|
cmd: string,
|
|
659
652
|
verbose = false,
|
|
660
653
|
maxBuffer = 1024 * 1024,
|
|
654
|
+
{ provideStdin = false, returnStdout = true, returnStderr = true } = {},
|
|
661
655
|
): Promise<{ stdout: string; stderr: string }> {
|
|
662
656
|
if (verbose) log.debug(`runShellCommand cmd: ${cmd}`)
|
|
663
657
|
return new Promise((resolve, reject) => {
|
|
664
658
|
const p = spawn(cmd, {
|
|
665
659
|
shell: true,
|
|
666
|
-
stdio: [
|
|
660
|
+
stdio: [
|
|
661
|
+
provideStdin ? 'inherit' : 'ignore',
|
|
662
|
+
returnStdout ? 'pipe' : 'inherit',
|
|
663
|
+
returnStderr ? 'pipe' : 'inherit',
|
|
664
|
+
],
|
|
667
665
|
detached: true,
|
|
668
666
|
})
|
|
669
667
|
let stdout = ''
|
|
670
668
|
let stderr = ''
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
669
|
+
if (returnStdout) {
|
|
670
|
+
p.stdout?.on('data', data => {
|
|
671
|
+
if (maxBuffer && stdout.length > maxBuffer) {
|
|
672
|
+
stdout = stdout.slice(data.length)
|
|
673
|
+
}
|
|
674
|
+
stdout += data
|
|
675
|
+
})
|
|
676
|
+
}
|
|
677
|
+
if (returnStderr) {
|
|
678
|
+
p.stderr?.on('data', data => {
|
|
679
|
+
if (maxBuffer && stderr.length > maxBuffer) {
|
|
680
|
+
stderr = stderr.slice(data.length)
|
|
681
|
+
}
|
|
682
|
+
stderr += data
|
|
683
|
+
})
|
|
684
|
+
}
|
|
683
685
|
p.once('error', err => reject(err))
|
|
684
686
|
p.once('close', code => {
|
|
685
687
|
if (code !== 0) {
|
|
@@ -806,21 +808,26 @@ export class Scheduler {
|
|
|
806
808
|
log.debug(`[${this.name}-scheduler] constructor interval=${this.interval}ms`)
|
|
807
809
|
}
|
|
808
810
|
|
|
809
|
-
start()
|
|
811
|
+
start() {
|
|
810
812
|
log.debug(`[${this.name}-scheduler] start`)
|
|
811
813
|
this.running = true
|
|
812
814
|
this.scheduleNext()
|
|
813
815
|
}
|
|
814
816
|
|
|
815
|
-
stop()
|
|
817
|
+
async stop() {
|
|
816
818
|
log.debug(`[${this.name}-scheduler] stop`)
|
|
817
819
|
this.running = false
|
|
818
820
|
if (this.statsTimeoutId) {
|
|
819
821
|
clearTimeout(this.statsTimeoutId)
|
|
820
822
|
}
|
|
823
|
+
try {
|
|
824
|
+
await this.callback(Date.now())
|
|
825
|
+
} catch (err) {
|
|
826
|
+
log.error(`[${this.name}-scheduler] stop callback error: ${(err as Error).stack}`, err)
|
|
827
|
+
}
|
|
821
828
|
}
|
|
822
829
|
|
|
823
|
-
private scheduleNext()
|
|
830
|
+
private scheduleNext() {
|
|
824
831
|
if (!this.running) {
|
|
825
832
|
return
|
|
826
833
|
}
|