Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(utils): add benchmark logic and example #137

Merged
merged 18 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,25 @@ Projects are tagged in two different dimensions - scope and type:
| `type:feature` | library with business logic for a specific feature | `type:util` |
| `type:util` | general purpose utilities and types intended for reuse | `type:util` |
| `type:e2e` | E2E testing | `type:app` or `type:feature` |

### Contributing

Listed you can find information helpful when contributing to the project
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved

#### Special Targets

The repository includes a couple of common optional targets:

- `perf` - runs micro benchmarks of a project e.g. `nx perf utils` or `nx affected -t perf`

#### Special Folder

The repository standards organize reusable code specific to a target in dedicated folders at project root level.
This helps to organize and share target related code.

The following optional folders can be present in a project root;

- `perf` - micro benchmarks related code
- `test` - testing related code
- `docs` - docs related files
- `tooling` - tooling related code
4 changes: 3 additions & 1 deletion nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
"!{projectRoot}/test-setup.[jt]s",
"!{projectRoot}/**/?(*.)mock.[jt]s?(x)",
"!{projectRoot}/vite.config.[jt]s",
"!{projectRoot}/test/**/*"
"!{projectRoot}/test/**/*",
"!{projectRoot}/perf/**/*",
"!{projectRoot}/code-pushup.config.[(mj)jt]s"
BioPhoton marked this conversation as resolved.
Show resolved Hide resolved
],
"sharedGlobals": []
},
Expand Down
24 changes: 24 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@swc/core": "~1.3.51",
"@testing-library/react": "14.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@types/benchmark": "^2.1.4",
"@types/chalk": "^2.2.0",
"@types/eslint": "^8.44.2",
"@types/node": "18.14.2",
Expand All @@ -47,6 +48,7 @@
"@vitejs/plugin-react": "~4.0.0",
"@vitest/coverage-c8": "~0.32.0",
"@vitest/ui": "~0.32.0",
"benchmark": "^2.1.4",
"commitizen": "^4.3.0",
"dotenv": "^16.3.1",
"esbuild": "^0.17.17",
Expand Down
93 changes: 93 additions & 0 deletions packages/utils/perf/implementations/base.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
function groupRefToScore(audits) {
return ref => {
const score = audits.find(audit => audit.slug === ref.slug)?.score;
if (score == null) {
throw new Error(
`Group has invalid ref - audit with slug ${ref.slug} not found`,
);
}
return score;
};
}

function categoryRefToScore(audits, groups) {
return ref => {
let audit;
let group;

switch (ref.type) {
case CategoryConfigRefType.Audit:
audit = audits.find(
a => a.slug === ref.slug && a.plugin === ref.plugin,
);
if (!audit) {
throw new Error(
`Category has invalid ref - audit with slug ${ref.slug} not found in ${ref.plugin} plugin`,
);
}
return audit.score;

case CategoryConfigRefType.Group:
group = groups.find(
g => g.slug === ref.slug && g.plugin === ref.plugin,
);
if (!group) {
throw new Error(
`Category has invalid ref - group with slug ${ref.slug} not found in ${ref.plugin} plugin`,
);
}
return group.score;
default:
throw new Error(`Type ${ref.type} is unknown`);
}
};
}

export function calculateScore(refs, scoreFn) {
const numerator = refs.reduce(
(sum, ref) => sum + scoreFn(ref) * ref.weight,
0,
);
const denominator = refs.reduce((sum, ref) => sum + ref.weight, 0);
return numerator / denominator;
}

export function scoreReport(report) {
const scoredPlugins = report.plugins.map(plugin => {
const { groups, audits } = plugin;
const preparedAudits = audits.map(audit => ({
...audit,
plugin: plugin.slug,
}));
const preparedGroups =
groups?.map(group => ({
...group,
score: calculateScore(group.refs, groupRefToScore(preparedAudits)),
plugin: plugin.slug,
})) || [];

return {
...plugin,
audits: preparedAudits,
groups: preparedGroups,
};
});

// @TODO intro dict to avoid multiple find calls in the scoreFn
const allScoredAudits = scoredPlugins.flatMap(({ audits }) => audits);
const allScoredGroups = scoredPlugins.flatMap(({ groups }) => groups);

const scoredCategories = report.categories.map(category => ({
...category,
score: calculateScore(
category.refs,
categoryRefToScore(allScoredAudits, allScoredGroups),
),
}));

return {
...report,
categories: scoredCategories,
plugins: scoredPlugins,
};
}
92 changes: 92 additions & 0 deletions packages/utils/perf/implementations/optimized0.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
function groupRefToScore(audits) {
return ref => {
const score = audits.find(audit => audit.slug === ref.slug)?.score;
if (score == null) {
throw new Error(
`Group has invalid ref - audit with slug ${ref.slug} not found`,
);
}
return score;
};
}

