feat(new-tool): simple benchmark calculator
This commit is contained in:
		
							parent
							
								
									004cb83719
								
							
						
					
					
						commit
						6e84ea4061
					
				
							
								
								
									
										34
									
								
								src/tools/benchmark-builder/benchmark-builder.models.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/tools/benchmark-builder/benchmark-builder.models.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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}`; | ||||
| } | ||||
							
								
								
									
										117
									
								
								src/tools/benchmark-builder/benchmark-builder.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/tools/benchmark-builder/benchmark-builder.vue
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
							
								
								
									
										61
									
								
								src/tools/benchmark-builder/dynamic-values.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/tools/benchmark-builder/dynamic-values.vue
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
							
								
								
									
										11
									
								
								src/tools/benchmark-builder/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/tools/benchmark-builder/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||
| }); | ||||
| @ -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', | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user