Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

Commit e40c59e

Browse files
perf: run flamegrill in CI (#2163)
* store flamegrill results to json * run flamegrill in circleci * remove cruft console.log * store flamegrill stats to db * correctly name flamegrill data in db
1 parent 02978bd commit e40c59e

File tree

6 files changed

+104
-24
lines changed

6 files changed

+104
-24
lines changed

.circleci/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ jobs:
6060
- run:
6161
name: Circular Dependencies Tests
6262
command: yarn test:circulars
63+
- run:
64+
name: Flamegrill
65+
command: yarn perf:test
6366
- run:
6467
name: Bundle Statistics (master only)
6568
command: |

.github/test-a-feature.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,10 @@ Finding the right number of `iterations` is a balancing act between having a fas
265265

266266
Run test and watch:
267267
```
268-
cd packages/perf-test
269-
yarn just perf-test
268+
yarn perf:test
270269
```
271270

272-
After running `perf-test`, results can be viewed in the `packages/perf-test/dist` folder with the main entry file being `packages/perf-test/dist/perfCounts.html`.
271+
After running `perf:test`, results can be viewed in the `packages/perf-test/dist` folder with the main entry file being `packages/perf-test/dist/perfCounts.html`.
273272

274273
There are more detailed commands as well (these must be run from `packages/perf-test` directory):
275274

build/gulp/tasks/stats.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,34 @@ function readSummaryPerfStats() {
143143
.value()
144144
}
145145

146+
function readFlamegrillStats() {
147+
return require(paths.packageDist('perf-test', 'perfCounts.json'))
148+
}
149+
150+
// 1. iterate over all perf-test results
151+
// 2. the ones which have filename are docsite perf examples
152+
// -> use camelCase name (docsite perf examples convention)
153+
// -> and merge yarn perf (summaryPerf) and yarn perf:test (flamegrill) data
154+
// 3. the others are perf-test only examples -> store
155+
function mergePerfStats(summaryPerfStats, perfTestStats) {
156+
return _.transform(
157+
perfTestStats,
158+
(result, value, key: string) => {
159+
const flamegrill = _.pick(value, ['profile.metrics', 'analysis', 'extended'])
160+
if (value['extended'].filename) {
161+
const docsiteFilename = _.camelCase(value['extended'].filename)
162+
result[docsiteFilename] = {
163+
...summaryPerfStats[docsiteFilename],
164+
flamegrill,
165+
}
166+
} else {
167+
result[key.replace(/\./g, '_')] = { flamegrill } // mongodb does not allow dots in keys
168+
}
169+
},
170+
{},
171+
)
172+
}
173+
146174
function readCurrentBundleStats() {
147175
return _.mapKeys(require(currentStatsFilePath), (value, key) => _.camelCase(key)) // mongodb does not allow dots in keys
148176
}
@@ -158,6 +186,9 @@ task('stats:save', async () => {
158186
)
159187
const bundleStats = readCurrentBundleStats()
160188
const perfStats = readSummaryPerfStats()
189+
const flamegrillStats = readFlamegrillStats()
190+
191+
const mergedPerfStats = mergePerfStats(perfStats, flamegrillStats)
161192

162193
const prUrl =
163194
process.env.CIRCLE_PULL_REQUEST ||
@@ -170,7 +201,7 @@ task('stats:save', async () => {
170201
build: process.env.BUILD_BUILDID || process.env.CIRCLE_BUILD_NUM,
171202
...commandLineArgs, // allow command line overwrites
172203
bundleSize: bundleStats,
173-
performance: perfStats,
204+
performance: mergedPerfStats,
174205
ts: new Date(),
175206
}
176207

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"now-build": "cross-env NOW=true yarn predeploy:docs",
1515
"perf": "cross-env PERF=true gulp perf --times=100",
1616
"perf:debug": "cross-env PERF=true gulp perf:debug --debug",
17+
"perf:test": "yarn lerna run --stream --scope @fluentui/perf-test perf:test",
1718
"prettier": "prettier --list-different \"**/*.{ts,tsx}\"",
1819
"prettier:fix": "prettier --write \"**/*.{ts,tsx}\"",
1920
"precommit": "lint-staged",

packages/perf-test/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"scripts": {
1212
"build": "just-scripts build",
1313
"clean": "just-scripts clean",
14-
"test": "just-scripts test"
14+
"test": "just-scripts test",
15+
"perf:test": "just-scripts perf-test"
1516
},
1617
"devDependencies": {
1718
"@fluentui/digest": "^0.41.1",

packages/perf-test/tasks/perf-test.ts

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
import fs from 'fs'
22
import path from 'path'
3+
import _ from 'lodash'
34
import flamegrill, { CookResult, CookResults, ScenarioConfig, Scenarios } from 'flamegrill'
45

56
import { generateUrl } from '@fluentui/digest'
67

8+
type ExtendedCookResult = CookResult & {
9+
extended: {
10+
kind: string
11+
story: string
12+
iterations: number
13+
tpi?: number
14+
fabricTpi?: number
15+
filename?: number
16+
}
17+
}
18+
type ExtendedCookResults = Record<string, ExtendedCookResult>
19+
720
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
821
// TODO:
922
//
@@ -84,13 +97,18 @@ export default async function getPerfRegressions() {
8497
const scenarioConfig: ScenarioConfig = { outDir, tempDir }
8598
const scenarioResults = await flamegrill.cook(scenarios, scenarioConfig)
8699

87-
const comment = createReport(stories, scenarioResults)
100+
const extendedCookResults = extendCookResults(stories, scenarioResults)
101+
fs.writeFileSync(
102+
path.join(outDir, 'perfCounts.json'),
103+
JSON.stringify(extendedCookResults, null, 2),
104+
)
105+
106+
const comment = createReport(stories, extendedCookResults)
88107

89108
// TODO: determine status according to perf numbers
90109
const status = 'success'
91110

92111
console.log(`Perf evaluation status: ${status}`)
93-
console.log(`Writing comment to file:\n${comment}`)
94112

95113
// Write results to file
96114
fs.writeFileSync(path.join(outDir, 'perfCounts.html'), comment)
@@ -101,13 +119,35 @@ export default async function getPerfRegressions() {
101119
console.log(`##vso[task.setvariable variable=PerfCommentStatus;]${status}`)
102120
}
103121

122+
function extendCookResults(stories, testResults: CookResults): ExtendedCookResults {
123+
return _.mapValues(testResults, (testResult, resultKey) => {
124+
const kind = getKindKey(resultKey)
125+
const story = getStoryKey(resultKey)
126+
const iterations = getIterations(stories, kind, story)
127+
const tpi = getTpiResult(testResults, stories, kind, story) // || 'n/a'
128+
const fabricTpi = getTpiResult(testResults, stories, kind, 'Fabric') // || ''
129+
130+
return {
131+
...testResult,
132+
extended: {
133+
kind,
134+
story,
135+
iterations,
136+
tpi,
137+
fabricTpi,
138+
filename: stories[kind][story].filename,
139+
},
140+
}
141+
})
142+
}
143+
104144
/**
105145
* Create test summary based on test results.
106146
*
107147
* @param {CookResults} testResults
108148
* @returns {string}
109149
*/
110-
function createReport(stories, testResults: CookResults): string {
150+
function createReport(stories, testResults: ExtendedCookResults): string {
111151
const report = ''
112152

113153
// TODO: We can't do CI, measure baseline or do regression analysis until master & PR files are deployed and publicly accessible.
@@ -131,7 +171,7 @@ function createReport(stories, testResults: CookResults): string {
131171
* @param {boolean} showAll Show only significant results by default.
132172
* @returns {string}
133173
*/
134-
function createScenarioTable(stories, testResults: CookResults, showAll: boolean): string {
174+
function createScenarioTable(stories, testResults: ExtendedCookResults, showAll: boolean): string {
135175
const resultsToDisplay = Object.keys(testResults)
136176
.filter(
137177
key =>
@@ -191,18 +231,27 @@ function createScenarioTable(stories, testResults: CookResults, showAll: boolean
191231
resultsToDisplay
192232
.map(resultKey => {
193233
const testResult = testResults[resultKey]
194-
const kind = getKindKey(resultKey)
195-
const story = getStoryKey(resultKey)
196-
const iterations = getIterations(stories, kind, story)
197-
const tpi = getTpiResult(testResults, stories, kind, story) || 'n/a'
198-
const fabricTpi = getTpiResult(testResults, stories, kind, 'Fabric') || ''
234+
const tpi = testResult.extended.tpi
235+
? linkifyResult(
236+
testResult,
237+
testResult.extended.tpi.toLocaleString('en', { maximumSignificantDigits: 2 }),
238+
false,
239+
)
240+
: 'n/a'
241+
const fabricTpi = testResult.extended.fabricTpi
242+
? linkifyResult(
243+
testResult,
244+
testResult.extended.fabricTpi.toLocaleString('en', { maximumSignificantDigits: 2 }),
245+
false,
246+
)
247+
: ''
199248

200249
return `<tr>
201-
<td>${kind}</td>
202-
<td>${story}</td>
250+
<td>${testResult.extended.kind}</td>
251+
<td>${testResult.extended.story}</td>
203252
<td>${fabricTpi}</td>
204253
<td>${tpi}</td>
205-
<td>${iterations}</td>
254+
<td>${testResult.extended.iterations}</td>
206255
<td>${getTicksResult(testResult, false)}</td>
207256
</tr>`
208257
})
@@ -223,23 +272,19 @@ function getStoryKey(resultKey: string): string {
223272
return story
224273
}
225274

226-
function getTpiResult(testResults, stories, kind, story): string | undefined {
275+
function getTpiResult(testResults, stories, kind, story): number | undefined {
227276
let tpi = undefined
228277
if (stories[kind][story]) {
229278
const resultKey = `${kind}.${story}`
230279
const testResult = testResults[resultKey]
231280
const ticks = getTicks(testResult)
232281
const iterations = getIterations(stories, kind, story)
233-
tpi =
234-
ticks &&
235-
iterations &&
236-
(ticks / iterations).toLocaleString('en', { maximumSignificantDigits: 2 })
237-
tpi = linkifyResult(testResult, tpi, false)
282+
tpi = ticks && iterations && Math.round((ticks / iterations) * 100) / 100
238283
}
239284
return tpi
240285
}
241286

242-
function getIterations(stories, kind, story) {
287+
function getIterations(stories, kind, story): number {
243288
// Give highest priority to most localized definition of iterations. Story => kind => default.
244289
return (
245290
stories[kind][story].iterations ||

0 commit comments

Comments
 (0)