Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CHORE] adds asset reporting infra #6655

Merged
merged 5 commits into from
Nov 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ module.exports = {
'.mocharc.js',
'.eslintrc.js',
'.prettierrc.js',
'bin/*',
'bin/**',
runspired marked this conversation as resolved.
Show resolved Hide resolved
'packages/private-build-infra/src/**/*.js',
'packages/unpublished-test-infra/src/**/*.js',
'packages/*/.ember-cli.js',
Expand Down Expand Up @@ -108,12 +108,13 @@ module.exports = {

// bin files
{
files: ['bin/*'],
files: ['bin/**'],
// eslint-disable-next-line node/no-unpublished-require
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
'no-console': 'off',
'no-process-exit': 'off',
runspired marked this conversation as resolved.
Show resolved Hide resolved
'node/no-unpublished-require': 'off',
'node/no-unsupported-features/node-builtins': 'off',
}),
},
],
Expand Down
75 changes: 75 additions & 0 deletions .github/workflows/asset-size-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: AssetSizeCheck

on:
pull_request:
branches:
- master
runspired marked this conversation as resolved.
Show resolved Hide resolved

jobs:
asset-size-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/setup-node@v1
with:
node-version: 12.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout master
runspired marked this conversation as resolved.
Show resolved Hide resolved
run: git checkout master
- name: Install dependencies for master
run: yarn install
- name: Build Production master
# This will leave the assets in a dist that is maintained when
# We switch back to the primary branch
run: yarn workspace ember-data ember build -e production
- name: Checkout ${{github.ref}}
# We checkout the PR Branch before parsing the master vendor file
# So that we are always using the current analysis tooling
run: git checkout --progress --force $GITHUB_SHA
- name: Install dependencies for ${{github.ref}}
run: yarn install
- name: Parse Master Assets
run: |
node ./bin/asset-size-tracking/generate-analysis.js
mkdir -p tmp
mkdir -p tmp/asset-sizes
node ./bin/asset-size-tracking/print-analysis.js -show > tmp/asset-sizes/master-analysis.txt
- name: Upload Master Dist Artifacts
uses: actions/upload-artifact@v1
with:
name: master-dist
path: packages/-ember-data/dist/assets
- name: Build Production ${{github.ref}}
run: yarn workspace ember-data ember build -e production
- name: Test Asset Sizes
run: |
mkdir -p tmp
mkdir -p tmp/asset-sizes
node ./bin/asset-size-tracking/generate-diff.js | tee tmp/asset-sizes/diff.txt
- name: Prepare Data For Report
if: failure() || success()
run: |
node ./bin/asset-size-tracking/generate-analysis.js
- name: Print Asset Size Report
if: failure() || success()
run: |
node ./bin/asset-size-tracking/print-analysis.js | tee tmp/asset-sizes/commit-analysis.txt
- name: Upload ${{github.ref}} Dist Artifacts
uses: actions/upload-artifact@v1
with:
name: commit-dist
path: packages/-ember-data/dist/assets
- name: Upload Report Artifacts
uses: actions/upload-artifact@v1
with:
name: reports
path: tmp/asset-sizes
- name: Report Asset Sizes
if: failure() || success()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
COMMENT_MARKER="Asset Size Report for "
node ./bin/asset-size-tracking/src/create-comment-text.js $GITHUB_SHA > tmp/asset-sizes/comment.txt
COMMENT_TEXT="@./tmp/asset-sizes/comment.txt"
source bin/asset-size-tracking/src/post-comment.sh
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ tmp
# dependencies
bower_components
node_modules
bin/asset-size-tracking/current-data.json

# misc
.env*
Expand Down
20 changes: 20 additions & 0 deletions bin/asset-size-tracking/generate-analysis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';
/**
* Analyze Ember-Data Modules
*
* Generates a JSON file with details of size costs of each individual module
* and package. You should crate a production build of the ember-data
* package prior to running this script.
*
*/
const fs = require('fs');
const path = require('path');
let INPUT_FILE = process.argv[2] || false;
const parseModules = require('./src/parse-modules');
const getBuiltDist = require('./src/get-built-dist');

