Skip to content

Commit e55974c

Browse files
author
Blue F
authored
Merge pull request #20079 from cypress-io/issue-19403-perf-reporter-changes
chore: Performance reporter changes
2 parents f84bac5 + ce956de commit e55974c

File tree

4 files changed

+136
-42
lines changed

4 files changed

+136
-42
lines changed

circle.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ commands:
206206
name: Restore cache state, to check for known modules cache existence
207207
keys:
208208
- v{{ .Environment.CACHE_VERSION }}-{{ arch }}-system-tests-projects-node-modules-cache-state-{{ checksum "system_tests_cache_key" }}
209+
- run:
210+
name: Send root honeycomb event for this CI build
211+
command: cd system-tests/scripts && node ./send-root-honecomb-event.js
209212
- run:
210213
name: Bail if specific cache exists
211214
command: |
@@ -2042,6 +2045,7 @@ linux-workflow: &linux-workflow
20422045
requires:
20432046
- build
20442047
- system-tests-node-modules-install:
2048+
context: test-runner:performance-tracking
20452049
requires:
20462050
- build
20472051
- system-tests-chrome:

scripts/get-next-version.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ const getNextVersionForPath = async (path) => {
2424
return semver.inc(currentVersion, releaseType || 'patch')
2525
}
2626

