@jsenv/performance-impact 2.3.0 → 3.0.0
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 +10 -16
- package/src/importMetricFromFiles.js +3 -3
- package/src/internal/assertions.js +10 -4
- package/src/internal/comment/formatImpact.js +9 -2
- package/src/internal/comment/jsenvFormatGroupSummary.js +14 -3
- package/src/internal/comment/jsenvIsPerformanceImpactBig.js +6 -2
- package/src/internal/comment/renderGroupImpactTable.js +10 -3
- package/src/internal/formatMetricValue.js +3 -4
- package/src/internal/formatRatio.js +6 -3
- package/src/internal/measureMultipleTimes.js +29 -16
- package/src/main.js +5 -0
- package/src/reportPerformanceImpact.js +5 -7
- package/src/startMeasures.js +2 -1
- package/main.js +0 -5
- package/src/internal/formatByte.js +0 -10
- package/src/internal/formatMs.js +0 -13
- package/src/internal/format_percentage.js +0 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/performance-impact",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Report pull request impacts on performance metrics",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -23,28 +23,22 @@
|
|
|
23
23
|
"type": "module",
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
|
-
"import": "./main.js"
|
|
26
|
+
"import": "./src/main.js"
|
|
27
27
|
},
|
|
28
28
|
"./*": "./*"
|
|
29
29
|
},
|
|
30
|
-
"main": "./main.js",
|
|
30
|
+
"main": "./src/main.js",
|
|
31
31
|
"files": [
|
|
32
|
-
"/src/"
|
|
33
|
-
"/main.js"
|
|
32
|
+
"/src/"
|
|
34
33
|
],
|
|
35
34
|
"scripts": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"test": "node ./script/test/test.mjs",
|
|
39
|
-
"test-with-coverage": "npm run test -- --coverage",
|
|
40
|
-
"prettier": "prettier --write ."
|
|
35
|
+
"snapshot": "node ./tests/comment_snapshot/generate_comment_snapshot_file.mjs",
|
|
36
|
+
"test": "node ./scripts/test.mjs"
|
|
41
37
|
},
|
|
42
38
|
"dependencies": {
|
|
43
|
-
"@jsenv/dynamic-import-worker": "1.0
|
|
44
|
-
"@jsenv/filesystem": "
|
|
45
|
-
"@jsenv/github-pull-request-impact": "1.
|
|
46
|
-
"@jsenv/
|
|
47
|
-
"bytes": "3.1.1",
|
|
48
|
-
"humanize-duration": "3.27.1"
|
|
39
|
+
"@jsenv/dynamic-import-worker": "1.1.0",
|
|
40
|
+
"@jsenv/filesystem": "4.1.0",
|
|
41
|
+
"@jsenv/github-pull-request-impact": "1.7.0",
|
|
42
|
+
"@jsenv/log": "2.1.0"
|
|
49
43
|
}
|
|
50
44
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertAndNormalizeDirectoryUrl
|
|
2
|
-
import { createLogger } from "@jsenv/
|
|
1
|
+
import { assertAndNormalizeDirectoryUrl } from "@jsenv/filesystem"
|
|
2
|
+
import { createLogger } from "@jsenv/log"
|
|
3
3
|
import { importOneExportFromFile } from "@jsenv/dynamic-import-worker"
|
|
4
4
|
|
|
5
5
|
import { measureMultipleTimes } from "./internal/measureMultipleTimes.js"
|
|
@@ -27,7 +27,7 @@ export const importMetricFromFiles = async ({
|
|
|
27
27
|
iterations,
|
|
28
28
|
msToWaitBetweenEachIteration = 100,
|
|
29
29
|
} = metricsDescription
|
|
30
|
-
const url =
|
|
30
|
+
const url = new URL(file, directoryUrl).href
|
|
31
31
|
|
|
32
32
|
const measure = async () => {
|
|
33
33
|
const metrics = await importOneExportFromFile(url)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { createDetailedMessage } from "@jsenv/
|
|
1
|
+
import { createDetailedMessage } from "@jsenv/log"
|
|
2
2
|
|
|
3
3
|
export const assertPerformanceReport = (performanceReport) => {
|
|
4
4
|
if (typeof performanceReport !== "object" || performanceReport === null) {
|
|
5
|
-
throw new TypeError(
|
|
5
|
+
throw new TypeError(
|
|
6
|
+
`performanceReport must be an object, received ${performanceReport}.`,
|
|
7
|
+
)
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
Object.keys(performanceReport).forEach((groupName) => {
|
|
@@ -23,12 +25,16 @@ export const assertMetrics = (metrics, metricsTrace) => {
|
|
|
23
25
|
Object.keys(metrics).forEach((metricName) => {
|
|
24
26
|
const metric = metrics[metricName]
|
|
25
27
|
if (typeof metric !== "object") {
|
|
26
|
-
throw new TypeError(
|
|
28
|
+
throw new TypeError(
|
|
29
|
+
`metric must be an object, got ${metric} for ${metricName}`,
|
|
30
|
+
)
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
const { value } = metric
|
|
30
34
|
if (typeof value !== "number") {
|
|
31
|
-
throw new TypeError(
|
|
35
|
+
throw new TypeError(
|
|
36
|
+
`metric value must be a number, got ${value} for ${metricName}`,
|
|
37
|
+
)
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
const { unit } = metric
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { formatMetricValue } from "../formatMetricValue.js"
|
|
2
2
|
import { formatRatioAsPercentage } from "../formatRatio.js"
|
|
3
3
|
|
|
4
|
-
export const formatImpact = ({
|
|
4
|
+
export const formatImpact = ({
|
|
5
|
+
metricValueBeforeMerge,
|
|
6
|
+
metricValueAfterMerge,
|
|
7
|
+
metricUnit,
|
|
8
|
+
}) => {
|
|
5
9
|
const diff = metricValueAfterMerge - metricValueBeforeMerge
|
|
6
10
|
|
|
7
11
|
if (diff === 0) {
|
|
@@ -16,7 +20,10 @@ export const formatImpact = ({ metricValueBeforeMerge, metricValueAfterMerge, me
|
|
|
16
20
|
return diffFormatted
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
export const formatImpactAsPercentage = ({
|
|
23
|
+
export const formatImpactAsPercentage = ({
|
|
24
|
+
metricValueBeforeMerge,
|
|
25
|
+
metricValueAfterMerge,
|
|
26
|
+
}) => {
|
|
20
27
|
const diff = metricValueAfterMerge - metricValueBeforeMerge
|
|
21
28
|
const diffRatio =
|
|
22
29
|
metricValueBeforeMerge === 0
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { formatRatioAsPercentage } from "../formatRatio.js"
|
|
2
2
|
|
|
3
|
-
export const jsenvFormatGroupSummary = ({
|
|
3
|
+
export const jsenvFormatGroupSummary = ({
|
|
4
|
+
groupName,
|
|
5
|
+
beforeMergeMetrics,
|
|
6
|
+
afterMergeMetrics,
|
|
7
|
+
}) => {
|
|
4
8
|
if (!beforeMergeMetrics) {
|
|
5
9
|
return `${groupName} (new)`
|
|
6
10
|
}
|
|
7
11
|
|
|
8
|
-
const allRatios = getAllImpactRatios({
|
|
12
|
+
const allRatios = getAllImpactRatios({
|
|
13
|
+
beforeMergeMetrics,
|
|
14
|
+
afterMergeMetrics,
|
|
15
|
+
})
|
|
9
16
|
const ratiosCount = allRatios.length
|
|
10
17
|
if (ratiosCount === 0) {
|
|
11
18
|
return `${groupName} (no impact)`
|
|
@@ -31,7 +38,11 @@ const getAllImpactRatios = ({ afterMergeMetrics, beforeMergeMetrics }) => {
|
|
|
31
38
|
const metricValueBeforeMerge = metricBeforeMerge.value
|
|
32
39
|
const metricValueAfterMerge = metricAfterMerge.value
|
|
33
40
|
const metricDiff = metricValueAfterMerge - metricValueBeforeMerge
|
|
34
|
-
if (
|
|
41
|
+
if (
|
|
42
|
+
metricDiff === 0 &&
|
|
43
|
+
metricValueAfterMerge === 0 &&
|
|
44
|
+
metricValueBeforeMerge === 0
|
|
45
|
+
) {
|
|
35
46
|
// there is no impact on a metric that is 0
|
|
36
47
|
// we can ignore this metric
|
|
37
48
|
return
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
export const jsenvIsPerformanceImpactBig = ({
|
|
1
|
+
export const jsenvIsPerformanceImpactBig = ({
|
|
2
|
+
metricValueBeforeMerge,
|
|
3
|
+
metricValueDelta,
|
|
4
|
+
}) => {
|
|
2
5
|
const absoluteDelta = Math.abs(metricValueDelta)
|
|
3
|
-
const absoluteDeltaAsRatio =
|
|
6
|
+
const absoluteDeltaAsRatio =
|
|
7
|
+
absoluteDelta === 0 ? 0 : absoluteDelta / metricValueBeforeMerge
|
|
4
8
|
const absoluteDeltaAsPercentage = absoluteDeltaAsRatio * 100
|
|
5
9
|
|
|
6
10
|
// absolute diff as percentage is below 5% -> not big
|
|
@@ -47,7 +47,9 @@ const renderPerfImpactTableBody = ({
|
|
|
47
47
|
const metricNameDisplayed = truncateMetricName(metricName)
|
|
48
48
|
const metric = afterMergeMetrics[metricName]
|
|
49
49
|
const metricValueAfterMerge = metric.value
|
|
50
|
-
const metricBeforeMerge = beforeMergeMetrics
|
|
50
|
+
const metricBeforeMerge = beforeMergeMetrics
|
|
51
|
+
? beforeMergeMetrics[metricName]
|
|
52
|
+
: undefined
|
|
51
53
|
if (!metricBeforeMerge) {
|
|
52
54
|
lines.push([
|
|
53
55
|
`<td nowrap>${metricNameDisplayed}</td>`,
|
|
@@ -84,14 +86,19 @@ const renderPerfImpactTableBody = ({
|
|
|
84
86
|
})
|
|
85
87
|
if (metricNames !== metricAllNames) {
|
|
86
88
|
lines.push([
|
|
87
|
-
`<td colspan="5" align="center">... ${
|
|
89
|
+
`<td colspan="5" align="center">... ${
|
|
90
|
+
metricCount - MAX_METRIC_PER_GROUP
|
|
91
|
+
} more ...</td>`,
|
|
88
92
|
])
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
return renderTableLines(lines)
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
const renderEmojiCellContent = ({
|
|
98
|
+
const renderEmojiCellContent = ({
|
|
99
|
+
metricValueAfterMerge,
|
|
100
|
+
metricValueBeforeMerge,
|
|
101
|
+
}) => {
|
|
95
102
|
const delta = metricValueAfterMerge - metricValueBeforeMerge
|
|
96
103
|
if (delta === 0) {
|
|
97
104
|
return ":ghost:"
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { formatByte } from "./formatByte.js"
|
|
1
|
+
import { byteAsFileSize, msAsDuration } from "@jsenv/log"
|
|
3
2
|
|
|
4
3
|
export const formatMetricValue = ({ value, unit }) => {
|
|
5
4
|
return formatters[unit](value)
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
const formatters = {
|
|
9
|
-
ms:
|
|
10
|
-
byte:
|
|
8
|
+
ms: msAsDuration,
|
|
9
|
+
byte: byteAsFileSize,
|
|
11
10
|
undefined: (value) => value,
|
|
12
11
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setRoundedPrecision } from "@jsenv/log"
|
|
2
2
|
|
|
3
3
|
export const formatRatioAsPercentage = (ratio) => {
|
|
4
|
-
const
|
|
5
|
-
return
|
|
4
|
+
const percentage = ratio * 100
|
|
5
|
+
return `${percentage < 0 ? `-` : "+"}${setRoundedPrecision(percentage, {
|
|
6
|
+
decimals: 0,
|
|
7
|
+
decimalsWhenSmall: 1,
|
|
8
|
+
})}%`
|
|
6
9
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createDetailedMessage } from "@jsenv/
|
|
1
|
+
import { createDetailedMessage } from "@jsenv/log"
|
|
2
2
|
|
|
3
3
|
import { assertMetrics } from "./assertions.js"
|
|
4
4
|
|
|
@@ -11,14 +11,18 @@ export const measureMultipleTimes = async (
|
|
|
11
11
|
throw new TypeError(`measure must be a function, received ${measure}`)
|
|
12
12
|
}
|
|
13
13
|
if (typeof iterationCount !== "number") {
|
|
14
|
-
throw new TypeError(
|
|
14
|
+
throw new TypeError(
|
|
15
|
+
`iterationCount must be a number, received ${iterationCount}`,
|
|
16
|
+
)
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
const firstIterationMetrics = await measure()
|
|
18
20
|
assertMetrics(firstIterationMetrics)
|
|
19
21
|
const firstIterationMetricNames = Object.keys(firstIterationMetrics)
|
|
20
22
|
if (firstIterationMetricNames.length === 0) {
|
|
21
|
-
throw new Error(
|
|
23
|
+
throw new Error(
|
|
24
|
+
`measure must return a non empty object, received an object without key`,
|
|
25
|
+
)
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
const metricsWithIteration = {}
|
|
@@ -41,14 +45,20 @@ export const measureMultipleTimes = async (
|
|
|
41
45
|
const currentMetrics = await measure()
|
|
42
46
|
assertMetrics(currentMetrics)
|
|
43
47
|
const currentIterationMetricNames = Object.keys(currentMetrics)
|
|
44
|
-
const missingMetricNamesInCurrentIteration =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const missingMetricNamesInCurrentIteration =
|
|
49
|
+
firstIterationMetricNames.filter(
|
|
50
|
+
(firstIterationMetricName) =>
|
|
51
|
+
!currentIterationMetricNames.includes(firstIterationMetricName),
|
|
52
|
+
)
|
|
53
|
+
const extraMetricNamesInCurrentIteration =
|
|
54
|
+
currentIterationMetricNames.filter(
|
|
55
|
+
(currentIterationMetricName) =>
|
|
56
|
+
!firstIterationMetricNames.includes(currentIterationMetricName),
|
|
57
|
+
)
|
|
58
|
+
if (
|
|
59
|
+
missingMetricNamesInCurrentIteration.length ||
|
|
60
|
+
extraMetricNamesInCurrentIteration.length
|
|
61
|
+
) {
|
|
52
62
|
throw new Error(
|
|
53
63
|
createVariableMetricNamesErrorMessage({
|
|
54
64
|
missingMetricNamesInCurrentIteration,
|
|
@@ -63,11 +73,14 @@ export const measureMultipleTimes = async (
|
|
|
63
73
|
const currentMetric = currentMetrics[metricName]
|
|
64
74
|
if (currentMetric.unit !== metricWithIteration.unit) {
|
|
65
75
|
throw new Error(
|
|
66
|
-
createDetailedMessage(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
76
|
+
createDetailedMessage(
|
|
77
|
+
`A metric unit has changed between iterations.`,
|
|
78
|
+
{
|
|
79
|
+
"metric unit on first iteration": metricWithIteration.unit,
|
|
80
|
+
[`metric unit on iteration ${index + 1}`]: currentMetric.unit,
|
|
81
|
+
"metric name": metricName,
|
|
82
|
+
},
|
|
83
|
+
),
|
|
71
84
|
)
|
|
72
85
|
}
|
|
73
86
|
metricWithIteration.values.push(currentMetric.value)
|
package/src/main.js
ADDED
|
@@ -18,7 +18,7 @@ export const reportPerformanceImpact = async ({
|
|
|
18
18
|
pullRequestNumber,
|
|
19
19
|
|
|
20
20
|
installCommand = "npm install",
|
|
21
|
-
|
|
21
|
+
performanceReportUrl,
|
|
22
22
|
isPerformanceImpactBig = jsenvCommentParameters.isPerformanceImpactBig,
|
|
23
23
|
formatGroupSummary = jsenvCommentParameters.formatGroupSummary,
|
|
24
24
|
formatPerformanceImpactCell = jsenvCommentParameters.formatPerformanceImpactCell,
|
|
@@ -27,14 +27,12 @@ export const reportPerformanceImpact = async ({
|
|
|
27
27
|
commitInGeneratedByInfo,
|
|
28
28
|
}) => {
|
|
29
29
|
rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
} else if (performanceReportPath instanceof URL) {
|
|
34
|
-
performanceReportUrl = performanceReportPath.href
|
|
30
|
+
if (performanceReportUrl === "string") {
|
|
31
|
+
performanceReportUrl = new URL(performanceReportUrl, rootDirectoryUrl).href
|
|
32
|
+
} else if (performanceReportUrl instanceof URL) {
|
|
35
33
|
} else {
|
|
36
34
|
throw new TypeError(
|
|
37
|
-
`
|
|
35
|
+
`performanceReportUrl must be a string or an url but received ${performanceReportUrl}`,
|
|
38
36
|
)
|
|
39
37
|
}
|
|
40
38
|
|
package/src/startMeasures.js
CHANGED
|
@@ -34,7 +34,8 @@ export const startMeasures = ({
|
|
|
34
34
|
const beforeRessourceUsage = resourceUsage()
|
|
35
35
|
measures.push(() => {
|
|
36
36
|
const afterRessourceUsage = resourceUsage()
|
|
37
|
-
const fileSystemReadOperationCount =
|
|
37
|
+
const fileSystemReadOperationCount =
|
|
38
|
+
afterRessourceUsage.fsRead - beforeRessourceUsage.fsRead
|
|
38
39
|
const fileSystemWriteOperationCount =
|
|
39
40
|
afterRessourceUsage.fsWrite - beforeRessourceUsage.fsWrite
|
|
40
41
|
return {
|
package/main.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { startMeasures } from "./src/startMeasures.js"
|
|
2
|
-
export { importMetricFromFiles } from "./src/importMetricFromFiles.js"
|
|
3
|
-
|
|
4
|
-
export { readGitHubWorkflowEnv } from "@jsenv/github-pull-request-impact"
|
|
5
|
-
export { reportPerformanceImpact } from "./src/reportPerformanceImpact.js"
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module"
|
|
2
|
-
|
|
3
|
-
const require = createRequire(import.meta.url)
|
|
4
|
-
|
|
5
|
-
// https://github.com/visionmedia/bytes.js/
|
|
6
|
-
const bytes = require("bytes")
|
|
7
|
-
|
|
8
|
-
export const formatByte = (metricValue) => {
|
|
9
|
-
return bytes(metricValue, { decimalPlaces: 2, unitSeparator: " " })
|
|
10
|
-
}
|
package/src/internal/formatMs.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module"
|
|
2
|
-
|
|
3
|
-
const require = createRequire(import.meta.url)
|
|
4
|
-
|
|
5
|
-
const humanizeDuration = require("humanize-duration")
|
|
6
|
-
|
|
7
|
-
export const formatMs = (metricValue) => {
|
|
8
|
-
return humanizeDuration(metricValue, {
|
|
9
|
-
largest: 2,
|
|
10
|
-
maxDecimalPoints: metricValue < 1 ? 4 : metricValue < 1000 ? 3 : 2,
|
|
11
|
-
// units: ["s"]
|
|
12
|
-
})
|
|
13
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export const formatPercentage = (number) => {
|
|
2
|
-
const absoluteValue = Math.abs(number)
|
|
3
|
-
const valueRounded = roundNumber(
|
|
4
|
-
absoluteValue,
|
|
5
|
-
determineDecimals(absoluteValue),
|
|
6
|
-
)
|
|
7
|
-
return `${number < 0 ? `-` : "+"}${valueRounded}%`
|
|
8
|
-
}
|
|
9
|
-
const determineDecimals = (number) => {
|
|
10
|
-
if (number < 0.001) {
|
|
11
|
-
return 4
|
|
12
|
-
}
|
|
13
|
-
if (number < 0.01) {
|
|
14
|
-
return 3
|
|
15
|
-
}
|
|
16
|
-
if (number < 0.1) {
|
|
17
|
-
return 2
|
|
18
|
-
}
|
|
19
|
-
if (number < 1) {
|
|
20
|
-
return 1
|
|
21
|
-
}
|
|
22
|
-
return 0
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const roundNumber = (value, maxDecimals) => {
|
|
26
|
-
const expValue = Math.pow(10, maxDecimals)
|
|
27
|
-
const roundedValue = Math.round(expValue * value) / expValue
|
|
28
|
-
const valueAsString = roundedValue.toFixed(maxDecimals)
|
|
29
|
-
return parseFloat(valueAsString)
|
|
30
|
-
}
|