const builtAsset = getBuiltDist(INPUT_FILE);
const library = parseModules(builtAsset);
const outputPath = path.resolve(__dirname, './current-data.json');

fs.writeFileSync(outputPath, JSON.stringify(library, null, 2));
213 changes: 213 additions & 0 deletions bin/asset-size-tracking/generate-diff.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
'use strict';

const fs = require('fs');
const path = require('path');
const Library = require('./src/library');
const parseModules = require('./src/parse-modules');
const getBuiltDist = require('./src/get-built-dist');
const chalk = require('chalk');
const library_failure_threshold = 15;
const package_warn_threshold = 0;

let BASE_DATA_FILE = process.argv[2] || false;
let NEW_VENDOR_FILE = process.argv[3] || false;

if (!BASE_DATA_FILE) {
BASE_DATA_FILE = path.resolve(__dirname, './current-data.json');
}

const data = fs.readFileSync(BASE_DATA_FILE, 'utf-8');
const current_library = Library.fromData(JSON.parse(data));

const builtAsset = getBuiltDist(NEW_VENDOR_FILE);
const new_library = parseModules(builtAsset);

function getDiff(oldLibrary, newLibrary) {
const diff = {
name: oldLibrary.name,
currentSize: oldLibrary.absoluteSize,
newSize: newLibrary.absoluteSize,
currentSizeCompressed: oldLibrary.compressedSize,
newSizeCompressed: newLibrary.compressedSize,
packages: {},
};
oldLibrary.packages.forEach(pkg => {
diff.packages[pkg.name] = {
name: pkg.name,
currentSize: pkg.absoluteSize,
newSize: 0,
currentSizeCompressed: pkg.compressedSize,
newSizeCompressed: 0,
modules: {},
};
let modules = diff.packages[pkg.name].modules;
pkg.modules.forEach(m => {
modules[m.name] = {
name: m.name,
currentSize: m.absoluteSize,
newSize: 0,
currentSizeCompressed: m.compressedSize,
newSizeCompressed: 0,
};
});
});
newLibrary.packages.forEach(pkg => {
diff.packages[pkg.name] = diff.packages[pkg.name] || {
name: pkg.name,
currentSize: 0,
newSize: pkg.absoluteSize,
currentSizeCompressed: 0,
newSizeCompressed: pkg.compressedSize,
modules: {},
};
diff.packages[pkg.name].newSize = pkg.absoluteSize;
diff.packages[pkg.name].newSizeCompressed = pkg.compressedSize;
let modules = diff.packages[pkg.name].modules;
pkg.modules.forEach(m => {
modules[m.name] = modules[m.name] || {
name: m.name,
currentSize: 0,
newSize: m.absoluteSize,
currentSizeCompressed: 0,
newSizeCompressed: m.compressedSize,
};
modules[m.name].newSize = m.absoluteSize;
modules[m.name].newSizeCompressed = m.compressedSize;
});
});
diff.packages = Object.values(diff.packages);
diff.packages.forEach(pkg => {
pkg.modules = Object.values(pkg.modules);
});

return diff;
}

const diff = getDiff(current_library, new_library);

function analyzeDiff(diff) {
let failures = [];
let warnings = [];

if (diff.currentSize < diff.newSize) {
let delta = diff.newSize - diff.currentSize;
let compressedDelta = diff.newSizeCompressed - diff.currentSizeCompressed;
if (delta > library_failure_threshold) {
failures.push(
`The size of the library ${diff.name} has increased by ${formatBytes(delta)} (${formatBytes(
compressedDelta
)} compressed) which exceeds the failure threshold of ${library_failure_threshold} bytes.`
);
}
}

diff.packages.forEach(pkg => {
if (pkg.currentSize < pkg.newSize) {
let delta = pkg.newSize - pkg.currentSize;
if (delta > package_warn_threshold) {
warnings.push(`The uncompressed size of the package ${pkg.name} has increased by ${formatBytes(delta)}.`);
}
}
});

return { failures, warnings };
}

function printDiff(diff) {
printItem(diff);
diff.packages.forEach(pkg => {
printItem(pkg, 2);
pkg.modules.forEach(m => {
printItem(m, 4);
});
});
}