27+
if (require.main !== module) {
28+
module.exports.getNextVersionForPath = getNextVersionForPath
29+
30+
return
31+
}
32+
2733
Bluebird.mapSeries(paths, async (path) => {
2834
const pathNextVersion = await getNextVersionForPath(path)
2935

Lines changed: 113 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,109 @@
11
const path = require('path')
22
const chalk = require('chalk')
33
const Libhoney = require('libhoney')
4+
const { v4: uuidv4 } = require('uuid')
45

5-
const pkg = require('@packages/root')
66
const ciProvider = require('@packages/server/lib/util/ci_provider')
77
const { commitInfo } = require('@cypress/commit-info')
8+
const { getNextVersionForPath } = require('../../scripts/get-next-version')
89

9-
class StatsdReporter {
10+
const honey = new Libhoney({
11+
dataset: 'systemtest-performance',
12+
writeKey: process.env.HONEYCOMB_API_KEY,
13+
})
14+
15+
// This event is created here independently every time the reporter
16+
// is imported (in each parallel instance of the system-tests
17+
// in circleci) so that we can use it as the parent,
18+
// but ../scripts/send-root-honeycomb-event.js
19+
// is only invoked once at the start of the build,
20+
// and is responsible for sending it to honeycomb.
21+
const spanId = process.env.CIRCLE_WORKFLOW_ID || uuidv4()
22+
const circleCiRootEvent = honey.newEvent()
23+
24+
circleCiRootEvent.timestamp = Date.now()
25+
circleCiRootEvent.add({
26+
buildUrl: process.env.CIRCLE_BUILD_URL,
27+
platform: process.platform,
28+
arch: process.arch,
29+
name: 'ci_run',
30+
31+
spanId,
32+
traceId: spanId,
33+
})
34+
35+
// Mocha events ('test', 'test end', etc) have no way to wait
36+
// for async callbacks, so we can't guarantee we have this
37+
// data ready by the time any of the reporter's events are emitted.
38+
39+
// Therefore, we have each honeycomb event await this promise
40+
// before sending itself.
41+
let asyncInfo = Promise.all([getNextVersionForPath(path.resolve(__dirname, '../../packages')), commitInfo()])
42+
.then(([nextVersion, commitInformation]) => {
43+
const ciInformation = ciProvider.commitParams() || {}
44+
45+
return {
46+
nextVersion,
47+
branch: commitInformation.branch || ciInformation.branch,
48+
commitSha: commitInformation.sha || ciInformation.sha,
49+
}
50+
})
51+
52+
function addAsyncInfoAndSend (honeycombEvent) {
53+
return asyncInfo.then((info) => {
54+
honeycombEvent.add(info)
55+
honeycombEvent.send()
56+
})
57+
}
58+
59+
class HoneycombReporter {
1060
constructor (runner) {
1161
if (!process.env.HONEYCOMB_API_KEY) {
1262
return
1363
}
1464

1565
console.log(chalk.green('Reporting to honeycomb'))
1666

17-
let branch
18-
let commitSha
67+
runner.on('suite', (suite) => {
68+
if (!suite.title) {
69+
return
70+
}
1971

20-
this.honey = new Libhoney({
21-
dataset: 'systemtest-performance',
22-
writeKey: process.env.HONEYCOMB_API_KEY,
23-
})
72+
const parent = suite.parent && suite.parent.honeycombEvent ? suite.parent.honeycombEvent : circleCiRootEvent
2473

25-
commitInfo().then((commitInformation) => {
26-
const ciInformation = ciProvider.commitParams() || {}
74+
suite.honeycombEvent = honey.newEvent()
75+
suite.honeycombEvent.timestamp = Date.now()
76+
suite.honeycombEvent.add({
77+
...parent.data,
78+
suite: suite.title,
79+
specFile: suite.file && path.basename(suite.file),
80+
name: 'spec_execution',
2781

28-
branch = commitInformation.branch || ciInformation.branch
29-
commitSha = commitInformation.sha || ciInformation.sha
82+
spanId: uuidv4(),
83+
parentId: parent.data.spanId,
84+
})
3085
})
3186

3287
runner.on('test', (test) => {
33-
test.wallclockStart = Date.now()
88+
const path = test.titlePath()
89+
// This regex pulls apart a string like `failing1 [electron]`
90+
// into `failing1` and `electron`, letting us use the same
91+
// test name for all browsers, with the browser as a separate field.
92+
// The browser capture group is optional because some tests aren't browser specific,
93+
// in which case it will be undefined and not passed as a field to honeycomb.
94+
const [, testTitle, browser] = path[path.length - 1].match(/(.+?)(?: \[([a-z]+)\])?$/)
95+
96+
test.honeycombEvent = honey.newEvent()
97+
test.honeycombEvent.timestamp = Date.now()
98+
test.honeycombEvent.add({
99+
...test.parent.honeycombEvent.data,
100+
test: testTitle,
101+
browser,
102+
name: 'test_execution',
103+
104+
spanId: uuidv4(),
105+
parentId: test.parent.honeycombEvent.data.spanId,
106+
})
34107
})
35108

36109
runner.on('test end', (test) => {
@@ -39,44 +112,42 @@ class StatsdReporter {
39112
return
40113
}
41114

42-
const title = test.titlePath().join(' / ')
43-
// This regex pulls apart a string like `e2e async timeouts / failing1 [electron]`
44-
// into `e2e async timeouts / failing1` and `electron`, letting us use the same
45-
// test name for all browsers, with the browser as a separate field.
46-
// The browser capture group is optional because some tests aren't browser specific,
47-
// in which case it will be undefined and not passed as a field to honeycomb.
48-
const [, testTitle, browser] = title.match(/(.+?)(?: \[([a-z]+)\])?$/)
49-
50-
const honeycombEvent = this.honey.newEvent()
51-
52-
honeycombEvent.timestamp = test.wallclockStart
53-
honeycombEvent.add({
54-
test: testTitle,
55-
specFile: path.basename(test.file),
56-
browser,
115+
test.honeycombEvent.add({
57116
state: test.state,
58117
err: test.err && test.err.message,
59118
errStack: test.err && test.err.stack,
60-
durationMs: Date.now() - test.wallclockStart,
61-
mochaDurationMs: test.duration,
62-
branch,
63-
commitSha,
64-
buildUrl: process.env.CIRCLE_BUILD_URL,
65-
platform: process.platform,
66-
arch: process.arch,
67-
version: pkg.version,
119+
durationMs: Date.now() - test.honeycombEvent.timestamp,
68120
})
69121

70-
honeycombEvent.send()
122+
addAsyncInfoAndSend(test.honeycombEvent)
123+
})
124+
125+
runner.on('suite end', (suite) => {
126+
if (!suite.honeycombEvent) {
127+
return
128+
}
129+
130+
suite.honeycombEvent.add({
131+
durationMs: Date.now() - suite.honeycombEvent.timestamp,
132+
})
133+
134+
addAsyncInfoAndSend(suite.honeycombEvent)
71135
})
72136
}
73137

74-
// If there is no done callback, then mocha-multi-reporter will kill the process without waiting for our honeycomb post to complete.
138+
// If there is no done method, then mocha-multi-reporter will kill the process
139+
// without waiting for our honeycomb posts to complete.
75140
done (failures, callback) {
76-
if (this.honey) {
77-
this.honey.flush().then(callback)
78-
}
141+
// Await the asyncInfo promise one last time, to ensure all events have
142+
// added the data and sent themselves before we flush honeycomb's queue and exit.
143+
asyncInfo
144+
.then(() => honey.flush())
145+
.then(callback)
79146
}
80147
}
81148

82-
module.exports = StatsdReporter
149+
module.exports = HoneycombReporter
150+
151+
HoneycombReporter.honey = honey
152+
HoneycombReporter.circleCiRootEvent = circleCiRootEvent
153+
HoneycombReporter.addAsyncInfoAndSend = addAsyncInfoAndSend
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const { addAsyncInfoAndSend, circleCiRootEvent, honey } = require('../lib/performance-reporter')
2+
3+
// This file is executed once during the circleci build,
4+
// so that we can send the root event honeycomb event for this
5+
// run of the system tests exactly once.
6+
// All the system test build hosts reference this root event,
7+
// joining them into a single trace.
8+
if (require.main === module) {
9+
addAsyncInfoAndSend(circleCiRootEvent).then(() => {
10+
console.log(circleCiRootEvent.data)
11+
honey.flush()
12+
})
13+
}

0 commit comments

Comments
 (0)