diff options
author | 2023-04-05 22:55:40 +0200 | |
---|---|---|
committer | 2023-04-05 22:57:58 +0200 | |
commit | 6e84ea40616ab95d90f7ff6f8ab1f7723dea7112 (patch) | |
tree | 848ae64ed8e01bf020ec14d86e473778f421277b | |
parent | 004cb83719492f06370179388a6d8e2e6cacadce (diff) | |
download | it-tools-6e84ea40616ab95d90f7ff6f8ab1f7723dea7112.tar.gz it-tools-6e84ea40616ab95d90f7ff6f8ab1f7723dea7112.tar.zst it-tools-6e84ea40616ab95d90f7ff6f8ab1f7723dea7112.zip |
feat(new-tool): simple benchmark calculator
-rw-r--r-- | src/tools/benchmark-builder/benchmark-builder.models.ts | 34 | ||||
-rw-r--r-- | src/tools/benchmark-builder/benchmark-builder.vue | 117 | ||||
-rw-r--r-- | src/tools/benchmark-builder/dynamic-values.vue | 61 | ||||
-rw-r--r-- | src/tools/benchmark-builder/index.ts | 11 | ||||
-rw-r--r-- | src/tools/index.ts | 3 |
5 files changed, 225 insertions, 1 deletions
diff --git a/src/tools/benchmark-builder/benchmark-builder.models.ts b/src/tools/benchmark-builder/benchmark-builder.models.ts new file mode 100644 index 0000000..be8f965 --- /dev/null +++ b/src/tools/benchmark-builder/benchmark-builder.models.ts @@ -0,0 +1,34 @@ +import _ from 'lodash'; + +export { computeAverage, computeVariance, arrayToMarkdownTable }; + +function computeAverage({ data }: { data: number[] }) { + if (data.length === 0) { + return 0; + } + + return _.sum(data) / data.length; +} + +function computeVariance({ data }: { data: number[] }) { + const mean = computeAverage({ data }); + + const squaredDiffs = data.map((value) => Math.pow(value - mean, 2)); + + return computeAverage({ data: squaredDiffs }); +} + +function arrayToMarkdownTable({ data, headerMap = {} }: { data: unknown[]; headerMap?: Record<string, string> }) { + if (!Array.isArray(data) || data.length === 0) { + return ''; + } + + const headers = Object.keys(data[0]); + const rows = data.map((obj) => Object.values(obj)); + + const headerRow = `| ${headers.map((header) => headerMap[header] ?? header).join(' | ')} |`; + const separatorRow = `| ${headers.map(() => '---').join(' | ')} |`; + const dataRows = rows.map((row) => `| ${row.join(' | ')} |`).join('\n'); + + return `${headerRow}\n${separatorRow}\n${dataRows}`; +} diff --git a/src/tools/benchmark-builder/benchmark-builder.vue b/src/tools/benchmark-builder/benchmark-builder.vue new file mode 100644 index 0000000..387da19 --- /dev/null +++ b/src/tools/benchmark-builder/benchmark-builder.vue @@ -0,0 +1,117 @@ +<template> + <n-scrollbar style="flex: 1" x-scrollable> + <n-space :wrap="false" style="flex: 1" justify="center" :size="0"> + <div v-for="(suite, index) of suites" :key="index"> + <n-card style="width: 292px; margin: 0 8px"> + <n-form-item label="Suite name:" :show-feedback="false" label-placement="left"> + <n-input v-model:value="suite.title" /> + </n-form-item> + + <n-divider></n-divider> + <n-form-item label="Suite values" :show-feedback="false"> + <dynamic-values v-model:values="suite.data" /> + </n-form-item> + </n-card> + + <n-space justify="center"> + <n-button quaternary class="delete-suite" @click="suites.splice(index, 1)"> + <template #icon> + <n-icon :component="Trash" depth="3" /> + </template> + Delete suite + </n-button> + <n-button + quaternary + class="delete-suite" + @click="suites.splice(index + 1, 0, { data: [0], title: `Suite ${suites.length + 1}` })" + > + <template #icon> + <n-icon :component="Plus" depth="3" /> + </template> + Add suite + </n-button> + </n-space> + </div> + </n-space> + <br /> + </n-scrollbar> + + <div style="flex: 0 0 100%"> + <div style="max-width: 600px; margin: 0 auto"> + <n-table> + <thead> + <tr> + <th>{{ header.position }}</th> + <th>{{ header.title }}</th> + <th>{{ header.size }}</th> + <th>{{ header.mean }}</th> + <th>{{ header.variance }}</th> + </tr> + </thead> + <tbody> + <tr v-for="{ title, size, mean, variance, position } of results" :key="title"> + <td>{{ position }}</td> + <td>{{ title }}</td> + <td>{{ size }}</td> + <td>{{ mean }}</td> + <td>{{ variance }}</td> + </tr> + </tbody> + </n-table> + <br /> + <n-space justify="center"> + <n-button tertiary @click="copyAsMarkdown">Copy as markdown table</n-button> + </n-space> + </div> + </div> +</template> + +<script setup lang="ts"> +import { Trash, Plus } from '@vicons/tabler'; +import { useClipboard, useStorage } from '@vueuse/core'; +import _ from 'lodash'; +import { computed } from 'vue'; +import { computeAverage, computeVariance, arrayToMarkdownTable } from './benchmark-builder.models'; +import DynamicValues from './dynamic-values.vue'; + +const suites = useStorage('benchmark-builder:suites', [ + { title: 'Suite 1', data: [5, 10] }, + { title: 'Suite 2', data: [8, 12] }, +]); + +const results = computed(() => { + return suites.value + .map(({ data: dirtyData, title }) => { + const data = dirtyData.filter(_.isNumber); + + return { + title, + size: data.length, + mean: computeAverage({ data }), + variance: computeVariance({ data }), + }; + }) + .sort((a, b) => a.mean - b.mean) + .map((value, index) => ({ position: index + 1, ...value })); +}); + +const { copy } = useClipboard(); + +const header = { + title: 'Suite name', + size: 'Sample count', + mean: 'Mean', + variance: 'Variance', + position: 'Position', +}; + +function copyAsMarkdown() { + copy(arrayToMarkdownTable({ data: results.value, headerMap: header })); +} +</script> + +<style lang="less" scoped> +.delete-suite { + margin-top: 15px; +} +</style> diff --git a/src/tools/benchmark-builder/dynamic-values.vue b/src/tools/benchmark-builder/dynamic-values.vue new file mode 100644 index 0000000..70268ae --- /dev/null +++ b/src/tools/benchmark-builder/dynamic-values.vue @@ -0,0 +1,61 @@ +<template> + <div> + <n-space v-for="(value, index) of values" :key="index" :wrap="false" style="margin-bottom: 5px" :size="5"> + <n-input-number + :ref="refs.set" + v-model:value="values[index]" + :show-button="false" + placeholder="Set your measure..." + autofocus + @keydown.enter="onInputEnter(index)" + /> + <n-tooltip> + <template #trigger> + <n-button circle quaternary @click="values.splice(index, 1)"> + <template #icon> + <n-icon :component="Trash" depth="3" /> + </template> + </n-button> + </template> + Delete value + </n-tooltip> + </n-space> + + <n-button tertiary @click="addValue"> + <template #icon> + <n-icon :component="Plus" /> + </template> + Add a measure + </n-button> + </div> +</template> + +<script setup lang="ts"> +import { Trash, Plus } from '@vicons/tabler'; +import { useTemplateRefsList, useVModel } from '@vueuse/core'; +import { NInputNumber } from 'naive-ui'; +import { nextTick } from 'vue'; + +const refs = useTemplateRefsList<typeof NInputNumber>(); + +const props = defineProps<{ values: (number | null)[] }>(); +const emit = defineEmits(['update:values']); +const values = useVModel(props, 'values', emit); + +async function addValue() { + values.value.push(null); + await nextTick(); + refs.value.at(-1)?.focus(); +} + +function onInputEnter(index: number) { + if (index === values.value.length - 1) { + addValue(); + return; + } + + refs.value.at(index + 1)?.focus(); +} +</script> + +<style scoped></style> diff --git a/src/tools/benchmark-builder/index.ts b/src/tools/benchmark-builder/index.ts new file mode 100644 index 0000000..b9dcf11 --- /dev/null +++ b/src/tools/benchmark-builder/index.ts @@ -0,0 +1,11 @@ +import { SpeedFilled } from '@vicons/material'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Benchmark builder', + path: '/benchmark-builder', + description: 'Easily compare execution time of tasks with this very simple online benchmark builder.', + keywords: ['benchmark', 'builder', 'execution', 'duration', 'mean', 'variance'], + component: () => import('./benchmark-builder.vue'), + icon: SpeedFilled, +}); diff --git a/src/tools/index.ts b/src/tools/index.ts index 2a21301..29d3242 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as benchmarkBuilder } from './benchmark-builder'; import { tool as ipv4SubnetCalculator } from './ipv4-subnet-calculator'; import { tool as dockerRunToDockerComposeConverter } from './docker-run-to-docker-compose-converter'; import { tool as htmlWysiwygEditor } from './html-wysiwyg-editor'; @@ -107,7 +108,7 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Measurement', - components: [chronometer, temperatureConverter], + components: [chronometer, temperatureConverter, benchmarkBuilder], }, { name: 'Text', |