@jsenv/performance-impact 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/internal/assertions.js +2 -7
- package/src/internal/comment/createPerfImpactComment.js +4 -4
- package/src/internal/comment/jsenvFormatGroupSummary.js +12 -7
- package/src/startMeasures.js +23 -24
- package/src/internal/collectOneExportFromFile.js +0 -64
- package/src/internal/worker_collecting_one_export.js +0 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/performance-impact",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Report pull request impacts on performance metrics",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
"node": ">=14.17.0"
|
|
17
17
|
},
|
|
18
18
|
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
20
|
-
"registry": "https://registry.npmjs.org"
|
|
19
|
+
"access": "public"
|
|
21
20
|
},
|
|
22
21
|
"type": "module",
|
|
23
22
|
"exports": {
|
|
@@ -44,6 +43,7 @@
|
|
|
44
43
|
"postpublish": "node ./script/publish/restore_postinstall.mjs"
|
|
45
44
|
},
|
|
46
45
|
"dependencies": {
|
|
46
|
+
"@jsenv/dynamic-import-worker": "1.0.0",
|
|
47
47
|
"@jsenv/filesystem": "2.3.1",
|
|
48
48
|
"@jsenv/github-pull-request-impact": "1.6.4",
|
|
49
49
|
"@jsenv/logger": "4.0.1",
|
|
@@ -5,13 +5,8 @@ export const assertPerformanceReport = (performanceReport) => {
|
|
|
5
5
|
throw new TypeError(`performanceReport must be an object, received ${performanceReport}.`)
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
throw new TypeError(`performanceReport.groups must be an object, found ${groups}.`)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
Object.keys(groups).forEach((groupName) => {
|
|
14
|
-
const metrics = groups[groupName]
|
|
8
|
+
Object.keys(performanceReport).forEach((groupName) => {
|
|
9
|
+
const metrics = performanceReport[groupName]
|
|
15
10
|
assertMetrics(metrics)
|
|
16
11
|
})
|
|
17
12
|
}
|
|
@@ -13,11 +13,11 @@ export const createPerfImpactComment = ({
|
|
|
13
13
|
|
|
14
14
|
// here we have are sure beforeMergeData and afterMergeData are valid
|
|
15
15
|
// because we assert their shape is correct in reportPerformanceImpact.js#collectInfo()
|
|
16
|
-
// for
|
|
17
|
-
// for
|
|
16
|
+
// for metrics here before merge but are no longer in afetr merge, we will ignore them
|
|
17
|
+
// for new metrics we'll show them without doing the diff
|
|
18
18
|
|
|
19
|
-
const
|
|
20
|
-
const
|
|
19
|
+
const beforeMergeGroups = beforeMergeData
|
|
20
|
+
const afterMergeGroups = afterMergeData
|
|
21
21
|
|
|
22
22
|
const groupNames = Object.keys(afterMergeGroups)
|
|
23
23
|
const metricCount = groupNames.reduce((previous, groupName) => {
|
|
@@ -5,16 +5,21 @@ export const jsenvFormatGroupSummary = ({ groupName, beforeMergeMetrics, afterMe
|
|
|
5
5
|
return `${groupName} (new)`
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
if (
|
|
8
|
+
const allRatios = getAllImpactRatios({ beforeMergeMetrics, afterMergeMetrics })
|
|
9
|
+
if (allRatios.length === 0) {
|
|
10
10
|
return `${groupName} (no impact)`
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const ratioSum = allRatios.reduce((previous, ratio) => previous + ratio, 0)
|
|
14
|
+
if (ratioSum === 0) {
|
|
15
|
+
return `${groupName} (no impact)`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return `${groupName} (${formatRatioAsPercentage(ratioSum)})`
|
|
14
19
|
}
|
|
15
20
|
|
|
16
|
-
const
|
|
17
|
-
let
|
|
21
|
+
const getAllImpactRatios = ({ afterMergeMetrics, beforeMergeMetrics }) => {
|
|
22
|
+
let allRatios = []
|
|
18
23
|
Object.keys(afterMergeMetrics).forEach((metricName) => {
|
|
19
24
|
const metricBeforeMerge = beforeMergeMetrics[metricName]
|
|
20
25
|
if (!metricBeforeMerge) {
|
|
@@ -33,7 +38,7 @@ const getTotalImpactRatio = ({ afterMergeMetrics, beforeMergeMetrics }) => {
|
|
|
33
38
|
: metricValueAfterMerge === 0
|
|
34
39
|
? -1
|
|
35
40
|
: metricDiff / metricValueBeforeMerge
|
|
36
|
-
|
|
41
|
+
allRatios.push(metricDiffRatio)
|
|
37
42
|
})
|
|
38
|
-
return
|
|
43
|
+
return allRatios
|
|
39
44
|
}
|
package/src/startMeasures.js
CHANGED
|
@@ -8,20 +8,17 @@ export const startMeasures = ({
|
|
|
8
8
|
filesystemUsage = false,
|
|
9
9
|
gc = false,
|
|
10
10
|
} = {}) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
global.gc
|
|
17
|
-
|
|
18
|
-
CODE: "EXPOSE_GC_IS_MISSING",
|
|
19
|
-
detail: "global.gc forced with v8.setFlagsFromString",
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
global.gc()
|
|
11
|
+
if (gc && !global.gc) {
|
|
12
|
+
v8.setFlagsFromString("--expose_gc")
|
|
13
|
+
global.gc = runInNewContext("gc")
|
|
14
|
+
emitWarning(`node must be started with --expose-gc`, {
|
|
15
|
+
CODE: "EXPOSE_GC_IS_MISSING",
|
|
16
|
+
detail: "global.gc forced with v8.setFlagsFromString",
|
|
17
|
+
})
|
|
23
18
|
}
|
|
24
19
|
|
|
20
|
+
const measures = []
|
|
21
|
+
|
|
25
22
|
if (duration) {
|
|
26
23
|
const beforeMs = Date.now()
|
|
27
24
|
measures.push(() => {
|
|
@@ -33,17 +30,6 @@ export const startMeasures = ({
|
|
|
33
30
|
})
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
if (memoryHeapUsage) {
|
|
37
|
-
const beforeHeapUsed = memoryUsage().heapUsed
|
|
38
|
-
measures.push(() => {
|
|
39
|
-
const afterHeapUsed = memoryUsage().heapUsed
|
|
40
|
-
const heapUsed = afterHeapUsed - beforeHeapUsed
|
|
41
|
-
return {
|
|
42
|
-
heapUsed,
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
|
|
47
33
|
if (filesystemUsage) {
|
|
48
34
|
const beforeRessourceUsage = resourceUsage()
|
|
49
35
|
measures.push(() => {
|
|
@@ -58,11 +44,24 @@ export const startMeasures = ({
|
|
|
58
44
|
})
|
|
59
45
|
}
|
|
60
46
|
|
|
61
|
-
|
|
47
|
+
if (memoryHeapUsage) {
|
|
62
48
|
if (gc) {
|
|
63
49
|
global.gc()
|
|
64
50
|
}
|
|
51
|
+
const beforeHeapUsed = memoryUsage().heapUsed
|
|
52
|
+
measures.push(() => {
|
|
53
|
+
if (gc) {
|
|
54
|
+
global.gc()
|
|
55
|
+
}
|
|
56
|
+
const afterHeapUsed = memoryUsage().heapUsed
|
|
57
|
+
const heapUsed = afterHeapUsed - beforeHeapUsed
|
|
58
|
+
return {
|
|
59
|
+
heapUsed,
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}
|
|
65
63
|
|
|
64
|
+
const stop = () => {
|
|
66
65
|
let metrics = {}
|
|
67
66
|
measures.forEach((measure) => {
|
|
68
67
|
metrics = {
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// TODO: This deserves its own npm package
|
|
2
|
-
|
|
3
|
-
import { Worker } from "node:worker_threads"
|
|
4
|
-
import { existsSync } from "node:fs"
|
|
5
|
-
import { fileURLToPath } from "node:url"
|
|
6
|
-
|
|
7
|
-
// we use a worker to bypass node cache on dynamic import
|
|
8
|
-
const WORKER_COLLECTING_ON_EXPORT_FILE_URL = new URL(
|
|
9
|
-
"./worker_collecting_one_export.js",
|
|
10
|
-
import.meta.url,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
export const collectOneExportFromFile = async (fileUrl) => {
|
|
14
|
-
const { hash, urlWithoutHash } = extractHashFromUrl(fileUrl)
|
|
15
|
-
if (!hash) {
|
|
16
|
-
throw new Error(`no hash found in fileUrl ${fileUrl}`)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const url = urlWithoutHash
|
|
20
|
-
const exportName = hash.slice(1)
|
|
21
|
-
|
|
22
|
-
const exists = existsSync(fileURLToPath(new URL(url)))
|
|
23
|
-
if (!exists) {
|
|
24
|
-
throw new Error(`File not found at ${url}`)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const worker = new Worker(WORKER_COLLECTING_ON_EXPORT_FILE_URL, {
|
|
28
|
-
workerData: {
|
|
29
|
-
url,
|
|
30
|
-
exportName,
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
let errorData
|
|
35
|
-
let messageData
|
|
36
|
-
await new Promise((resolve) => {
|
|
37
|
-
worker.once("message", (message) => {
|
|
38
|
-
messageData = message
|
|
39
|
-
})
|
|
40
|
-
worker.once("error", (error) => {
|
|
41
|
-
errorData = error
|
|
42
|
-
})
|
|
43
|
-
worker.once("exit", () => {
|
|
44
|
-
resolve()
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
if (errorData) {
|
|
49
|
-
const error = new Error(errorData.message)
|
|
50
|
-
error.name = errorData.name
|
|
51
|
-
error.stack = errorData.stack
|
|
52
|
-
throw error
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return messageData
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const extractHashFromUrl = (url) => {
|
|
59
|
-
const urlObject = new URL(url)
|
|
60
|
-
const { hash } = urlObject
|
|
61
|
-
urlObject.hash = ""
|
|
62
|
-
const urlWithoutHash = String(urlObject)
|
|
63
|
-
return { hash, urlWithoutHash }
|
|
64
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { parentPort, workerData } from "node:worker_threads"
|
|
2
|
-
|
|
3
|
-
const { url, exportName } = workerData
|
|
4
|
-
const namespace = await import(url)
|
|
5
|
-
|
|
6
|
-
if (!Object.prototype.hasOwnProperty.call(namespace, exportName)) {
|
|
7
|
-
throw new Error(`No export named "${exportName}" in ${url}`)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const exportValue = namespace[exportName]
|
|
11
|
-
if (typeof exportValue === "function") {
|
|
12
|
-
const metrics = await exportValue()
|
|
13
|
-
parentPort.postMessage(metrics)
|
|
14
|
-
} else {
|
|
15
|
-
const metrics = exportValue
|
|
16
|
-
parentPort.postMessage(metrics)
|
|
17
|
-
}
|