Skip to content

Commit

Permalink
perf analysis and thresholds refactoring (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
ludeknovy authored Mar 14, 2021
1 parent 5147d60 commit e294234
Show file tree
Hide file tree
Showing 12 changed files with 375 additions and 229 deletions.
4 changes: 4 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ import { ItemControlsComponent } from './scenario/item-controls/item-controls.co
import { ShareComponent } from './item-detail/share/share.component';
import { CreateNewShareLinkComponent } from './item-detail/share/create-new-share-link/create-new-share-link.component';
import { DeleteShareLinkComponent } from './item-detail/share/delete-share-link/delete-share-link.component';
import { ThresholdsAlertComponent } from './item-detail/thresholds-alert/thresholds-alert.component';
import { PerformanceAnalysisComponent } from './item-detail/performance-analysis/performance-analysis.component';

const appRoutes: Routes = [
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
Expand Down Expand Up @@ -138,6 +140,8 @@ const appRoutes: Routes = [
ShareComponent,
CreateNewShareLinkComponent,
DeleteShareLinkComponent,
ThresholdsAlertComponent,
PerformanceAnalysisComponent,
],
imports: [
RouterModule.forRoot(
Expand Down
107 changes: 3 additions & 104 deletions src/app/item-detail/item-detail.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,115 +57,14 @@

<div class="row" *ngIf="itemData.thresholds && !itemData.thresholds?.passed">
<div class="col">
<ng-template #tipContent>
<p>90 percentile decrease toleration: {{itemData.thresholds.thresholds.percentile}} %
</p>
<p>
Througput decrease toleration: {{itemData.thresholds.thresholds.throughput}}%
</p>
<p>
Error rate increase toleration: {{itemData.thresholds.thresholds.errorRate}}%
</p>
</ng-template>
<div class="card perf-issue">
<div class="card-body">
<h6 class="card-title text-alizarin">Performance regression issue detected! <i class="ttp text-secondary"
placement="bottom" [ngbTooltip]="tipContent"><i class="far fa-question-circle icon"></i></i></h6>
<div>
<div class="perf-issue-check" *ngIf="!itemData.thresholds.result.percentile.passed"> <i
class="fas fa-exclamation-triangle text-danger"></i> 90 percentile response time
<div class="perf-analaysis-desc text-secondary"><small>
90 percentile response time is about {{Math.round(itemData.thresholds.result.percentile.diffValue - 100) }}% slower than in the previous reports.
</small></div>
</div>
<div class="perf-issue-check" *ngIf="!itemData.thresholds.result.errorRate.passed"><i
class="fas fa-exclamation-triangle text-danger"></i> Error rate
<div class="perf-analaysis-desc text-secondary">
Error rate is about {{
Math.round(100 - itemData.thresholds.result.errorRate.diffValue) }}% higher than the average.
</div>
</div>
<div class="perf-issue-check" *ngIf="!itemData.thresholds.result.throughput.passed"><i
class="fas fa-exclamation-triangle text-danger"></i> Throughput
<div class="perf-analaysis-desc text-secondary"><small> Throughput is about {{
Math.round(100 - itemData.thresholds.result.throughput.diffValue ) }}% lower than in the previous reports.</small>
</div>
</div>
</div>

</div>
</div>
<app-regression-alert [itemData]="itemData"></app-regression-alert>
</div>
</div>


<div class="row" *ngIf="itemData.analysisEnabled">
<div class="col">
<div class="card"
[ngClass]="(perfAnalysis.onePerc.failed || perfAnalysis.variability.failed || perfAnalysis.throughputVariability.failed) ? 'performance-analysis-warning' : 'performance-analysis-success'">
<div class="card-body">
<h6 class="overview-body perf-analysis">Performance Analysis</h6>
<div>
<div><i
[ngClass]="(perfAnalysis.onePerc.failed===true) ? 'fas fa-exclamation-triangle text-warning' : 'far fa-check-circle text-success'"></i>
Slowest 1% of responses</div>
<div *ngIf="perfAnalysis.onePerc.failed===false" class="perf-analaysis-desc text-secondary">
<small>The 1% of the slowest responses do not have a significant deviation from the average response
time.</small>
</div>
<div *ngIf="perfAnalysis.onePerc.failed===true" class="perf-analaysis-desc text-secondary">
<small>The 1% of response times shows up to {{ perfAnalysis.onePerc.value }}x slower response times
than the
average. This might mean a performance issue for some clients and indicates that SUT was most likely
overloaded. </small> <button class="btn btn-sm btn-link-custom jtl-no-glow text-primary" (click)="toggleFoldBottom($event.target)">Show more</button>
<div class="response-time-variability" [@panelState]="foldedBottom">
<div><small>Labels with the highest difference from the average:</small></div>
<div *ngFor="let _ of perfAnalysis.onePerc.failingLabels">
<li><small><strong>{{_.label}}</strong> 1% of the responses were <strong>{{_.onePerc}}x</strong> slower then the average. The 1% of the response time were <strong>{{_.p99}}ms</strong> and slower, while the average was <strong>{{_.avgResponseTime}}ms</strong>.</small></li>
</div>
</div>
</div>
</div>

<div class="perf-analysis-check">
<div><i
[ngClass]="(perfAnalysis.variability.failed===true) ? 'fas fa-exclamation-triangle text-warning' : 'far fa-check-circle text-success'"></i>
Steady response time performance</div>
<div *ngIf="perfAnalysis.variability.failed===true" class="perf-analaysis-desc text-secondary">
<small>Increased variability between the fastest and the average response time was detected (up to
{{perfAnalysis.variability.value}}x). The SUT might have been overloaded. </small> <button class="btn btn-sm btn-link-custom jtl-no-glow text-primary" (click)="toggleFoldRT($event.target)">Show more</button>

<div class="response-time-variability" [@panelState]="folded">
<div><small>Labels with the highest variability:</small></div>
<div *ngFor="let _ of perfAnalysis.variability.failingLabels">
<li><small><strong>{{_.label}}</strong> shows <strong>{{_.variability}}x</strong> variability. The minimum reponse time measured was <strong>{{_.minResponseTime}}ms</strong> and the average <strong>{{_.avgResponseTime}}ms</strong>.</small></li>
</div>
</div>


</div>
<div *ngIf="perfAnalysis.variability.failed===false" class="perf-analaysis-desc text-secondary">
<small>The SUT was providing balanced response times across all labels.</small>
</div>
</div>

<div class="perf-analysis-check">
<div><i
[ngClass]="(perfAnalysis.throughputVariability.failed===true) ? 'fas fa-exclamation-triangle text-warning' : 'far fa-check-circle text-success'"></i>
Steady throughput performance</div>
<div *ngIf="perfAnalysis.throughputVariability.failed===true"
class="perf-analaysis-desc text-secondary">
<small>Significant drops in throughput performance were detected (up to
{{perfAnalysis.throughputVariability.value}}%). The SUT might have been overloaded. <button
class="btn btn-sm btn-link-custom jtl-no-glow text-primary"
(click)="toggleThroughputBand($event.target)">Display in chart</button></small>
</div>
<div *ngIf="perfAnalysis.throughputVariability.failed===false"
class="perf-analaysis-desc text-secondary">
<small>SUT was providing balanced throughput.</small>
</div>
</div>
</div>
</div>
<app-performance-analysis [itemData]="itemData" (overallChartChange)="toggleThroughputBand($event)"></app-performance-analysis>
</div>

</div>
Expand Down
9 changes: 0 additions & 9 deletions src/app/item-detail/item-detail.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,6 @@ thead .hd {
border-bottom: 0px;
}


.perf-issue .card-body h6 {
font-size: 1rem !important;
}

.perf-issue-check {
margin-top: 10px;
}

.perf-analysis-check {
margin-top: 10px;
}
Expand Down
123 changes: 7 additions & 116 deletions src/app/item-detail/item-detail.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ItemsApiService } from '../items-api.service';
import { ItemDetail, ItemStatistics } from '../items.service.model';
import { ItemDetail } from '../items.service.model';
import { NgxSpinnerService } from 'ngx-spinner';
import { DecimalPipe } from '@angular/common';
import * as Highcharts from 'highcharts';
Expand All @@ -20,19 +20,11 @@ import { SharedMainBarService } from '../shared-main-bar.service';
import { ToastrService } from 'ngx-toastr';
import { ItemStatusValue } from './item-detail.model';
import { logScaleButton } from '../graphs/log-scale-button';
import { animate, state, style, transition, trigger } from '@angular/animations';

@Component({
selector: 'app-item-detail',
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.scss', '../shared-styles.css'],
animations : [
trigger('panelState', [
state('closed', style({ height: 0, overflow: 'hidden' })),
state('open', style({ height: '*' })),
transition('closed <=> open', animate('300ms ease-in-out')),
]),
],
providers: [DecimalPipe]
})
export class ItemDetailComponent implements OnInit {
Expand Down Expand Up @@ -68,17 +60,7 @@ export class ItemDetailComponent implements OnInit {
comparisonWarning = [];
token: string;
isAnonymous = false;
perfAnalysis = {
variability: null, onePerc: null, throughputVariability: {
failed: null,
value: null,
bandValues: null,
}
};
toggleThroughputBandFlag = false;
folded = 'closed';
foldedBottom = 'closed';


constructor(
private route: ActivatedRoute,
Expand Down Expand Up @@ -121,7 +103,6 @@ export class ItemDetailComponent implements OnInit {
this.hasErrorsAttachment = this.itemData.attachements.find((_) => _ === 'error');
this.monitoringAlerts();
this.generateCharts();
this.performanceAnalaysis();
this.spinner.hide();
Highcharts.chart('container', this.throughputChartOptions);

Expand Down Expand Up @@ -269,82 +250,11 @@ export class ItemDetailComponent implements OnInit {
return Math.round(number * 100) / 100;
}

private performanceAnalaysis() {
if (!this.itemData.analysisEnabled) {
return;
}
const output = [];
this.itemData.statistics.forEach(_ => {
const variability = this.roundNumberTwoDecimals(_.avgResponseTime / _.minResponseTime);
const onePerc = this.roundNumberTwoDecimals(_.n9 / _.avgResponseTime);
output.push({
variability,
onePerc,
minResponseTime: _.minResponseTime,
avgResponseTime: _.avgResponseTime,
p99: _.n9,
label: _.label
});
});

const variabilitySorted = [...output].sort((a, b) => b.variability - a.variability);
const onePercSorted = [...output].sort((a, b) => b.onePerc - a.onePerc);

this.perfAnalysis = {
variability: {
value: variabilitySorted[0].variability,
avgResponseTime: variabilitySorted[0].avgResponseTime,
minResponseTime: variabilitySorted[0].minResponseTime,
failed: variabilitySorted[0].variability > 2.5,
failingLabels: variabilitySorted.filter(_ => _.variability > 2.5)
},
onePerc: {
value: onePercSorted[0].onePerc,
avgResponseTime: onePercSorted[0].onePerc.avgResponseTime,
failed: onePercSorted[0].onePerc > 2.5,
failingLabels: onePercSorted.filter(_ => _.onePerc > 2.5)
},
throughputVariability: this.calculateThroughputVariability()
};
}

private calculateThroughputVariability() {
try {
const { overallThroughput, threads } = this.itemData.plot;
const { maxVu, throughput } = this.itemData.overview;
const rampUpIndex = threads.map(_ => _[1]).indexOf(maxVu);

const throughputValues = overallThroughput.data.slice(rampUpIndex, -2).map(_ => _[1]);
const minThroughput = Math.min(...throughputValues);
const minThroughputIndex = throughputValues.indexOf(minThroughput);
const maxBandIndex = throughputValues.length;
const bandTo = minThroughputIndex + 5 <= maxBandIndex ? minThroughputIndex + 3 : maxBandIndex;
const throughputBandValues = [
overallThroughput.data.slice(rampUpIndex)[minThroughputIndex - 3][0],
overallThroughput.data.slice(rampUpIndex)[bandTo][0]
];
const throughputVariability = this.roundNumberTwoDecimals(100 - (minThroughput / throughput) * 100);

return {
value: throughputVariability,
failed: throughputVariability > 20,
bandValues: throughputBandValues
};
} catch (error) {
return {
value: null,
failed: false,
bandValues: []
};
}

}

bytesToMbps(bytes) {
return this.roundNumberTwoDecimals(bytes / Math.pow(1024, 2));
}

toggleThroughputBand(element) {
toggleThroughputBand({ element, perfAnalysis }) {
this.overallChartOptions.series.forEach(serie => {
if (['response time', 'errors'].includes(serie.name)) {
serie.visible = this.toggleThroughputBandFlag;
Expand All @@ -355,18 +265,18 @@ export class ItemDetailComponent implements OnInit {
return;
}
serie.zones = [{
value: this.itemData.overview.throughput,
color: '#e74c3c'
}];
value: this.itemData.overview.throughput,
color: '#e74c3c'
}];
}
});

if (!this.toggleThroughputBandFlag) {
element.textContent = 'Hide in chart';
this.overallChartOptions.xAxis.plotBands = {
color: '#e74c3c4f',
from: this.perfAnalysis.throughputVariability.bandValues[0],
to: this.perfAnalysis.throughputVariability.bandValues[1]
from: perfAnalysis.throughputVariability.bandValues[0],
to: perfAnalysis.throughputVariability.bandValues[1]
};
this.toggleThroughputBandFlag = true;
} else {
Expand All @@ -377,23 +287,4 @@ export class ItemDetailComponent implements OnInit {
this.updateChartFlag = true;
}

toggleFoldRT(element) {
if (this.folded === 'open') {
this.folded = 'closed';
element.textContent = 'Show more';
return;
}
this.folded = 'open';
element.textContent = 'Show less';
}

toggleFoldBottom(element) {
if (this.foldedBottom === 'open') {
this.foldedBottom = 'closed';
element.textContent = 'Show more';
return;
}
this.foldedBottom = 'open';
element.textContent = 'Show less';
}
}
Empty file.
Loading

0 comments on commit e294234

Please sign in to comment.