aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Corentin Thomasset <corentin.thomasset74@gmail.com> 2023-04-05 22:55:40 +0200
committerGravatar Corentin THOMASSET <corentin.thomasset74@gmail.com> 2023-04-05 22:57:58 +0200
commit6e84ea40616ab95d90f7ff6f8ab1f7723dea7112 (patch)
tree848ae64ed8e01bf020ec14d86e473778f421277b
parent004cb83719492f06370179388a6d8e2e6cacadce (diff)
downloadit-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.ts34
-rw-r--r--src/tools/benchmark-builder/benchmark-builder.vue117
-rw-r--r--src/tools/benchmark-builder/dynamic-values.vue61
-rw-r--r--src/tools/benchmark-builder/index.ts11
-rw-r--r--src/tools/index.ts3
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',