Skip to content

Commit 142943e

Browse files
authored
perf(utils): add benchmark logic and example (#137)
1 parent 89fecbb commit 142943e

File tree

12 files changed

+535
-2
lines changed

12 files changed

+535
-2
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,21 @@ Projects are tagged in two different dimensions - scope and type:
7878
| `type:feature` | library with business logic for a specific feature | `type:util` |
7979
| `type:util` | general purpose utilities and types intended for reuse | `type:util` |
8080
| `type:e2e` | E2E testing | `type:app` or `type:feature` |
81+
82+
#### Special Targets
83+
84+
The repository includes a couple of common optional targets:
85+
86+
- `perf` - runs micro benchmarks of a project e.g. `nx perf utils` or `nx affected -t perf`
87+
88+
#### Special Folder
89+
90+
The repository standards organize reusable code specific to a target in dedicated folders at project root level.
91+
This helps to organize and share target related code.
92+
93+
The following optional folders can be present in a project root;
94+
95+
- `perf` - micro benchmarks related code
96+
- `test` - testing related code
97+
- `docs` - docs related files
98+
- `tooling` - tooling related code

nx.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
"!{projectRoot}/test-setup.[jt]s",
4040
"!{projectRoot}/**/?(*.)mock.[jt]s?(x)",
4141
"!{projectRoot}/vite.config.[jt]s",
42-
"!{projectRoot}/test/**/*"
42+
"!{projectRoot}/test/**/*",
43+
"!{projectRoot}/perf/**/*",
44+
"!{projectRoot}/code-pushup.config.?(m)[jt]s"
4345
],
4446
"sharedGlobals": []
4547
},

package-lock.json

