Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default {
const y = d3
.scaleLinear()
.range([this.height, 0])
.domain([0, d3.max(data, (d) => d.total) * 1.05]);
.domain([0, d3.max(data, (d) => d.totalPerInterval) * 1.05]);

//draw areas
const miss = this.areas
Expand All @@ -101,8 +101,8 @@ export default {
d3
.area()
.x((d) => x(d.timestamp))
.y0((d) => y(d.hit))
.y1((d) => y(d.total)),
.y0((d) => y(d.hitsPerInterval))
.y1((d) => y(d.totalPerInterval)),
);
miss.exit().remove();

Expand All @@ -118,7 +118,7 @@ export default {
.area()
.x((d) => x(d.timestamp))
.y0(y(0))
.y1((d) => y(d.hit)),
.y1((d) => y(d.hitsPerInterval)),
);
hit.exit().remove();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { screen, waitFor } from '@testing-library/vue';
import { enableAutoUnmount, shallowMount } from '@vue/test-utils';
import { HttpResponse, http } from 'msw';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { applications } from '@/mocks/applications/data';
import { server } from '@/mocks/server';
import Application from '@/services/application';
import { render } from '@/test-utils';
import DetailsCache from '@/views/instances/details/details-cache.vue';

vi.mock('@/sba-config', async () => {
const sbaConfig: any = await vi.importActual('@/sba-config');
return {
default: {
...sbaConfig.default,
uiSettings: {
pollTimer: {
cache: 100,
},
},
},
};
});

describe('DetailsCache', () => {
enableAutoUnmount(afterEach);

const CACHE_NAME = 'foo-cache';
const HITS = [24.0, 32.0, 48.0];
const MISSES = [8.0, 8.0, 16.0];
const TOTAL = [32, 40, 64];
const HITS_PER_INTERVAL = [0, 8, 16];
const MISSES_PER_INTERVAL = [0, 0, 8];
const TOTAL_PER_INTERVAL = [0, 8, 24];

beforeEach(() => {
const hitsGenerator = (function* () {
yield* HITS;
})();
const missesGenerator = (function* () {
yield* MISSES;
})();

server.use(
http.get(
'/instances/:instanceId/actuator/metrics/cache.gets',
({ request }) => {
const url = new URL(request.url);
const tags = url.searchParams.getAll('tag');

if (tags.includes('result:hit')) {
return HttpResponse.json({
measurements: [{ value: hitsGenerator.next()?.value }],
});
} else if (tags.includes('result:miss')) {
return HttpResponse.json({
measurements: [{ value: missesGenerator.next()?.value }],
});
}
},
),
http.get('/instances/:instanceId/actuator/metrics/cache.size', () => {
return HttpResponse.json({
measurements: [{ value: '1337' }],
});
}),
);
});

async function renderComponent(stubs = {}) {
const application = new Application(applications[0]);
const instance = application.instances[0];
return render(DetailsCache, {
global: {
stubs,
},
props: {
instance,
cacheName: CACHE_NAME,
index: 0,
},
});
}

it('should render cache name', async () => {
await renderComponent();

expect(
await screen.findByRole('heading', { name: `Cache: ${CACHE_NAME}` }),
).toBeVisible();
});

it('should render hits and misses', async () => {
await renderComponent();

expect(
await screen.findByLabelText('instances.details.cache.hits'),
).toHaveTextContent(`${HITS[0]}`);
expect(
await screen.findByLabelText('instances.details.cache.misses'),
).toHaveTextContent(`${MISSES[0]}`);
});

it('should calculate and render hit/miss ratio', async () => {
await renderComponent();

expect(
await screen.findByLabelText('instances.details.cache.hit_ratio'),
).toHaveTextContent('75.00%'); // 24 hits, 8 misses
});

it('should calculate total', async () => {
const application = new Application(applications[0]);
const instance = application.instances[0];

const vueWrapper = shallowMount(DetailsCache, {
props: {
instance,
cacheName: CACHE_NAME,
index: 0,
},
});

await waitFor(() => {
expect(vueWrapper.vm.chartData).toHaveLength(3);
});

for (let index = 0; index < vueWrapper.vm.chartData.length; index++) {
expect(vueWrapper.vm.chartData[index].total).toEqual(TOTAL[index]);
}
});

it('should calculate hits, misses and total per interval', async () => {
const application = new Application(applications[0]);
const instance = application.instances[0];

const vueWrapper = shallowMount(DetailsCache, {
props: {
instance,
cacheName: CACHE_NAME,
index: 0,
},
});

await waitFor(() => {
expect(vueWrapper.vm.chartData).toHaveLength(3);
});

for (let index = 0; index < vueWrapper.vm.chartData.length; index++) {
expect(vueWrapper.vm.chartData[index].hitsPerInterval).toEqual(
HITS_PER_INTERVAL[index],
);
expect(vueWrapper.vm.chartData[index].missesPerInterval).toEqual(
MISSES_PER_INTERVAL[index],
);
expect(vueWrapper.vm.chartData[index].totalPerInterval).toEqual(
TOTAL_PER_INTERVAL[index],
);
}
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,50 @@
>
<template v-if="current.hit !== undefined">
<dt
:id="`metrics.cache.${index}.hits`"
class="text-sm font-medium text-gray-500 sm:col-span-4"
v-text="$t('instances.details.cache.hits')"
/>
<dd
:aria-labelledby="`metrics.cache.${index}.hits`"
class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"
v-text="current.hit"
/>
</template>
<template v-if="current.miss !== undefined">
<dt
:id="`metrics.cache.${index}.misses`"
class="text-sm font-medium text-gray-500 sm:col-span-4"
v-text="$t('instances.details.cache.misses')"
/>
<dd
:aria-labelledby="`metrics.cache.${index}.misses`"
class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"
v-text="current.miss"
/>
</template>
<template v-if="ratio !== undefined">
<dt
class="sm:col-span-4"
:id="`metrics.cache.${index}.ratio`"
class="text-sm font-medium text-gray-500 sm:col-span-4"
v-text="$t('instances.details.cache.hit_ratio')"
/>
<dd v-text="ratio" />
<dd
:aria-labelledby="`metrics.cache.${index}.ratio`"
class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2"
v-text="ratio"
/>
</template>
<template v-if="current.size !== undefined">
<dt class="sm:col-span-4" v-text="$t('instances.details.cache.size')" />
<dd v-text="current.size" />
<dt
:id="`metrics.cache.${index}.size`"
class="sm:col-span-4"
v-text="$t('instances.details.cache.size')"
/>
<dd
:aria-labelledby="`metrics.cache.${index}.size`"
v-text="current.size"
/>
</template>
</dl>
<cache-chart v-if="chartData.length > 0" :data="chartData" />
Expand All @@ -61,14 +77,17 @@
import moment from 'moment';
import { take } from 'rxjs/operators';

import sbaAlert from '@/components/sba-alert.vue';
import sbaPanel from '@/components/sba-panel.vue';

import subscribing from '@/mixins/subscribing';
import sbaConfig from '@/sba-config';
import Instance from '@/services/instance';
import { concatMap, delay, retryWhen, timer } from '@/utils/rxjs';
import { concatMap, delay, map, retryWhen, timer } from '@/utils/rxjs';
import cacheChart from '@/views/instances/details/cache-chart';

export default {
components: { cacheChart },
components: { sbaAlert, sbaPanel, cacheChart },
mixins: [subscribing],
props: {
instance: {
Expand All @@ -79,6 +98,10 @@ export default {
type: String,
required: true,
},
index: {
type: Number,
required: true,
},
},
data: () => ({
hasLoaded: false,
Expand All @@ -91,7 +114,10 @@ export default {
}),
computed: {
ratio() {
if (Number.isFinite(this.current.hit) && Number.isFinite(this.current)) {
if (
Number.isFinite(this.current.hit) &&
Number.isFinite(this.current.miss)
) {
const total = this.current.hit + this.current.miss;
return total > 0
? ((this.current.hit / total) * 100).toFixed(2) + '%'
Expand Down Expand Up @@ -167,10 +193,25 @@ export default {
}
}
},
calculateMetricsPerInterval(data) {
let hitsPerInterval = 0;
let missesPerInterval = 0;
let totalPerInterval = 0;

if (this.chartData.length > 0) {
const previousChartData = this.chartData[this.chartData.length - 1];
hitsPerInterval = data.hit - previousChartData.hit;
missesPerInterval = data.miss - previousChartData.miss;
totalPerInterval = data.total - previousChartData.total;
}

return { ...data, hitsPerInterval, missesPerInterval, totalPerInterval };
},
createSubscription() {
return timer(0, sbaConfig.uiSettings.pollTimer.cache)
.pipe(
concatMap(this.fetchMetrics),
map(this.calculateMetricsPerInterval),
retryWhen((err) => {
return err.pipe(delay(1000), take(5));
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
<template>
<div>
<details-cache
v-for="cache in caches"
v-for="(cache, index) in caches"
:key="cache"
:index="index"
:cache-name="cache"
:instance="instance"
/>
Expand Down
Loading