function printItem(item, indent = 0) {
if (item.currentSize !== item.newSize) {
const indentColor = indent >= 4 ? 'grey' : indent >= 2 ? 'yellow' : indent >= 0 ? 'magenta' : 'green';
console.log(
leftPad(
chalk[indentColor](item.name) + ' ' + chalk.white(formatBytes(item.newSizeCompressed)) + formatDelta(item),
indent * 2
)
);
}
}

function formatDelta(item) {
if (item.currentSize === item.newSize) {
return '';
}
if (item.currentSize > item.newSize) {
return chalk.green(` (- ${formatBytes(item.currentSizeCompressed - item.newSizeCompressed)})`);
} else {
return chalk.red(` (+ ${formatBytes(item.newSizeCompressed - item.currentSizeCompressed)})`);
}
}

function formatBytes(b) {
let str;
if (b > 1024) {
str = (b / 1024).toFixed(2) + ' KB';
} else {
str = b + ' B';
}

return str;
}

function leftPad(str, len, char = ' ') {
for (let i = 0; i < len; i++) {
str = char + str;
}
return str;
}

printDiff(diff);
const { failures, warnings } = analyzeDiff(diff);

if (failures.length) {
console.log(`\n<details>\n <summary>${failures[0]}</summary>`);
if (failures.length > 1) {
console.log('\nFailed Checks\n-----------------------');
failures.forEach(f => {
console.log(f);
});
}
if (warnings.length) {
console.log('\nWarnings\n-----------------------');
warnings.forEach(w => {
console.log(w);
});
}
console.log('\n</details>');
process.exit(1);
} else {
let delta = diff.currentSize - diff.newSize;
if (delta === 0) {
console.log(`\n<details>\n <summary>${diff.name} has not changed in size</summary>`);
} else if (delta > 0) {
console.log(
`\n<detail>\n <summary>${diff.name} shrank by ${formatBytes(delta)} (${formatBytes(
diff.currentSizeCompressed - diff.newSizeCompressed
)} compressed)</summary>`
);
} else {
console.log(
`\n${diff.name} increased by ${formatBytes(-1 * delta)} (${formatBytes(
diff.newSizeCompressed - diff.currentSizeCompressed
)} compressed) which is within the allowed tolerance of ${library_failure_threshold} bytes uncompressed`
);
}
if (warnings.length) {
console.log('\nWarnings\n-----------------------');
warnings.forEach(w => {
console.log(w);
});
} else {
console.log('\nIf any packages had changed sizes they would be listed here.');
}
console.log('\n</details>');
process.exit(0);
}
17 changes: 17 additions & 0 deletions bin/asset-size-tracking/print-analysis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const fs = require('fs');
const path = require('path');
const Library = require('./src/library');

let INPUT_FILE = process.argv[2] !== '-show' ? process.argv[2] : false;
let SHOW_MODULES = process.argv[2] === '-show' || process.argv[3] === '-show';

if (!INPUT_FILE) {
INPUT_FILE = path.resolve(__dirname, './current-data.json');
}

const data = fs.readFileSync(INPUT_FILE, 'utf-8');

const library = Library.fromData(JSON.parse(data));
library.print(SHOW_MODULES);
15 changes: 15 additions & 0 deletions bin/asset-size-tracking/src/create-comment-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const fs = require('fs');
const path = require('path');
const GITHUB_SHA = process.argv[2];

const diffPath = path.resolve(__dirname, '../../../tmp/asset-sizes/diff.txt');
const analysisPath = path.resolve(__dirname, '../../../tmp/asset-sizes/commit-analysis.txt');
const diffText = fs.readFileSync(diffPath);
const analysisText = fs.readFileSync(analysisPath);

const commentText = `Asset Size Report for ${GITHUB_SHA}\n${diffText}\n<details>\n <summary>Full Asset Analysis</summary>\n\n\`\`\`${analysisText}\n\`\`\`\n</details>`;
const commentJSON = {
body: commentText,
};

console.log(JSON.stringify(commentJSON, null, 2));
Loading