analytics/tracker/compiler/analyze-sizes.js

321 lines
9.6 KiB
JavaScript

/*
This script analyzes the size changes of tracker script variants across different versions and git branches.
Requires clickhouse-local to be installed.
To run it:
- Switch to the desired baseline branch
- Run `node compile.js --suffix baseline`
- Switch to the current branch
- Run `node compile.js --suffix current`
- Run `node compiler/analyze-sizes.js --baselineSuffix baseline --currentSuffix current`
It will output tables outlining tracker script size changes.
*/
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import { execSync } from 'child_process'
import { parseArgs } from 'node:util'
import { markdownTable } from 'markdown-table'
import variantsFile from './variants.json' with { type: 'json' }
const { values } = parseArgs({
options: {
help: {
type: 'boolean'
},
currentSuffix: {
type: 'string',
default: 'current'
},
baselineSuffix: {
type: 'string',
default: 'master'
},
usePreviousData: {
type: 'boolean',
default: false
}
}
})
const { currentSuffix, baselineSuffix } = values
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const TRACKER_FILES_DIR = path.join(__dirname, '../../priv/tracker/js/')
const NPM_PACKAGE_FILES_DIR = path.join(__dirname, '../npm_package')
const HEADER = ['', 'Brotli', 'Gzip', 'Uncompressed']
if (values.help) {
console.log('Usage: node analyze-sizes.js master current')
console.log('Options:')
console.log(' --help Show this help message')
console.log(
' --currentSuffix The suffix of the current script variants (see suffix flag for compile.js). Default: current'
)
console.log(
' --baselineSuffix The suffix of the previous script variants (see suffix flag for compile.js). Default: master'
)
console.log(
' --usePreviousData Use data from a previous run, speeding up the analysis.'
)
process.exit(0)
}
let fileData
if (values.usePreviousData) {
fileData = JSON.parse(
fs.readFileSync(path.join(__dirname, '.analyze-sizes.json'), 'utf8')
)
} else {
fileData = readPlausibleScriptSizes()
fs.writeFileSync(
path.join(__dirname, '.analyze-sizes.json'),
JSON.stringify(fileData)
)
}
const manualVariants = variantsFile.manualVariants
.map((variant) => `'${variant.name}'`)
.join(', ')
const ctes = `
WITH
array(${manualVariants}) as manual_variants,
array(
'plausible.js',
'plausible.hash.js',
'plausible.pageview-props.tagged-events.js',
'plausible.file-downloads.hash.pageview-props.revenue.js',
'plausible.compat.exclusions.file-downloads.outbound-links.pageview-props.revenue.tagged-events.js'
) as important_variants,
data AS (
SELECT
variant,
not has(manual_variants, variant) as is_legacy_variant,
sumIf(uncompressed, suffix = '${baselineSuffix}') as baseline_uncompressed,
sumIf(gzip, suffix = '${baselineSuffix}') as baseline_gzip,
sumIf(brotli, suffix = '${baselineSuffix}') as baseline_brotli,
sumIf(uncompressed, suffix = '${currentSuffix}') as current_uncompressed,
sumIf(gzip, suffix = '${currentSuffix}') as current_gzip,
sumIf(brotli, suffix = '${currentSuffix}') as current_brotli,
current_uncompressed - baseline_uncompressed as uncompressed_increase,
current_gzip - baseline_gzip as gzip_increase,
current_brotli - baseline_brotli as brotli_increase,
ifNotFinite((current_uncompressed / baseline_uncompressed - 1.0) * 100.0, NULL) as uncompressed_increase_percentage,
ifNotFinite((current_gzip / baseline_gzip - 1.0) * 100.0, NULL) as gzip_increase_percentage,
ifNotFinite((current_brotli / baseline_brotli - 1.0) * 100.0, NULL) as brotli_increase_percentage
FROM table
GROUP BY variant
)
`
const mainVariantResults = clickhouseLocal(
`
${ctes}
SELECT *
FROM data
WHERE not is_legacy_variant
ORDER BY variant
`,
fileData
)
const legacyVariantResults = clickhouseLocal(
`
${ctes}
SELECT *
FROM data
WHERE is_legacy_variant AND has(important_variants, variant)
ORDER BY length(variant)
`,
fileData
)
const rowAsMap = `
map(
'variant', variant,
'baseline_uncompressed', toString(baseline_uncompressed),
'baseline_gzip', toString(baseline_gzip),
'baseline_brotli', toString(baseline_brotli),
'current_uncompressed', toString(current_uncompressed),
'current_gzip', toString(current_gzip),
'current_brotli', toString(current_brotli),
'uncompressed_increase', toString(uncompressed_increase),
'gzip_increase', toString(gzip_increase),
'brotli_increase', toString(brotli_increase),
'uncompressed_increase_percentage', toString(uncompressed_increase_percentage),
'gzip_increase_percentage', toString(gzip_increase_percentage),
'brotli_increase_percentage', toString(brotli_increase_percentage)
)
`
const [summary] = clickhouseLocal(
`
${ctes}
SELECT
count() AS total_variants,
countIf(brotli_increase_percentage > 0.0) AS brotli_increase_percentaged_variants,
countIf(brotli_increase_percentage < 0.0) AS brotli_decreased_variants,
argMax(
${rowAsMap},
brotli_increase_percentage
) AS max_increase_variant,
argMin(
${rowAsMap},
brotli_increase_percentage
) AS min_increase_variant,
argMaxIf(
${rowAsMap},
current_brotli,
is_legacy_variant
) AS largest_variant,
map(
'variant', 'Median change',
'baseline_uncompressed', toString(median(baseline_uncompressed)),
'baseline_gzip', toString(median(baseline_gzip)),
'baseline_brotli', toString(median(baseline_brotli)),
'current_uncompressed', toString(median(current_uncompressed)),
'current_gzip', toString(median(current_gzip)),
'current_brotli', toString(median(current_brotli)),
'uncompressed_increase', toString(median(uncompressed_increase)),
'gzip_increase', toString(median(gzip_increase)),
'brotli_increase', toString(median(brotli_increase)),
'uncompressed_increase_percentage', toString(median(uncompressed_increase_percentage)),
'gzip_increase_percentage', toString(median(gzip_increase_percentage)),
'brotli_increase_percentage', toString(median(brotli_increase_percentage))
) AS median_result
FROM data
`,
fileData
)
console.log(
`Analyzed ${summary.total_variants} tracker script variants for size changes.`
)
console.log(
`The following tables summarize the results, with comparison with the baseline version in parentheses.\n`
)
console.log('Main variants:')
console.log(createMarkdownTable(mainVariantResults))
console.log('\nImportant legacy variants:')
console.log(createMarkdownTable(legacyVariantResults))
console.log('\nSummary:')
console.log(
createMarkdownTable([
{
...summary.largest_variant,
variant: `Largest variant (${summary.largest_variant.variant})`
},
{
...summary.max_increase_variant,
variant: `Max change (${summary.max_increase_variant.variant})`
},
{
...summary.min_increase_variant,
variant: `Min change (${summary.min_increase_variant.variant})`
},
summary.median_result
])
)
console.log(
`\nIn total, ${summary.brotli_increase_percentaged_variants} variants brotli size increased and ${summary.brotli_decreased_variants} variants brotli size decreased.`
)
function createMarkdownTable(rows) {
return markdownTable([HEADER].concat(rows.map(markdownRow)))
}
function markdownRow(row) {
if (Array.isArray(row)) {
return row
}
const isNew = row.baseline_uncompressed === null
return [
isNew ? `${row.variant} (new variant)` : row.variant,
sizeColumn(row, 'brotli'),
sizeColumn(row, 'gzip'),
sizeColumn(row, 'uncompressed')
]
}
function sizeColumn(row, key) {
const currentSize = row[`current_${key}`]
const previousIncrease = row[`${key}_increase`]
const increasePercentage = row[`${key}_increase_percentage`]
if (previousIncrease === null) {
return `${currentSize}B`
} else {
return `${currentSize}B (${addSign(previousIncrease)}B / ${formatPercentage(increasePercentage)})`
}
}
function formatPercentage(value) {
const prefix = +value > 0 ? '+' : ''
return `${prefix}${Math.round(+value * 10) / 10}%`
}
function addSign(value) {
return +value >= 0 ? `+${value}` : +value
}
function readPlausibleScriptSizes() {
const trackerFileSizes = fs
.readdirSync(TRACKER_FILES_DIR)
.filter(isRelevantFile)
.map((filename) => readFileSize(filename, TRACKER_FILES_DIR))
const npmPackageFileSizes = fs
.readdirSync(NPM_PACKAGE_FILES_DIR)
.filter(isRelevantFile)
.map((filename) => readFileSize(filename, NPM_PACKAGE_FILES_DIR))
return trackerFileSizes.concat(npmPackageFileSizes)
}
function readFileSize(filename, basepath) {
const filePath = path.join(basepath, filename)
const [_, variant, suffix] = /(.*)[.]js(.*)/.exec(filename)
return {
variant:
basepath === TRACKER_FILES_DIR
? `${variant}.js`
: 'npm_package/plausible.js',
suffix,
uncompressed: fs.statSync(filePath).size,
gzip: execSync(`gzip -c -9 "${filePath}"`).length,
brotli: execSync(`brotli -c -q 11 "${filePath}"`).length
}
}
function isRelevantFile(filename) {
return (
!['.gitkeep', 'p.js'].includes(filename) &&
filename.includes('.js') &&
(filename.includes(currentSuffix) || filename.includes(baselineSuffix))
)
}
function clickhouseLocal(sql, inputLines = null) {
const options = {}
if (inputLines) {
options.input = inputLines.map(JSON.stringify).join('\n')
}
const result = execSync(
`clickhouse-local --query="${sql}" --format=JSON ${inputLines ? '--input-format=JSONLines' : ''}`,
options
)
const json = JSON.parse(result.toString())
return json.data
}