+24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@swc/core": "~1.3.51",
3838
"@testing-library/react": "14.0.0",
3939
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
40+
"@types/benchmark": "^2.1.4",
4041
"@types/chalk": "^2.2.0",
4142
"@types/eslint": "^8.44.2",
4243
"@types/node": "18.14.2",
@@ -47,6 +48,7 @@
4748
"@vitejs/plugin-react": "~4.0.0",
4849
"@vitest/coverage-c8": "~0.32.0",
4950
"@vitest/ui": "~0.32.0",
51+
"benchmark": "^2.1.4",
5052
"commitizen": "^4.3.0",
5153
"dotenv": "^16.3.1",
5254
"esbuild": "^0.17.17",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
function groupRefToScore(audits) {
2+
return ref => {
3+
const score = audits.find(audit => audit.slug === ref.slug)?.score;
4+
if (score == null) {
5+
throw new Error(
6+
`Group has invalid ref - audit with slug ${ref.slug} not found`,
7+
);
8+
}
9+
return score;
10+
};
11+
}
12+
13+
function categoryRefToScore(audits, groups) {
14+
return ref => {
15+
let audit;
16+
let group;
17+
18+
switch (ref.type) {
19+
case CategoryConfigRefType.Audit:
20+
audit = audits.find(
21+
a => a.slug === ref.slug && a.plugin === ref.plugin,
22+
);
23+
if (!audit) {
24+
throw new Error(
25+
`Category has invalid ref - audit with slug ${ref.slug} not found in ${ref.plugin} plugin`,
26+
);
27+
}
28+
return audit.score;
29+
30+
case CategoryConfigRefType.Group:
31+
group = groups.find(
32+
g => g.slug === ref.slug && g.plugin === ref.plugin,
33+
);
34+
if (!group) {
35+
throw new Error(
36+
`Category has invalid ref - group with slug ${ref.slug} not found in ${ref.plugin} plugin`,
37+
);
38+
}
39+
return group.score;
40+
default:
41+
throw new Error(`Type ${ref.type} is unknown`);
42+
}
43+
};
44+
}
45+
46+
export function calculateScore(refs, scoreFn) {
47+
const numerator = refs.reduce(
48+
(sum, ref) => sum + scoreFn(ref) * ref.weight,
49+
0,
50+
);
51+
const denominator = refs.reduce((sum, ref) => sum + ref.weight, 0);
52+
return numerator / denominator;
53+
}
54+
55+
export function scoreReport(report) {
56+
const scoredPlugins = report.plugins.map(plugin => {
57+
const { groups, audits } = plugin;
58+
const preparedAudits = audits.map(audit => ({
59+
...audit,
60+
plugin: plugin.slug,
61+
}));
62+
const preparedGroups =
63+
groups?.map(group => ({
64+
...group,
65+
score: calculateScore(group.refs, groupRefToScore(preparedAudits)),
66+
plugin: plugin.slug,
67+
})) || [];
68+
69+
return {
70+
...plugin,
71+
audits: preparedAudits,
72+
groups: preparedGroups,
73+
};
74+
});
75+
76+
// @TODO intro dict to avoid multiple find calls in the scoreFn
77+
const allScoredAudits = scoredPlugins.flatMap(({ audits }) => audits);
78+
const allScoredGroups = scoredPlugins.flatMap(({ groups }) => groups);
79+
80+
const scoredCategories = report.categories.map(category => ({
81+
...category,
82+
score: calculateScore(
83+
category.refs,
84+
categoryRefToScore(allScoredAudits, allScoredGroups),
85+
),
86+
}));
87+
88+
return {
89+
...report,
90+
categories: scoredCategories,
91+
plugins: scoredPlugins,
92+
};
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
function groupRefToScore(audits) {
2+
return ref => {
3+
const score = audits.find(audit => audit.slug === ref.slug)?.score;
4+
if (score == null) {
5+
throw new Error(
6+
`Group has invalid ref - audit with slug ${ref.slug} not found`,
7+
);
8+
}
9+
return score;
10+
};
11+
}
12+
13+
function categoryRefToScore(audits, groups) {
14+
return ref => {
15+
let audit;
16+
let group;
17+
18+
switch (ref.type) {
19+
case 'audit':
20+
audit = audits.find(
21+
a => a.slug === ref.slug && a.plugin === ref.plugin,
22+
);
23+
if (!audit) {
24+
throw new Error(
25+
`Category has invalid ref - audit with slug ${ref.slug} not found in ${ref.plugin} plugin`,
26+
);
27+
}
28+
return audit.score;
29+
30+
case 'group':
31+
group = groups.find(
32+
g => g.slug === ref.slug && g.plugin === ref.plugin,
33+
);
34+
if (!group) {
35+
throw new Error(
36+
`Category has invalid ref - group with slug ${ref.slug} not found in ${ref.plugin} plugin`,
37+
);
38+
}
39+
return group.score;
40+
default:
41+
throw new Error(`Type ${ref.type} is unknown`);
42+
}
43+
};
44+
}
45+
46+
export function calculateScore(refs, scoreFn) {
47+
const numerator = refs.reduce(
48+
(sum, ref) => sum + scoreFn(ref) * ref.weight,
49+
0,
50+
);
51+
const denominator = refs.reduce((sum, ref) => sum + ref.weight, 0);
52+
return numerator / denominator;
53+
}
54+
55+
export function scoreReportOptimized0(report) {
56+
const scoredPlugins = report.plugins.map(plugin => {
57+
const { groups, audits } = plugin;
58+
const preparedAudits = audits.map(audit => ({
59+
...audit,
60+
plugin: plugin.slug,
61+
}));
62+
const preparedGroups =
63+
groups?.map(group => ({
64+
...group,
65+
score: calculateScore(group.refs, groupRefToScore(preparedAudits)),
66+
plugin: plugin.slug,
67+
})) || [];
68+
69+
return {
70+
...plugin,
71+
audits: preparedAudits,
72+
groups: preparedGroups,
73+
};
74+
});
75+
76+
const allScoredAudits = scoredPlugins.flatMap(({ audits }) => audits);
77+
const allScoredGroups = scoredPlugins.flatMap(({ groups }) => groups);
78+
79+
const scoredCategories = report.categories.map(category => ({
80+
...category,
81+
score: calculateScore(
82+
category.refs,
83+
categoryRefToScore(allScoredAudits, allScoredGroups),
84+
),
85+
}));
86+
87+
return {
88+
...report,
89+
categories: scoredCategories,
90+
plugins: scoredPlugins,
91+
};
92+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
export function calculateScore(refs, scoreFn) {
2+
const numerator = refs.reduce(
3+
(sum, ref) => sum + scoreFn(ref) * ref.weight,
4+
0,
5+
);
6+
const denominator = refs.reduce((sum, ref) => sum + ref.weight, 0);
7+
return numerator / denominator;
8+
}
9+
10+
export function scoreReportOptimized1(report) {
11+
let allScoredAuditsAndGroups = new Map();
12+
13+
report.plugins.forEach(plugin => {
14+
const { groups, audits } = plugin;
15+
audits.forEach(audit =>
16+
allScoredAuditsAndGroups.set(`${plugin.slug}-${audit.slug}-audit`, {
17+
...audit,
18+
plugin: plugin.slug,
19+
}),
20+
);
21+
22+
groups?.forEach(group => {
23+
allScoredAuditsAndGroups.set(`${plugin.slug}-${group.slug}-group`, {
24+
...group,
25+
score: calculateScore(group.refs, ref => {
26+
const score = allScoredAuditsAndGroups.get(
27+
`${plugin.slug}-${ref.slug}-audit`,
28+
)?.score;
29+
if (score == null) {
30+
throw new Error(
31+
`Group has invalid ref - audit with slug ${plugin.slug}-${ref.slug}-audit not found`,
32+
);
33+
}
34+
return score;
35+
}),
36+
plugin: plugin.slug,
37+
});
38+
});
39+
});
40+
41+
const scoredCategories = report.categories.reduce((categoryMap, category) => {
42+
categoryMap.set(category.slug, {
43+
...category,
44+
score: calculateScore(category.refs, ref => {
45+
const audit = allScoredAuditsAndGroups.get(
46+
`${ref.plugin}-${ref.slug}-${ref.type}`,
47+
);
48+
if (!audit) {
49+
throw new Error(
50+
`Category has invalid ref - audit with slug ${ref.plugin}-${ref.slug}-${ref.type} not found in ${ref.plugin} plugin`,
51+
);
52+
}
53+
return audit.score;
54+
}),
55+
});
56+
return categoryMap;
57+
}, new Map());
58+
59+
return {
60+
...report,
61+
categories: scoredCategories,
62+
plugins: allScoredAuditsAndGroups,
63+
};
64+
}

0 commit comments

Comments
 (0)