Skip to content

Commit

Permalink
UI: Add load test result summary
Browse files Browse the repository at this point in the history
  • Loading branch information
andylibrian committed Dec 19, 2020
1 parent a448343 commit d3c3b04
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 3 deletions.
2 changes: 1 addition & 1 deletion pkg/server/worker_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

type worker struct {
Name string `json:"name`
Name string `json:"name"`
conn *websocket.Conn
Metrics messages.WorkerLoadTestMetrics `json:"metrics"`
state messages.WorkerState
Expand Down
82 changes: 80 additions & 2 deletions web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
<link href="https://fonts.googleapis.com/css2?family=Lato&family=Open+Sans&family=Roboto&display=swap" rel="stylesheet">

<Navbar :serverInfo="serverInfo" />
<div id="section-launch-test" class="section">
<div class="section">
<LaunchTest :serverInfo="serverInfo" :serverBaseUrl="serverBaseUrl" />
</div>
<div class="section" :class="{'is-hidden': !isResultVisible}">
<ResultSummary :summary="metricsSummary"/>
</div>

</template>

<script>
import CreateWebsocket from './lib/websocket.js'
import Navbar from './components/navbar.vue'
import LaunchTest from './components/launch_test.vue'
import ResultSummary from './components/result_summary.vue'
let serverBaseUrl = process.env.VUE_APP_SERVER_BASE_URL;
if (!serverBaseUrl) {
Expand All @@ -27,14 +31,17 @@
components: {
Navbar,
LaunchTest,
ResultSummary,
},
data: function() {
return {
serverInfo: {
num_of_workers: 0,
state: "",
},
workers: {},
serverBaseUrl: serverBaseUrl,
isResultVisible: false,
}
},
created: function() {
Expand All @@ -58,7 +65,7 @@
const msg = JSON.parse(evt.data);
console.log(msg);
if (!('kind' in msg)) {
if (!("kind" in msg)) {
return;
}
Expand All @@ -69,6 +76,20 @@
if (msg.kind === "ServerInfo") {
Object.assign(_this.serverInfo, obj);
if ('state' in obj) {
if (!_this.isResultVisible && _this.serverInfo.state.toLowerCase() != 'notstarted') {
_this.isResultVisible = true;
}
}
} else if (msg.kind === "WorkersInfo") {
for (let key in obj) {
const worker = obj[key];
if ("name" in worker && "metrics" in worker) {
_this.workers[worker.name] = worker;
console.log("workers updated", _this.workers);
}
}
}
} catch(e) {
console.error("Can not parse incoming notification message as JSON", evt.data, e);
Expand All @@ -79,6 +100,63 @@
console.log("error", evt);
}
},
computed: {
metricsSummary() {
let requests = 0;
let rate = 0;
let throughput = 0;
let success = 0;
let workerCount = 0;
let sumMeanLatencies = 0;
let sumP50Latencies = 0;
let sumP95Latencies = 0;
let sumP99Latencies = 0;
let meanLatencies = 0;
let meanP50Latencies = 0;
let meanP95Latencies = 0;
let meanP99Latencies = 0;
let totalBytesIn = 0;
let totalBytesOut = 0;
let statusCodes = {'2xx': 0, '4xx': 0, '5xx': 0};
for (let w in this.workers) {
requests += this.workers[w].metrics.requests;
rate += this.workers[w].metrics.rate;
throughput += this.workers[w].metrics.throughput;
success += this.workers[w].metrics.success;
sumMeanLatencies += this.workers[w].metrics.latencies.mean;
sumP50Latencies += this.workers[w].metrics.latencies["50th"];
sumP95Latencies += this.workers[w].metrics.latencies["95th"];
sumP99Latencies += this.workers[w].metrics.latencies["99th"];
totalBytesIn += this.workers[w].metrics.bytes_in.total;
totalBytesOut += this.workers[w].metrics.bytes_out.total;
for (let s in this.workers[w].metrics.status_codes) {
if (s >= 200 && s <= 299) {
statusCodes['2xx'] += this.workers[w].metrics.status_codes[s]
} else if (s >= 400 && s <= 499) {
statusCodes['4xx'] += this.workers[w].metrics.status_codes[s]
} else if (s >= 500 && s <= 599) {
statusCodes['5xx'] += this.workers[w].metrics.status_codes[s]
}
}
workerCount++;
}
if (workerCount > 0) {
success = success / workerCount;
meanLatencies = sumMeanLatencies / workerCount;
meanP50Latencies = sumP50Latencies / workerCount;
meanP95Latencies = sumP95Latencies / workerCount;
meanP99Latencies = sumP99Latencies / workerCount;
}
return {
requests, rate, throughput, success, meanLatencies, meanP50Latencies, meanP95Latencies, meanP99Latencies, totalBytesIn, totalBytesOut, statusCodes
};
},
}
}
</script>

Expand Down
123 changes: 123 additions & 0 deletions web/src/components/result_summary.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<template>
<div class="tile">
<div class="tile is-parent">
<div class="card tile is-child">
<div class="card-content">
<div class="columns">
<div class="column">
<span class="title">{{ Math.floor(summary.throughput) }}</span>
<p class="content">response / seconds</p>
</div>
<div class="column has-text-right is-one-fifth m-1">
<span class="icon is-large"><i class="fas fa-space-shuttle fa-3x"></i></span>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-footer-item has-text-left">
<div class="content">
Total requests: <strong>{{ summary.requests }}</strong>
</div>
</div>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="card tile is-child">
<div class="card-content">
<div class="columns">
<div class="column">
<span class="title">{{ (summary.meanLatencies / 1000000).toFixed(1) }}ms</span>
<p class="content">avg response time</p>
</div>
<div class="column has-text-right is-one-fifth m-1">
<span class="icon is-large"><i class="fas fa-hourglass-end fa-3x"></i></span>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-footer-item has-text-left">
P50:&nbsp;<strong>{{ (summary.meanP50Latencies / 1000000).toFixed(1) }}ms</strong>
</div>
<div class="card-footer-item has-text-left">
P95:&nbsp;<strong>{{ (summary.meanP95Latencies / 1000000).toFixed(1) }}ms</strong>
</div>
<div class="card-footer-item has-text-left">
P99:&nbsp;<strong>{{ (summary.meanP99Latencies / 1000000).toFixed(1) }}ms</strong>
</div>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="card tile is-child">
<div class="card-content">
<div class="columns">
<div class="column">
<span class="title">{{ Math.floor(summary.success * 100) }}%</span>
<p class="content">success rate</p>
</div>
<div class="column has-text-right is-one-fifth m-1">
<span class="icon is-large"><i class="fas fa-chart-pie fa-3x"></i></span>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-footer-item has-text-left">
2xx:&nbsp;<strong>{{ summary.statusCodes['2xx'] }}</strong>
</div>
<div class="card-footer-item has-text-left">
4xx:&nbsp;<strong>{{ summary.statusCodes['4xx'] }}</strong>
</div>
<div class="card-footer-item has-text-left">
5xx:&nbsp;<strong>{{ summary.statusCodes['5xx'] }}</strong>
</div>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="card tile is-child">
<div class="card-content">
<div class="columns">
<div class="column">
<span class="title">{{ formatBytes(summary.totalBytesIn, 2) }}</span>
<p class="content">bytes in</p>
</div>
<div class="column has-text-right is-one-fifth m-1">
<span class="icon is-large"><i class="fas fa-exchange-alt fa-3x"></i></span>
</div>
</div>
</div>
<div class="card-footer">
<div class="card-footer-item has-text-left">
Bytes out:&nbsp;<strong>{{ formatBytes(summary.totalBytesOut, 2) }}</strong>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
export default {
name: 'ResultSummary',
props: {
summary: Object,
},
methods: {
formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Byte';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
}
}
</script>

<style scoped>
</style>

0 comments on commit d3c3b04

Please sign in to comment.