Merge pull request #35966 from overleaf/bg-return-optimised-image-stats-and-timings

return image stats and timings in compile response

GitOrigin-RevId: ec700b8cb6680bfd8f28ad0f1f2bbdb0479398bc
This commit is contained in:
Brian Gough
2026-09-17 11:44:22 +00:00
committed by Copybot
parent b53bfbe8a4
commit fd87cf1ddd
4 changed files with 87 additions and 24 deletions
+32 -2
View File
@@ -138,6 +138,10 @@ const LATEX_MK_METRICS_STDERR = [
],
[
'latexmk-img-times',
/**
* @param {string} s
* @returns {Array<{type: string, count: number, time_ms: number}>}
*/
s => {
const pngCategoriesByFile = new Map()
const pngCopyMatches = s.matchAll(/^PNG copy: (.*)$/gm)
@@ -157,6 +161,7 @@ const LATEX_MK_METRICS_STDERR = [
/^Image written \((PNG|JPG|JBIG2|PDF), (\d+) ms\): (.*)$/gm
)
const timingsByType = new Map()
const countByType = new Map()
for (const match of timingMatches) {
let type = match[1]
const timeMs = parseInt(match[2], 10)
@@ -174,9 +179,12 @@ const LATEX_MK_METRICS_STDERR = [
const accumulatedTime = timingsByType.get(type) ?? 0
timingsByType.set(type, accumulatedTime + timeMs)
const accumulatedCount = countByType.get(type) ?? 0
countByType.set(type, accumulatedCount + 1)
}
return Array.from(timingsByType.entries()).map(([type, timeMs]) => ({
type,
count: countByType.get(type) || 0,
time_ms: timeMs,
}))
},
@@ -190,9 +198,10 @@ const LATEX_MK_METRICS_STDERR = [
* `stats.latexmk` object.
*
* @param {{stdout?: string, stderr?: string}} output - The output from the latexmk process.
* @param {{latexmk: object}} stats - The statistics object to update. This object is mutated.
* @param {object} stats - The statistics object to update. This object is mutated.
* @param {object} [timings] - The timings object to update. This object is mutated.
*/
function addLatexMkMetrics(output, stats) {
function addLatexMkMetrics(output, stats, timings) {
for (const [stat, matcher] of LATEX_MK_METRICS_STDOUT) {
const match = matcher(output?.stdout || '', stats.latexmk)
if (match) {
@@ -205,6 +214,27 @@ function addLatexMkMetrics(output, stats) {
stats.latexmk[stat] = match
}
}
const imgTimings = stats.latexmk['latexmk-img-times']
if (imgTimings) {
let totalImageIncludeTime = 0
let optimisedImageIncludeTime = 0
let totalImageIncludeCount = 0
let optimisedImageIncludeCount = 0
for (const { type, count, time_ms } of imgTimings) {
totalImageIncludeCount += count
totalImageIncludeTime += time_ms
if (type === 'PNG-png2pdf') {
optimisedImageIncludeCount += count
optimisedImageIncludeTime += time_ms
}
}
stats['include-image-all'] = totalImageIncludeCount
stats['include-image-optimised'] = optimisedImageIncludeCount
if (timings) {
timings['include-image-optimised'] = optimisedImageIncludeTime
timings['include-image-all'] = totalImageIncludeTime
}
}
}
/**
+1 -1
View File
@@ -81,7 +81,7 @@ function runLatex(projectId, options, callback) {
}
if (stats.latexmk) {
try {
addLatexMkMetrics(output, stats)
addLatexMkMetrics(output, stats, timings)
} catch (err) {
logger.error({ err, projectId }, 'error adding latexmk metrics')
}
@@ -134,6 +134,8 @@ Hello world
const expectedRuns = scenario.expectedRuns ?? 1
// Note: chai's all.keys assertion rejects extra keys
stats.should.have.all.keys(
'include-image-all',
'include-image-optimised',
'isInitialCompile',
'latexmk-errors',
'latex-runs',
@@ -148,6 +150,8 @@ Hello world
'compile',
'output',
'compileE2E',
'include-image-all',
'include-image-optimised',
...pdfCachingTimings
)
})
+50 -21
View File
@@ -115,6 +115,7 @@ describe('LatexMetrics', function () {
describe('addLatexMkMetrics', function () {
beforeEach(function (ctx) {
ctx.stats = {}
ctx.timings = {}
Object.defineProperty(ctx.stats, 'latexmk', {
value: {},
enumerable: false,
@@ -128,34 +129,46 @@ describe('LatexMetrics', function () {
'Image written (PNG, 100 ms): output/image1.png\n' +
'Image written (JPG, 50 ms): output/image2.jpg\n',
}
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([
{ type: 'PNG', time_ms: 100 },
{ type: 'JPG', time_ms: 50 },
{ type: 'PNG', count: 1, time_ms: 100 },
{ type: 'JPG', count: 1, time_ms: 50 },
])
expect(ctx.stats['include-image-all']).to.equal(2)
expect(ctx.stats['include-image-optimised']).to.equal(0)
expect(ctx.timings['include-image-all']).to.equal(150)
expect(ctx.timings['include-image-optimised']).to.equal(0)
})
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)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([
{ type: 'PNG-png2pdf', time_ms: 150 },
{ type: 'PNG-png2pdf', count: 1, time_ms: 150 },
])
expect(ctx.stats['include-image-all']).to.equal(1)
expect(ctx.stats['include-image-optimised']).to.equal(1)
expect(ctx.timings['include-image-all']).to.equal(150)
expect(ctx.timings['include-image-optimised']).to.equal(150)
})
it('should handle uppercase .PNG extension', function (ctx) {
const output = {
stderr: 'Image written (PDF, 200 ms): image.PNG\n',
}
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([
{ type: 'PNG-png2pdf', time_ms: 200 },
{ type: 'PNG-png2pdf', count: 1, time_ms: 200 },
])
expect(ctx.stats['include-image-all']).to.equal(1)
expect(ctx.stats['include-image-optimised']).to.equal(1)
expect(ctx.timings['include-image-all']).to.equal(200)
expect(ctx.timings['include-image-optimised']).to.equal(200)
})
it('should keep PDF type for non-png extensions', function (ctx) {
@@ -164,11 +177,15 @@ describe('LatexMetrics', function () {
'Image written (PDF, 100 ms): document.pdf\n' +
'Image written (PDF, 80 ms): image.jpg\n',
}
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([
{ type: 'PDF', time_ms: 180 },
{ type: 'PDF', count: 2, time_ms: 180 },
])
expect(ctx.stats['include-image-all']).to.equal(2)
expect(ctx.stats['include-image-optimised']).to.equal(0)
expect(ctx.timings['include-image-all']).to.equal(180)
expect(ctx.timings['include-image-optimised']).to.equal(0)
})
it('should accumulate timing for the same type', function (ctx) {
@@ -178,15 +195,19 @@ describe('LatexMetrics', function () {
'Image written (PDF, 150 ms): image2.png\n' +
'Image written (PNG, 50 ms): image3.png\n',
}
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
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 },
{ type: 'PNG', count: 1, time_ms: 50 },
{ type: 'PNG-png2pdf', count: 2, time_ms: 250 },
])
expect(ctx.stats['include-image-all']).to.equal(3)
expect(ctx.stats['include-image-optimised']).to.equal(2)
expect(ctx.timings['include-image-all']).to.equal(300)
expect(ctx.timings['include-image-optimised']).to.equal(250)
})
it('should handle PNG with category from PNG copy skipped', function (ctx) {
@@ -195,11 +216,15 @@ describe('LatexMetrics', function () {
'PNG copy skipped (alpha): image1.png\n' +
'Image written (PNG, 75 ms): image1.png\n',
}
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
expect(ctx.stats.latexmk['latexmk-img-times']).to.deep.equal([
{ type: 'PNG-alpha', time_ms: 75 },
{ type: 'PNG-alpha', count: 1, time_ms: 75 },
])
expect(ctx.stats['include-image-all']).to.equal(1)
expect(ctx.stats['include-image-optimised']).to.equal(0)
expect(ctx.timings['include-image-all']).to.equal(75)
expect(ctx.timings['include-image-optimised']).to.equal(0)
})
it('should mix PDF-to-png2pdf and PNG types', function (ctx) {
@@ -209,16 +234,20 @@ describe('LatexMetrics', function () {
'Image written (PNG, 50 ms): native.png\n' +
'Image written (JPG, 30 ms): photo.jpg\n',
}
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
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 },
{ type: 'JPG', count: 1, time_ms: 30 },
{ type: 'PNG', count: 1, time_ms: 50 },
{ type: 'PNG-png2pdf', count: 1, time_ms: 100 },
])
expect(ctx.stats['include-image-all']).to.equal(3)
expect(ctx.stats['include-image-optimised']).to.equal(1)
expect(ctx.timings['include-image-all']).to.equal(180)
expect(ctx.timings['include-image-optimised']).to.equal(100)
})
})
@@ -231,7 +260,7 @@ describe('LatexMetrics', function () {
'PNG copy skipped (palette): palette.png\n' +
'PNG copy skipped (other): other.png\n',
}
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
expect(ctx.stats.latexmk['latexmk-png-slow']).to.deep.equal([
'images/alpha.png',
@@ -242,7 +271,7 @@ describe('LatexMetrics', function () {
it('should not include fast PNG copy lines', function (ctx) {
const output = { stderr: 'PNG copy: fast.png\n' }
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
expect(ctx.stats.latexmk).to.not.have.property('latexmk-png-slow')
})
@@ -253,7 +282,7 @@ describe('LatexMetrics', function () {
'PNG copy skipped (gamma): /compile/deep/img.png\n' +
'PNG copy skipped (interlaced): ./local.png\n',
}
addLatexMkMetrics(output, ctx.stats)
addLatexMkMetrics(output, ctx.stats, ctx.timings)
expect(ctx.stats.latexmk['latexmk-png-slow']).to.deep.equal([
'deep/img.png',