Skip to content

Commit

Permalink
feat(new tool): SLA Computer
Browse files Browse the repository at this point in the history
Fix #738
  • Loading branch information
sharevb committed Sep 22, 2024
1 parent 80e46c9 commit bc46f47
Show file tree
Hide file tree
Showing 4 changed files with 358 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/tools/index.ts
Original file line number Diff line number Diff line change
@@ -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 slaCalculator } from './sla-calculator';
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
import { tool as numeronymGenerator } from './numeronym-generator';
import { tool as macAddressGenerator } from './mac-address-generator';
Expand Down Expand Up @@ -151,7 +152,12 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Measurement',
components: [chronometer, temperatureConverter, benchmarkBuilder],
components: [
chronometer,
temperatureConverter,
benchmarkBuilder,
slaCalculator,
],
},
{
name: 'Text',
Expand Down
81 changes: 81 additions & 0 deletions src/tools/sla-calculator/sla-calculator.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { describe, expect, it } from 'vitest';
import { downTimeToSLA, slaToDowntimes } from './sla-calculator.service';

describe('sla-calculator', () => {
describe('downTimeToSLA', () => {
it('compute correct values', () => {
expect(downTimeToSLA({
downTimeSecondsPerDay: 100,
mondayHours: 16,
tuesdayHours: 6,
wednesdayHours: 4,
thursdayHours: 24,
fridayHours: 21,
saturdayHours: 16,
sundayHours: 13,
})).to.deep.eq({
slaForDay: null,
slaForMonth: 99.80555555555556,
slaForQuarter: 99.80555555555556,
slaForWeek: 99.80555555555556,
slaForYear: 99.80555555555556,
});
expect(downTimeToSLA({
downTimeSecondsPerDay: 86400 / 10000,
})).to.deep.eq({
slaForDay: 99.99,
slaForMonth: 99.99,
slaForQuarter: 99.99,
slaForWeek: 99.99,
slaForYear: 99.99,
});
expect(downTimeToSLA({
downTimeSecondsPerDay: 86400 / 2,
})).to.deep.eq({
slaForDay: 50,
slaForMonth: 50,
slaForQuarter: 50,
slaForWeek: 50,
slaForYear: 50,
});
});
});
describe('slaToDowntimes', () => {
it('compute correct values', () => {
expect(slaToDowntimes({
targetSLA: 99,
mondayHours: 9,
tuesdayHours: 24,
wednesdayHours: 10,
thursdayHours: 8,
fridayHours: 3,
saturdayHours: 24,
sundayHours: 4,
})).to.deep.eq({
secondsPerDay: null,
secondsPerMonth: 12835.665,
secondsPerQuarter: 38506.995,
secondsPerWeek: 2952,
secondsPerYear: 154027.98,
});
expect(slaToDowntimes({
targetSLA: 99.99,
})).to.deep.eq({
secondsPerDay: 8.64,
secondsPerMonth: 262.9746,
secondsPerQuarter: 788.9238,
secondsPerWeek: 60.48,
secondsPerYear: 3155.6952,
});
expect(slaToDowntimes({
targetSLA: 99,
})).to.deep.eq({
secondsPerDay: 864,
secondsPerMonth: 26297.46,
secondsPerQuarter: 78892.38,
secondsPerWeek: 6048,
secondsPerYear: 315569.52,
});
});
});
});
95 changes: 95 additions & 0 deletions src/tools/sla-calculator/sla-calculator.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import Big from 'big.js';

export function downTimeToSLA({
downTimeSecondsPerDay,
mondayHours = 24,
tuesdayHours = 24,
wednesdayHours = 24,
thursdayHours = 24,
fridayHours = 24,
saturdayHours = 24,
sundayHours = 24,
}: {
downTimeSecondsPerDay: number
mondayHours?: number
tuesdayHours?: number
wednesdayHours?: number
thursdayHours?: number
fridayHours?: number
saturdayHours?: number
sundayHours?: number
}) {
const hoursDay = Big('24');
const daysWeek = Big('7');
const monthsYear = Big('12');
const daysYear = Big('365.2425');
const weeksYear = daysYear.div(daysWeek);
const daysMonth = daysYear.div(monthsYear);
const daysQuarter = daysMonth.mul('3');
const weeksMonth = daysMonth.div(daysWeek);

const durHoursWeek = Big(mondayHours).plus(tuesdayHours).plus(wednesdayHours).plus(thursdayHours).plus(fridayHours).plus(saturdayHours).plus(sundayHours);
const durSecondsWeek = durHoursWeek.mul('3600');
const durSecondsDay = durSecondsWeek.div(daysWeek);
const durSecondsMonth = durSecondsWeek.mul(weeksMonth);
const durSecondsQuarter = durSecondsMonth.mul('3');
const durSecondsYear = durSecondsWeek.mul(weeksYear);
const fullWeekHours = daysWeek.mul(hoursDay);
const one = Big('1');
const hundred = Big('100');
const downTimeSecondsPerDayBig = Big(downTimeSecondsPerDay);

return {
slaForDay: fullWeekHours.eq(durHoursWeek) ? one.minus(downTimeSecondsPerDayBig.div(durSecondsDay)).mul(hundred).toNumber() : null,
slaForWeek: one.minus(downTimeSecondsPerDayBig.mul(daysWeek).div (durSecondsWeek)).mul(hundred).toNumber(),
slaForMonth: one.minus(downTimeSecondsPerDayBig.mul(daysMonth).div (durSecondsMonth)).mul(hundred).toNumber(),
slaForQuarter: one.minus(downTimeSecondsPerDayBig.mul(daysQuarter).div (durSecondsQuarter)).mul(hundred).toNumber(),
slaForYear: one.minus(downTimeSecondsPerDayBig.mul(daysYear).div (durSecondsYear)).mul(hundred).toNumber(),
};
}

export function slaToDowntimes({
targetSLA,
mondayHours = 24,
tuesdayHours = 24,
wednesdayHours = 24,
thursdayHours = 24,
fridayHours = 24,
saturdayHours = 24,
sundayHours = 24,
}: {
targetSLA: number
mondayHours?: number
tuesdayHours?: number
wednesdayHours?: number
thursdayHours?: number
fridayHours?: number
saturdayHours?: number
sundayHours?: number
}) {
const hoursDay = Big('24');
const daysWeek = Big('7');
const monthsYear = Big('12');
const daysYear = Big('365.2425');
const weeksYear = daysYear.div(daysWeek);
const daysMonth = daysYear.div(monthsYear);
const weeksMonth = daysMonth.div(daysWeek);

const durHoursWeek = Big(mondayHours).plus(tuesdayHours).plus(wednesdayHours).plus(thursdayHours).plus(fridayHours).plus(saturdayHours).plus(sundayHours);
const durSecondsWeek = durHoursWeek.mul('3600');
const durSecondsDay = durSecondsWeek.div(daysWeek);
const durSecondsMonth = durSecondsWeek.mul(weeksMonth);
const durSecondsQuarter = durSecondsMonth.mul('3');
const durSecondsYear = durSecondsWeek.mul(weeksYear);
const fullWeekHours = daysWeek.mul(hoursDay);

const allowedDowntime = Big('1').minus(Big(targetSLA).div('100'));

return {
secondsPerDay: fullWeekHours.eq(durHoursWeek) ? allowedDowntime.mul(durSecondsDay).toNumber() : null,
secondsPerWeek: allowedDowntime.mul(durSecondsWeek).toNumber(),
secondsPerMonth: allowedDowntime.mul(durSecondsMonth).toNumber(),
secondsPerQuarter: allowedDowntime.mul(durSecondsQuarter).toNumber(),
secondsPerYear: allowedDowntime.mul(durSecondsYear).toNumber(),
};
}
175 changes: 175 additions & 0 deletions src/tools/sla-calculator/sla-calculator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<script setup lang="ts">
import prettyMilliseconds from 'pretty-ms';
import parse from 'parse-duration';
import { downTimeToSLA, slaToDowntimes } from './sla-calculator.service';
import { useQueryParam, useQueryParamOrStorage } from '@/composable/queryParams';
import { useValidation } from '@/composable/validation';
function prettySeconds(seconds: number | null) {
if (!seconds) {
return '';
}
return prettyMilliseconds(seconds * 1000);
}
const daysHours = useQueryParamOrStorage<{
mon?: number
tue?: number
wed?: number
thu?: number
fri?: number
sat?: number
sun?: number
}>({
name: 'days',
storageName: 'sla:days',
defaultValue: {
mon: 24,
tue: 24,
wed: 24,
thu: 24,
fri: 24,
sat: 24,
sun: 24,
},
});
const inputSLA = useQueryParam({ name: 'sla', defaultValue: 99.99 });
const outputDownTimes = computed(() => {
const downtimesSeconds = slaToDowntimes({
targetSLA: inputSLA.value,
mondayHours: daysHours.value.mon,
tuesdayHours: daysHours.value.tue,
wednesdayHours: daysHours.value.wed,
thursdayHours: daysHours.value.thu,
fridayHours: daysHours.value.fri,
saturdayHours: daysHours.value.sat,
sundayHours: daysHours.value.sun,
});
return {
durationPerDay: prettySeconds(downtimesSeconds.secondsPerDay),
durationPerMonth: prettySeconds(downtimesSeconds.secondsPerDay),
durationPerQuarter: prettySeconds(downtimesSeconds.secondsPerDay),
durationPerWeek: prettySeconds(downtimesSeconds.secondsPerDay),
durationPerYear: prettySeconds(downtimesSeconds.secondsPerDay),
};
});
const inputDuration = useQueryParam({ name: 'dur', defaultValue: '1s' });
const inputDurationValidation = useValidation({
source: inputDuration,
rules: [
{
message: 'Invalid duration',
validator: value => parse(value),
},
],
});
const inputDirationSeconds = computed(() => {
if (!inputDurationValidation.isValid) {
return 0;
}
return parse(inputDuration.value, 's');
});
const outputSLAs = computed(() => {
const slas = downTimeToSLA({
downTimeSecondsPerDay: inputDirationSeconds.value,
mondayHours: daysHours.value.mon,
tuesdayHours: daysHours.value.tue,
wednesdayHours: daysHours.value.wed,
thursdayHours: daysHours.value.thu,
fridayHours: daysHours.value.fri,
saturdayHours: daysHours.value.sat,
sundayHours: daysHours.value.sun,
});
return {
slaForDay: slas.slaForDay?.toPrecision(5),
slaForWeek: slas.slaForWeek.toPrecision(5),
slaForMonth: slas.slaForMonth.toPrecision(5),
slaForQuarter: slas.slaForQuarter.toPrecision(5),
slaForYear: slas.slaForYear.toPrecision(5),
};
});
</script>

<template>
<div>
<c-card title="SLA to downtimes">
<n-form-item label="Agreed SLA" label-placement="left">
<n-input-number v-model:value="inputSLA" :min="0" :max="100" />
</n-form-item>

<n-divider />

<n-form-item v-if="outputDownTimes.durationPerDay" label="Daily downtime" label-placement="left">
<textarea-copyable :value="outputDownTimes.durationPerDay" />
</n-form-item>
<n-form-item label="Weekly downtime" label-placement="left">
<textarea-copyable :value="outputDownTimes.durationPerWeek" />
</n-form-item>
<n-form-item label="Monthly downtime" label-placement="left">
<textarea-copyable :value="outputDownTimes.durationPerMonth" />
</n-form-item>
<n-form-item label="Quarterly downtime" label-placement="left">
<textarea-copyable :value="outputDownTimes.durationPerQuarter" />
</n-form-item>
<n-form-item label="Yearly downtime" label-placement="left">
<textarea-copyable :value="outputDownTimes.durationPerYear" />
</n-form-item>
</c-card>

<c-card title="Downtime to SLA">
<c-input-text
v-model:value="inputDuration"
label="Downtime duration (per day)"
label-position="left"
placeholder="Put downtime duration here..."
:validation="inputDurationValidation"
mb-2
/>

<n-divider />

<n-form-item v-if="outputSLAs.slaForDay" label="Daily SLA" label-placement="left">
<textarea-copyable :value="outputSLAs.slaForDay" />
</n-form-item>
<n-form-item label="Weekly SLA" label-placement="left">
<textarea-copyable :value="outputSLAs.slaForWeek" />
</n-form-item>
<n-form-item label="Monthly SLA" label-placement="left">
<textarea-copyable :value="outputSLAs.slaForMonth" />
</n-form-item>
<n-form-item label="Quarterly SLA" label-placement="left">
<textarea-copyable :value="outputSLAs.slaForQuarter" />
</n-form-item>
<n-form-item label="Yearly SLA" label-placement="left">
<textarea-copyable :value="outputSLAs.slaForYear" />
</n-form-item>
</c-card>

<c-card title="Weekdays hours">
<div flex>
<n-form-item label="Monday" flex-1>
<n-input-number v-model:value="daysHours.mon" :min="0" :max="24" :step="0.01" />
</n-form-item>
<n-form-item label="Tuesday" flex-1>
<n-input-number v-model:value="daysHours.tue" :min="0" :max="24" :step="0.01" />
</n-form-item>
<n-form-item label="Wednesday" flex-1>
<n-input-number v-model:value="daysHours.wed" :min="0" :max="24" :step="0.01" />
</n-form-item>
<n-form-item label="Thursday" flex-1>
<n-input-number v-model:value="daysHours.thu" :min="0" :max="24" :step="0.01" />
</n-form-item>
<n-form-item label="Friday" flex-1>
<n-input-number v-model:value="daysHours.fri" :min="0" :max="24" :step="0.01" />
</n-form-item>
<n-form-item label="Saturday" flex-1>
<n-input-number v-model:value="daysHours.sat" :min="0" :max="24" :step="0.01" />
</n-form-item>
<n-form-item label="Sunday" flex-1>
<n-input-number v-model:value="daysHours.sun" :min="0" :max="24" :step="0.01" />
</n-form-item>
</div>
</c-card>
</div>
</template>

0 comments on commit bc46f47

Please sign in to comment.