function categoryRefToScore(audits, groups) {
return ref => {
let audit;
let group;

switch (ref.type) {
case 'audit':
audit = audits.find(
a => a.slug === ref.slug && a.plugin === ref.plugin,
);
if (!audit) {
throw new Error(
`Category has invalid ref - audit with slug ${ref.slug} not found in ${ref.plugin} plugin`,
);
}
return audit.score;

case 'group':
group = groups.find(
g => g.slug === ref.slug && g.plugin === ref.plugin,
);
if (!group) {
throw new Error(
`Category has invalid ref - group with slug ${ref.slug} not found in ${ref.plugin} plugin`,
);
}
return group.score;
default:
throw new Error(`Type ${ref.type} is unknown`);
}
};
}

export function calculateScore(refs, scoreFn) {
const numerator = refs.reduce(
(sum, ref) => sum + scoreFn(ref) * ref.weight,
0,
);
const denominator = refs.reduce((sum, ref) => sum + ref.weight, 0);
return numerator / denominator;
}

export function scoreReportOptimized0(report) {
const scoredPlugins = report.plugins.map(plugin => {
const { groups, audits } = plugin;
const preparedAudits = audits.map(audit => ({
...audit,
plugin: plugin.slug,
}));
const preparedGroups =
groups?.map(group => ({
...group,
score: calculateScore(group.refs, groupRefToScore(preparedAudits)),
plugin: plugin.slug,
})) || [];

return {
...plugin,
audits: preparedAudits,
groups: preparedGroups,
};
});

const allScoredAudits = scoredPlugins.flatMap(({ audits }) => audits);
const allScoredGroups = scoredPlugins.flatMap(({ groups }) => groups);

const scoredCategories = report.categories.map(category => ({
...category,
score: calculateScore(
category.refs,
categoryRefToScore(allScoredAudits, allScoredGroups),
),
}));

return {
...report,
categories: scoredCategories,
plugins: scoredPlugins,
};
}
64 changes: 64 additions & 0 deletions packages/utils/perf/implementations/optimized1.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export function calculateScore(refs, scoreFn) {
const numerator = refs.reduce(
(sum, ref) => sum + scoreFn(ref) * ref.weight,
0,
);
const denominator = refs.reduce((sum, ref) => sum + ref.weight, 0);
return numerator / denominator;
}

export function scoreReportOptimized1(report) {
let allScoredAuditsAndGroups = new Map();

report.plugins.forEach(plugin => {
const { groups, audits } = plugin;
audits.forEach(audit =>
allScoredAuditsAndGroups.set(`${plugin.slug}-${audit.slug}-audit`, {
...audit,
plugin: plugin.slug,
}),
);

groups?.forEach(group => {
allScoredAuditsAndGroups.set(`${plugin.slug}-${group.slug}-group`, {
...group,
score: calculateScore(group.refs, ref => {
const score = allScoredAuditsAndGroups.get(
`${plugin.slug}-${ref.slug}-audit`,
)?.score;
if (score == null) {
throw new Error(
`Group has invalid ref - audit with slug ${plugin.slug}-${ref.slug}-audit not found`,
);
}
return score;
}),
plugin: plugin.slug,
});
});
});

const scoredCategories = report.categories.reduce((categoryMap, category) => {
categoryMap.set(category.slug, {
...category,
score: calculateScore(category.refs, ref => {
const audit = allScoredAuditsAndGroups.get(
`${ref.plugin}-${ref.slug}-${ref.type}`,
);
if (!audit) {
throw new Error(
`Category has invalid ref - audit with slug ${ref.plugin}-${ref.slug}-${ref.type} not found in ${ref.plugin} plugin`,
);
}
return audit.score;
}),
});
return categoryMap;
}, new Map());

return {
...report,
categories: scoredCategories,
plugins: allScoredAuditsAndGroups,
};
}
Loading