diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index 0393ac9e56..507bdf1cfe 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -829,6 +829,7 @@ function _emitMetrics(request, status, stats, timings) { stop_on_first_error: request.stopOnFirstError ? 'true' : 'false', passes, type: request.syncType, + png2pdf: request.png2pdf ? 'true' : 'false', }) if (timings.sync != null) { diff --git a/services/clsi/app/js/LatexMetrics.js b/services/clsi/app/js/LatexMetrics.js index 1783064324..182b124ac8 100644 --- a/services/clsi/app/js/LatexMetrics.js +++ b/services/clsi/app/js/LatexMetrics.js @@ -1,3 +1,5 @@ +import Path from 'node:path' + /** * Converts a time string in seconds to an integer number of milliseconds. * @@ -128,8 +130,11 @@ const LATEX_MK_METRICS_STDERR = [ let type = match[1] const timeMs = parseInt(match[2], 10) const filename = match[3] + const extension = Path.extname(filename).toLowerCase() - if (type === 'PNG') { + if (type === 'PDF' && extension === '.png') { + type = `PNG-png2pdf` + } else if (type === 'PNG') { const pngCategory = pngCategoriesByFile.get(filename) if (pngCategory != null) { type = `PNG-${pngCategory}` diff --git a/services/clsi/app/js/Metrics.js b/services/clsi/app/js/Metrics.js index 6ec402a6a0..f77c944dd0 100644 --- a/services/clsi/app/js/Metrics.js +++ b/services/clsi/app/js/Metrics.js @@ -18,6 +18,7 @@ const compilesTotal = new prom.Counter({ 'stop_on_first_error', 'passes', 'type', + 'png2pdf', ], }) diff --git a/services/clsi/test/unit/js/LatexMetrics.test.js b/services/clsi/test/unit/js/LatexMetrics.test.js index a63fff3339..0b4d334114 100644 --- a/services/clsi/test/unit/js/LatexMetrics.test.js +++ b/services/clsi/test/unit/js/LatexMetrics.test.js @@ -3,7 +3,7 @@ import path from 'node:path' import { expect, describe, beforeEach, it } from 'vitest' import LatexMetrics from '../../../app/js/LatexMetrics.js' -const { addLatexFdbMetrics } = LatexMetrics +const { addLatexFdbMetrics, addLatexMkMetrics } = LatexMetrics describe('LatexMetrics', function () { describe('addLatexFdbMetrics', function () { @@ -111,4 +111,115 @@ describe('LatexMetrics', function () { }) }) }) + + describe('addLatexMkMetrics', function () { + beforeEach(function (ctx) { + ctx.stats = {} + Object.defineProperty(ctx.stats, 'latexmk', { + value: {}, + enumerable: false, + }) + }) + + describe('latexmk-img-times', function () { + it('should parse image timing information from stderr', function (ctx) { + const output = { + stderr: + 'Image written (PNG, 100 ms): output/image1.png\n' + + 'Image written (JPG, 50 ms): output/image2.jpg\n', + } + addLatexMkMetrics(output, ctx.stats) + + expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([ + { type: 'PNG', time_ms: 100 }, + { type: 'JPG', time_ms: 50 }, + ]) + }) + + it('should convert PDF type with .png extension to PNG-png2pdf', function (ctx) { + const output = { + stderr: 'Image written (PDF, 150 ms): image.png\n', + } + addLatexMkMetrics(output, ctx.stats) + + expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([ + { type: 'PNG-png2pdf', time_ms: 150 }, + ]) + }) + + it('should handle uppercase .PNG extension', function (ctx) { + const output = { + stderr: 'Image written (PDF, 200 ms): image.PNG\n', + } + addLatexMkMetrics(output, ctx.stats) + + expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([ + { type: 'PNG-png2pdf', time_ms: 200 }, + ]) + }) + + it('should keep PDF type for non-png extensions', function (ctx) { + const output = { + stderr: + 'Image written (PDF, 100 ms): document.pdf\n' + + 'Image written (PDF, 80 ms): image.jpg\n', + } + addLatexMkMetrics(output, ctx.stats) + + expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([ + { type: 'PDF', time_ms: 180 }, + ]) + }) + + it('should accumulate timing for the same type', function (ctx) { + const output = { + stderr: + 'Image written (PDF, 100 ms): image1.png\n' + + 'Image written (PDF, 150 ms): image2.png\n' + + 'Image written (PNG, 50 ms): image3.png\n', + } + addLatexMkMetrics(output, ctx.stats) + + const result = ctx.stats.latexmk['latexmk-img-times'].sort((a, b) => + a.type.localeCompare(b.type) + ) + expect(result).to.deep.equal([ + { type: 'PNG', time_ms: 50 }, + { type: 'PNG-png2pdf', time_ms: 250 }, + ]) + }) + + it('should handle PNG with category from PNG copy skipped', function (ctx) { + const output = { + stderr: + 'PNG copy skipped (alpha): image1.png\n' + + 'Image written (PNG, 75 ms): image1.png\n', + } + addLatexMkMetrics(output, ctx.stats) + + expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([ + { type: 'PNG-alpha', time_ms: 75 }, + ]) + }) + + it('should mix PDF-to-png2pdf and PNG types', function (ctx) { + const output = { + stderr: + 'Image written (PDF, 100 ms): converted.png\n' + + 'Image written (PNG, 50 ms): native.png\n' + + 'Image written (JPG, 30 ms): photo.jpg\n', + } + addLatexMkMetrics(output, ctx.stats) + + const result = ctx.stats.latexmk['latexmk-img-times'].sort((a, b) => + a.type.localeCompare(b.type) + ) + expect(result).to.deep.equal([ + { type: 'JPG', time_ms: 30 }, + { type: 'PNG', time_ms: 50 }, + { type: 'PNG-png2pdf', time_ms: 100 }, + ]) + }) + }) + }) })