From 82eeb77ec2d3e82ba8589956559e68eaddce5e24 Mon Sep 17 00:00:00 2001 From: Krzysztof Kowalczyk Date: Mon, 21 Oct 2024 21:15:58 +0200 Subject: [PATCH] [Screenshotting/PDF Worker Thread] Add type to GeneratePdfResponse (#196860) ## Summary This PR adds a `type` to `GeneratePdfResponse` and introduces more logging for PDF worker. Closes: #194493 (cherry picked from commit c25a97bc0a3a85ad5c7bb18aab7916410efd05b7) --- .../server/formats/pdf/pdf_maker/pdfmaker.ts | 35 +++++++-- .../server/formats/pdf/pdf_maker/worker.ts | 78 +++++++++++++++---- 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/pdfmaker.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/pdfmaker.ts index 2eda655509f71..65765d8fc78c8 100644 --- a/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/pdfmaker.ts +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/pdfmaker.ts @@ -22,7 +22,12 @@ import { } from './constants'; import { REPORTING_TABLE_LAYOUT } from './get_doc_options'; import { getFont } from './get_font'; -import type { GeneratePdfRequest, GeneratePdfResponse, WorkerData } from './worker'; +import { + GeneratePdfResponseType, + type GeneratePdfRequest, + type WorkerData, + GeneratePdfResponse, +} from './worker'; // Ensure that all dependencies are included in the release bundle. import './worker_dependencies'; @@ -32,6 +37,8 @@ export class PdfMaker { private content: Content[]; private worker?: Worker; + private workerLogger: Logger; + private pageCount: number = 0; private transferList: ArrayBuffer[] = []; @@ -71,6 +78,7 @@ export class PdfMaker { ) { this.title = ''; this.content = []; + this.workerLogger = logger.get('pdf-worker'); // running in dist: `worker.ts` becomes `worker.js` // running in source: `worker_src_harness.ts` needs to be wrapped in JS and have a ts-node environment initialized. @@ -209,26 +217,37 @@ export class PdfMaker { const { port1: myPort, port2: theirPort } = new MessageChannel(); this.worker = this.createWorker(theirPort); this.worker.on('error', (workerError: NodeJS.ErrnoException) => { + this.workerLogger.error(`Worker error: ${workerError}`); if (workerError.code === 'ERR_WORKER_OUT_OF_MEMORY') { reject(new errors.PdfWorkerOutOfMemoryError(workerError.message)); } else { reject(workerError); } }); - this.worker.on('exit', () => {}); + this.worker.on('exit', () => { + this.workerLogger.debug('Worker exited'); + }); - // We expect one message from the worker generating the PDF buffer. - myPort.on('message', ({ error, data }: GeneratePdfResponse) => { - if (error) { + myPort.on('message', ({ type, error, data, message }: GeneratePdfResponse) => { + if (type === GeneratePdfResponseType.Log && message) { + this.workerLogger.debug(message); + return; + } + + if (type === GeneratePdfResponseType.Error) { reject(new Error(`PDF worker returned the following error: ${error}`)); return; } - if (!data) { + + if (type === GeneratePdfResponseType.Data && !data) { reject(new Error(`Worker did not generate a PDF!`)); return; } - this.pageCount = data.metrics.pages; - resolve(data.buffer); + + if (data) { + this.pageCount = data.metrics.pages; + resolve(data.buffer); + } }); // Send the request diff --git a/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker.ts b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker.ts index 98c10598703ae..2599a57cdac97 100644 --- a/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker.ts +++ b/x-pack/plugins/screenshotting/server/formats/pdf/pdf_maker/worker.ts @@ -34,23 +34,44 @@ export interface GeneratePdfRequest { data: GenerateReportRequestData; } -export type GeneratePdfResponse = SuccessResponse | ErrorResponse; - -export interface SuccessResponse { - error?: undefined; - data: { - buffer: Uint8Array; - metrics: { - pages: number; - }; +export interface GeneratePdfData { + buffer: Uint8Array; + metrics: { + pages: number; }; } -export interface ErrorResponse { - error: string; - data: null; +export enum GeneratePdfResponseType { + Log, + Data, + Error, +} + +interface GeneratePdfLogResponse { + type: GeneratePdfResponseType.Log; + data?: GeneratePdfData; + error?: string; + message?: string; +} + +interface GeneratePdfDataResponse { + type: GeneratePdfResponseType.Data; + data?: GeneratePdfData; + error?: string; + message?: string; } +interface GeneratePdfErrorResponse { + type: GeneratePdfResponseType.Error; + data?: GeneratePdfData; + error?: string; + message?: string; +} +export type GeneratePdfResponse = + | GeneratePdfLogResponse + | GeneratePdfDataResponse + | GeneratePdfErrorResponse; + if (!isMainThread) { const { port } = workerData as WorkerData; port.on('message', execute); @@ -68,6 +89,11 @@ const getPageCount = (pdfDoc: PDFKit.PDFDocument): number => { async function execute({ data: { layout, logo, title, content } }: GeneratePdfRequest) { const { port } = workerData as WorkerData; + port.postMessage({ + type: GeneratePdfResponseType.Log, + message: 'Starting execution', + }); + try { const tableBorderWidth = 1; @@ -89,6 +115,11 @@ async function execute({ data: { layout, logo, title, content } }: GeneratePdfRe }, }; + port.postMessage({ + type: GeneratePdfResponseType.Log, + message: 'Initializing PDF printer', + }); + const printer = new Printer(fonts); const docDefinition = _.assign(getTemplate(layout, logo, title, tableBorderWidth, assetPath), { @@ -103,6 +134,11 @@ async function execute({ data: { layout, logo, title, content } }: GeneratePdfRe ), }); + port.postMessage({ + type: GeneratePdfResponseType.Log, + message: 'Generating document stream', + }); + const pdfDoc = printer.createPdfKitDocument(docDefinition, { tableLayouts: { noBorder: { @@ -121,6 +157,11 @@ async function execute({ data: { layout, logo, title, content } }: GeneratePdfRe throw new Error('Document stream has not been generated'); } + port.postMessage({ + type: GeneratePdfResponseType.Log, + message: 'Document stream has been generated', + }); + const buffer = await new Promise((resolve, reject) => { const buffers: Buffer[] = []; pdfDoc.on('error', reject); @@ -133,7 +174,13 @@ async function execute({ data: { layout, logo, title, content } }: GeneratePdfRe pdfDoc.end(); }); - const successResponse: SuccessResponse = { + port.postMessage({ + type: GeneratePdfResponseType.Log, + message: 'PDF buffer has been generated', + }); + + const successResponse: GeneratePdfResponse = { + type: GeneratePdfResponseType.Data, data: { buffer, metrics: { @@ -143,7 +190,10 @@ async function execute({ data: { layout, logo, title, content } }: GeneratePdfRe }; port.postMessage(successResponse, [buffer.buffer /* Transfer buffer instead of copying */]); } catch (error) { - const errorResponse: ErrorResponse = { error: error.message, data: null }; + const errorResponse: GeneratePdfResponse = { + type: GeneratePdfResponseType.Error, + error: error.message, + }; port.postMessage(errorResponse); } finally { process.nextTick(() => {