optimize webpack linters build time

refs DE-938
flag=none

TEST PLAN:
  Confirm all builds still run successfully
  Confirm all webpack linter jobs still run
  Comfirm bundle sizes are appropriately reported

This task removes the webpack linter stage from premerge builds,
spreads the previous tasks around,
and adds a step to postmerge builds to track and report
webpack bundle sizes

Change-Id: I017876726ac0db6f099367b1ecfd086cfb726cf8
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/289680
Reviewed-by: Aaron Ogata <aogata@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Bobby Buten <bobby.buten@instructure.com>
Product-Review: Bobby Buten <bobby.buten@instructure.com>
This commit is contained in:
Bobby Buten 2022-04-14 10:18:11 -04:00
parent 4e84cec463
commit 1b3a8216ae
9 changed files with 126 additions and 90 deletions

15
Jenkinsfile vendored
View File

@ -306,6 +306,7 @@ pipeline {
DYNAMODB_IMAGE_TAG = "$DYNAMODB_PREFIX:$IMAGE_CACHE_UNIQUE_SCOPE"
POSTGRES_IMAGE_TAG = "$POSTGRES_PREFIX:$IMAGE_CACHE_UNIQUE_SCOPE"
WEBPACK_BUILDER_IMAGE = "$WEBPACK_BUILDER_PREFIX:$IMAGE_CACHE_UNIQUE_SCOPE"
WEBPACK_CACHE_IMAGE = "$WEBPACK_CACHE_PREFIX:$IMAGE_CACHE_UNIQUE_SCOPE"
CASSANDRA_MERGE_IMAGE = "$CASSANDRA_PREFIX:$IMAGE_CACHE_MERGE_SCOPE-${env.RSPEC_PROCESSES ?: '4'}"
DYNAMODB_MERGE_IMAGE = "$DYNAMODB_PREFIX:$IMAGE_CACHE_MERGE_SCOPE-${env.RSPEC_PROCESSES ?: '4'}"
@ -498,6 +499,19 @@ pipeline {
gerrit.submitLintReview('-2', 'This commit contains only changes to config/locales/, this could be a bad sign!')
}
extendedStage('Webpack ES-Check')
.hooks(buildSummaryReportHooks.call())
.obeysAllowStages(false)
.required(env.GERRIT_CHANGE_ID != '0')
.execute { webpackStage.&runESCheck() }
extendedStage('Webpack Bundle Size Check')
.hooks(buildSummaryReportHooks.call())
.obeysAllowStages(false)
.required(configuration.isChangeMerged())
.timeout(20)
.execute { webpackStage.&calcBundleSizes() }
extendedStage('Parallel Run Tests').obeysAllowStages(false).execute { stageConfig, buildConfig ->
def stages = [:]
@ -562,7 +576,6 @@ pipeline {
callableWithDelegate(lintersStage.featureFlagStage(nestedStages, buildConfig))()
callableWithDelegate(lintersStage.groovyStage(nestedStages, buildConfig))()
callableWithDelegate(lintersStage.masterBouncerStage(nestedStages))()
callableWithDelegate(lintersStage.webpackStage(nestedStages))()
callableWithDelegate(lintersStage.yarnStage(nestedStages, buildConfig))()
parallel(nestedStages)

View File

@ -1,54 +0,0 @@
# frozen_string_literal: true
#
# Copyright (C) 2016 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
# gergich capture custom:./gergich/compile_assets:Gergich::CompileAssets \
# "bundle exec rake RAILS_ENV=test canvas:compile_assets[$GENERATE_DOCS,$CHECK_SYNTAX,$COMPILE_STYLEGUIDE,$BUILD_JS]"
class Gergich::CompileAssets
def run(output)
# HBS
pattern = %r| # Example:
^HBS\sPRECOMPILATION\sFAILED\n # HBS PRECOMPILATION FAILED
([^:\n]+):(\d+):\s([^\n]+\n # app/views/jst/googleDocsTreeView.handlebars:3 Parse error:
(\s\s[^\n]+\n)* # ...le="menuitem"> {{t} {{name}} <ul>
# ----------------------^
# Expecting 'CLOSE', 'CLOSE_UNESCAPED', 'STRING', ...
)
|mx
result = output.scan(pattern).map do |file, line, error|
error.sub!(/\n/, "\n\n") # separate first line from the rest, which will be indented (monospace)
{ path: file, message: error, position: line.to_i, severity: "error" }
end
# COFFEE
cwd = Dir.pwd
puts cwd
pattern = %r{ # Example:
^#{cwd}/([^\n]+?):(\d+):\d+:\serror:\s([^\n]+)\n # /absolute/path/to/file.coffee:7:1: error: unexpected INDENT
([^\n]+)\n # falseList = []
([^\n]+)\n # ^^^^^
}mx
result.concat(output.scan(pattern).map do |file, line, error, context1, context2|
error = "#{error}\n\n #{context1}\n #{context2}"
{ path: file, message: error, position: line.to_i, severity: "error" }
end)
end
end

View File

@ -211,9 +211,10 @@ else
[ "$WEBPACK_BUILDER_SELECTED_TAG" != "${WEBPACK_BUILDER_TAGS[SAVE_TAG]}" ] && tag_remote_async "WEBPACK_BUILDER_TAG_REMOTE_SAVE_PID" $WEBPACK_BUILDER_SELECTED_TAG ${WEBPACK_BUILDER_TAGS[SAVE_TAG]}
[ ! -z "${CACHE_UNIQUE_SCOPE-}" ] && tag_remote_async "WEBPACK_BUILDER_TAG_REMOTE_UNIQUE_PID" $WEBPACK_BUILDER_SELECTED_TAG ${WEBPACK_BUILDER_TAGS[UNIQUE_TAG]}
[ ! -z "${CACHE_UNIQUE_SCOPE-}" ] && tag_remote_async "WEBPACK_CACHE_TAG_REMOTE_UNIQUE_PID" $WEBPACK_CACHE_SELECTED_TAG ${WEBPACK_CACHE_TAGS[UNIQUE_TAG]}
fi
tag_many $WEBPACK_CACHE_SELECTED_TAG local/webpack-cache ${WEBPACK_CACHE_TAGS[SAVE_TAG]}
tag_many $WEBPACK_CACHE_SELECTED_TAG local/webpack-cache ${WEBPACK_CACHE_TAGS[SAVE_TAG]} ${WEBPACK_CACHE_TAGS[UNIQUE_TAG]-}
# Build Final Image
if [ -n "${1:-}" ]; then

View File

@ -78,15 +78,6 @@ def masterBouncerStage(stages) {
}
}
def webpackStage(stages) {
{ ->
callableWithDelegate(queueTestStage())(stages,
name: 'webpack',
command: './build/new-jenkins/linters/run-gergich-webpack.sh'
)
}
}
def featureFlagStage(stages, buildConfig) {
{ ->
extendedStage('Linters - feature-flag')

View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2022 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
def calcBundleSizes() {
sh '''
docker run --name webpack-linters \
$PATCHSET_TAG bash -c "./build/new-jenkins/record-webpack-sizes.sh"
'''
sh 'docker cp webpack-linters:/tmp/big_bundles.csv tmp/big_bundles.csv'
def bigFileList = []
def thingsWeKnowAreWayTooBig = [
'account_settings',
'assignment_edit',
'assignment_index',
'calendar',
'discussion_topic_edit',
'discussion_topics_post',
'edit_rubric',
'manage_groups',
'wiki_page_show'
]
def csv = readCSV file: 'tmp/big_bundles.csv'
for (def records : csv) {
// skip auto-generated bundles
if (records[0] =~ /^\d/) {
continue
}
reportToSplunk('webpack_bundle_size', [
'fileName': records[0],
'fileSize': records[1],
'version': '3a',
])
int fileSize = records[1].toInteger()
// should match maxAssetSize value in ~/ui-build/webpack/index.js
if ((fileSize > 1400000) && !thingsWeKnowAreWayTooBig.any{records[0].contains(it)}) {
bigFileList.push(records[0])
}
}
if (!bigFileList.isEmpty()) {
def authorSlackId = env.GERRIT_EVENT_ACCOUNT_EMAIL ? slackUserIdFromEmail(email: env.GERRIT_EVENT_ACCOUNT_EMAIL, botUser: true, tokenCredentialId: 'slack-user-id-lookup') : ''
def authorSlackMsg = authorSlackId ? "<@$authorSlackId>" : env.GERRIT_EVENT_ACCOUNT_NAME
def authorSegment = "Patchset <${env.GERRIT_CHANGE_URL}|#${env.GERRIT_CHANGE_NUMBER}> by ${authorSlackMsg} includes an asset bundle over the recommended max size"
def bigFiles = bigFileList.join(", ")
def extra = "Bundle ${bigFiles} is too big and not in our known list of oversized bundles. This patchset may have pushed in over the acceptable limit. Please review the <https://inst.splunkcloud.com/en-US/app/search/report?sid=scheduler_cm9iZXJ0LmJ1dGVuQGluc3RydWN0dXJlLmNvbQ__search__RMD5ca61b6204ef60fe9_at_1650416400_45040_1987C8A1-0299-4A02-A75B-503AB27123E0&s=%2FservicesNS%2Fnobody%2Fsearch%2Fsaved%2Fsearches%2Fwebpack_bundle_size|Webpack Bundle Size> to see how bundles have grown over time."
def summaryUrl = "${env.BUILD_URL}/build-summary-report"
slackSend(
channel: '#canvas_builds',
color: 'warning',
message: "${authorSegment}. Build <${summaryUrl}|#${env.BUILD_NUMBER}>\n\n$extra"
)
}
}
def runESCheck() {
sh './build/new-jenkins/docker-with-flakey-network-protection.sh pull $WEBPACK_CACHE_IMAGE'
def statusCode = sh(script: '''
docker run --name webpack-cache \
$WEBPACK_CACHE_IMAGE bash -c \
"yarn add es-check@5 --silent && node_modules/.bin/es-check es10 ./public/dist/**/*.js > /tmp/escheck.out"
''', returnStatus:true)
if (statusCode != 0) {
sh 'docker cp webpack-cache:/tmp/escheck.out tmp/escheck.out'
def data = readFile(file: 'tmp/escheck.out')
echo data
gerrit.submitLintReview('-2', 'ES version matching errors exist')
}
}

View File

@ -32,5 +32,7 @@ ruby script/rlint --no-fail-on-offense
ruby script/lint_commit_message
node script/yarn-validate-workspace-deps.js 2>/dev/null < <(yarn --silent workspaces info --json)
rake css:styleguide doc:api
gergich status
echo "LINTER OK!"

View File

@ -1,11 +0,0 @@
#!/bin/bash
set -ex
export COMPILE_ASSETS_NPM_INSTALL=0
export COMPILE_ASSETS_BRAND_CONFIGS=0
export JS_BUILD_NO_FALLBACK=1
gergich capture custom:./build/gergich/compile_assets:Gergich::CompileAssets 'rake canvas:compile_assets'
yarn lint:browser-code
gergich status
echo "WEBPACK_BUILD OK!"

View File

@ -0,0 +1,13 @@
#!/bin/bash
set -o errexit -o errtrace -o nounset -o pipefail
DANGER_FILE_SIZE=1000000
cd /usr/src/app/public/dist/webpack-production
ls -ltra
for file in *.js; do
filesize=$(ls -l $file | awk '{print $5}')
if [ $filesize -gt $DANGER_FILE_SIZE ]; then
echo "$file,$filesize" >> /tmp/big_bundles.csv
fi
done

View File

@ -68,20 +68,8 @@ module.exports = {
// this number to the size of our biggest known asset and hopefully someday get
// to where they are all under the default value of 250000 and then remove this
// TODO: decrease back to 1200000 LS-1222
maxAssetSize: 1400000,
assetFilter: assetFilename => {
const thingsWeKnowAreWayTooBig = [
'assignment_edit',
'canvas-rce-async-chunk',
'canvas-rce-old-async-chunk',
'discussion_topic_edit',
'discussion_topics_post'
]
return (
assetFilename.endsWith('.js') &&
!thingsWeKnowAreWayTooBig.some(t => assetFilename.includes(t))
)
}
// NOTE: if maxAssetSize changes, update: ~build/new-jenkins/library/vars/webpackStage.groovy
maxAssetSize: 1400000
},
optimization: {
// concatenateModules: false, // uncomment if you want to get more accurate stuff from `yarn webpack:analyze`