321 lines
9.6 KiB
JavaScript
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
|
|
}
|