From e2942342ba701adcfb6e99facc2ecd0ff6b2715b Mon Sep 17 00:00:00 2001 From: Ludek <13610612+ludeknovy@users.noreply.github.com> Date: Sun, 14 Mar 2021 12:20:37 +0100 Subject: [PATCH] perf analysis and thresholds refactoring (#115) --- src/app/app.module.ts | 4 + .../item-detail/item-detail.component.html | 107 +----------- .../item-detail/item-detail.component.scss | 9 - src/app/item-detail/item-detail.component.ts | 123 +------------- .../performance-analysis.component.css | 0 .../performance-analysis.component.html | 67 ++++++++ .../performance-analysis.component.spec.ts | 28 ++++ .../performance-analysis.component.ts | 154 ++++++++++++++++++ .../thresholds-alert.component.css | 7 + .../thresholds-alert.component.html | 40 +++++ .../thresholds-alert.component.spec.ts | 45 +++++ .../thresholds-alert.component.ts | 20 +++ 12 files changed, 375 insertions(+), 229 deletions(-) create mode 100644 src/app/item-detail/performance-analysis/performance-analysis.component.css create mode 100644 src/app/item-detail/performance-analysis/performance-analysis.component.html create mode 100644 src/app/item-detail/performance-analysis/performance-analysis.component.spec.ts create mode 100644 src/app/item-detail/performance-analysis/performance-analysis.component.ts create mode 100644 src/app/item-detail/thresholds-alert/thresholds-alert.component.css create mode 100644 src/app/item-detail/thresholds-alert/thresholds-alert.component.html create mode 100644 src/app/item-detail/thresholds-alert/thresholds-alert.component.spec.ts create mode 100644 src/app/item-detail/thresholds-alert/thresholds-alert.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 60348a67..3d7c80f8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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] }, @@ -138,6 +140,8 @@ const appRoutes: Routes = [ ShareComponent, CreateNewShareLinkComponent, DeleteShareLinkComponent, + ThresholdsAlertComponent, + PerformanceAnalysisComponent, ], imports: [ RouterModule.forRoot( diff --git a/src/app/item-detail/item-detail.component.html b/src/app/item-detail/item-detail.component.html index 9826593e..27e0a8db 100644 --- a/src/app/item-detail/item-detail.component.html +++ b/src/app/item-detail/item-detail.component.html @@ -57,115 +57,14 @@
- -

90 percentile decrease toleration: {{itemData.thresholds.thresholds.percentile}} % -

-

- Througput decrease toleration: {{itemData.thresholds.thresholds.throughput}}% -

-

- Error rate increase toleration: {{itemData.thresholds.thresholds.errorRate}}% -

-
-
-
-
Performance regression issue detected!
-
-
90 percentile response time -
- 90 percentile response time is about {{Math.round(itemData.thresholds.result.percentile.diffValue - 100) }}% slower than in the previous reports. -
-
-
Error rate -
- Error rate is about {{ - Math.round(100 - itemData.thresholds.result.errorRate.diffValue) }}% higher than the average. -
-
-
Throughput -
Throughput is about {{ - Math.round(100 - itemData.thresholds.result.throughput.diffValue ) }}% lower than in the previous reports. -
-
-
- -
-
+
+
-
-
-
Performance Analysis
-
-
- Slowest 1% of responses
-
- The 1% of the slowest responses do not have a significant deviation from the average response - time. -
-
- 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. -
-
Labels with the highest difference from the average:
-
-
  • {{_.label}} 1% of the responses were {{_.onePerc}}x slower then the average. The 1% of the response time were {{_.p99}}ms and slower, while the average was {{_.avgResponseTime}}ms.
  • -
    -
    -
    -
    - -
    -
    - Steady response time performance
    -
    - Increased variability between the fastest and the average response time was detected (up to - {{perfAnalysis.variability.value}}x). The SUT might have been overloaded. - -
    -
    Labels with the highest variability:
    -
    -
  • {{_.label}} shows {{_.variability}}x variability. The minimum reponse time measured was {{_.minResponseTime}}ms and the average {{_.avgResponseTime}}ms.
  • -
    -
    - - -
    -
    - The SUT was providing balanced response times across all labels. -
    -
    - -
    -
    - Steady throughput performance
    -
    - Significant drops in throughput performance were detected (up to - {{perfAnalysis.throughputVariability.value}}%). The SUT might have been overloaded. -
    -
    - SUT was providing balanced throughput. -
    -
    -
    -
    +
    diff --git a/src/app/item-detail/item-detail.component.scss b/src/app/item-detail/item-detail.component.scss index cc4fa01d..f0666ad6 100644 --- a/src/app/item-detail/item-detail.component.scss +++ b/src/app/item-detail/item-detail.component.scss @@ -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; } diff --git a/src/app/item-detail/item-detail.component.ts b/src/app/item-detail/item-detail.component.ts index 669278aa..0a74ad29 100644 --- a/src/app/item-detail/item-detail.component.ts +++ b/src/app/item-detail/item-detail.component.ts @@ -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'; @@ -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 { @@ -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, @@ -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); @@ -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; @@ -355,9 +265,9 @@ export class ItemDetailComponent implements OnInit { return; } serie.zones = [{ - value: this.itemData.overview.throughput, - color: '#e74c3c' - }]; + value: this.itemData.overview.throughput, + color: '#e74c3c' + }]; } }); @@ -365,8 +275,8 @@ export class ItemDetailComponent implements OnInit { 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 { @@ -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'; - } } diff --git a/src/app/item-detail/performance-analysis/performance-analysis.component.css b/src/app/item-detail/performance-analysis/performance-analysis.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/item-detail/performance-analysis/performance-analysis.component.html b/src/app/item-detail/performance-analysis/performance-analysis.component.html new file mode 100644 index 00000000..94875e57 --- /dev/null +++ b/src/app/item-detail/performance-analysis/performance-analysis.component.html @@ -0,0 +1,67 @@ + +
    +
    +
    Performance Analysis
    +
    +
    + Slowest 1% of responses
    +
    + The 1% of the slowest responses do not have a significant deviation from the average response + time. +
    +
    + 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. +
    +
    Labels with the highest difference from the average:
    +
    +
  • {{_.label}} 1% of the responses were {{_.onePerc}}x slower then the average. The 1% of the response time were {{_.p99}}ms and slower, while the average was {{_.avgResponseTime}}ms.
  • +
    +
    +
    +
    + +
    +
    + Steady response time performance
    +
    + Increased variability between the fastest and the average response time was detected (up to + {{perfAnalysis.variability.value}}x). The SUT might have been overloaded. + +
    +
    Labels with the highest variability:
    +
    +
  • {{_.label}} shows {{_.variability}}x variability. The minimum reponse time measured was {{_.minResponseTime}}ms and the average {{_.avgResponseTime}}ms.
  • +
    +
    + + +
    +
    + The SUT was providing balanced response times across all labels. +
    +
    + +
    +
    + Steady throughput performance
    +
    + Significant drops in throughput performance were detected (up to + {{perfAnalysis.throughputVariability.value}}%). The SUT might have been overloaded. +
    +
    + SUT was providing balanced throughput. +
    +
    +
    +
    diff --git a/src/app/item-detail/performance-analysis/performance-analysis.component.spec.ts b/src/app/item-detail/performance-analysis/performance-analysis.component.spec.ts new file mode 100644 index 00000000..f90045b6 --- /dev/null +++ b/src/app/item-detail/performance-analysis/performance-analysis.component.spec.ts @@ -0,0 +1,28 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { PerformanceAnalysisComponent } from './performance-analysis.component'; + +describe('PerformanceAnalysisComponent', () => { + let component: PerformanceAnalysisComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PerformanceAnalysisComponent], + imports: [NgbModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PerformanceAnalysisComponent); + component = fixture.componentInstance; + component.itemData = { analaysisEnabled: true }; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/item-detail/performance-analysis/performance-analysis.component.ts b/src/app/item-detail/performance-analysis/performance-analysis.component.ts new file mode 100644 index 00000000..b7f6ac9b --- /dev/null +++ b/src/app/item-detail/performance-analysis/performance-analysis.component.ts @@ -0,0 +1,154 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { animate, state, style, transition, trigger } from '@angular/animations'; + +@Component({ + selector: 'app-performance-analysis', + templateUrl: './performance-analysis.component.html', + styleUrls: ['./performance-analysis.component.css', '../item-detail.component.scss'], + animations: [ + trigger('panelState', [ + state('closed', style({ height: 0, overflow: 'hidden' })), + state('open', style({ height: '*' })), + transition('closed <=> open', animate('300ms ease-in-out')), + ]), + ], +}) +export class PerformanceAnalysisComponent implements OnInit { + + @Input() itemData; + @Output() overallChartChange = new EventEmitter<{}>(); + + + perfAnalysis = { + variability: { + value: null, + avgResponseTime: null, + minResponseTime: null, + failed: null, + failingLabels: null, + }, onePerc: { + value: null, + failed: null, + avgResponseTime: null, + failingLabels: null, + }, throughputVariability: { + failed: null, + value: null, + bandValues: null, + } + }; + folded = 'closed'; + foldedBottom = 'closed'; + Math: any; + + constructor() { + this.Math = Math; + + } + + ngOnInit() { + this.performanceAnalaysis(); + } + + 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: [] + }; + } + + } + + private roundNumberTwoDecimals = number => { + return Math.round(number * 100) / 100; + } + + 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'; + } + + toggleThroughputBand(element) { + this.overallChartChange.emit({ element, perfAnalysis: this.perfAnalysis }); + + } + + +} diff --git a/src/app/item-detail/thresholds-alert/thresholds-alert.component.css b/src/app/item-detail/thresholds-alert/thresholds-alert.component.css new file mode 100644 index 00000000..f30089a2 --- /dev/null +++ b/src/app/item-detail/thresholds-alert/thresholds-alert.component.css @@ -0,0 +1,7 @@ +.perf-issue .card-body h6 { + font-size: 1rem !important; +} + +.perf-issue-check { + margin-top: 10px; +} diff --git a/src/app/item-detail/thresholds-alert/thresholds-alert.component.html b/src/app/item-detail/thresholds-alert/thresholds-alert.component.html new file mode 100644 index 00000000..ba88ba6c --- /dev/null +++ b/src/app/item-detail/thresholds-alert/thresholds-alert.component.html @@ -0,0 +1,40 @@ + +

    90 percentile decrease toleration: {{itemData.thresholds.thresholds.percentile}} % +

    +

    + Througput decrease toleration: {{itemData.thresholds.thresholds.throughput}}% +

    +

    + Error rate increase toleration: {{itemData.thresholds.thresholds.errorRate}}% +

    +
    +
    +
    +
    Performance threshold failure
    +
    +
    90 percentile response time +
    + 90 percentile response time is about {{Math.round(itemData.thresholds.result.percentile.diffValue - 100) }}% + slower than in the previous reports. +
    +
    +
    Error rate +
    + Error rate is about {{ + Math.round(100 - itemData.thresholds.result.errorRate.diffValue) }}% higher than the average. +
    +
    +
    Throughput +
    Throughput is about {{ + Math.round(100 - itemData.thresholds.result.throughput.diffValue ) }}% lower than in the previous + reports. +
    +
    +
    + +
    +
    diff --git a/src/app/item-detail/thresholds-alert/thresholds-alert.component.spec.ts b/src/app/item-detail/thresholds-alert/thresholds-alert.component.spec.ts new file mode 100644 index 00000000..e23056cb --- /dev/null +++ b/src/app/item-detail/thresholds-alert/thresholds-alert.component.spec.ts @@ -0,0 +1,45 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { ThresholdsAlertComponent } from './thresholds-alert.component'; + +describe('ThresholdAlertComponent', () => { + let component: ThresholdsAlertComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ThresholdsAlertComponent], + imports: [NgbModule] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ThresholdsAlertComponent); + component = fixture.componentInstance; + component.itemData = { + thresholds: { + thresholds: { + percentile: 10 + }, + result: { + percentile: { + passed: true + }, + throughput: { + + }, + errorRate: { + + } + } + } + }; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/item-detail/thresholds-alert/thresholds-alert.component.ts b/src/app/item-detail/thresholds-alert/thresholds-alert.component.ts new file mode 100644 index 00000000..ee34be63 --- /dev/null +++ b/src/app/item-detail/thresholds-alert/thresholds-alert.component.ts @@ -0,0 +1,20 @@ +import { Component, Input, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-regression-alert', + templateUrl: './thresholds-alert.component.html', + styleUrls: ['./thresholds-alert.component.css', '../item-detail.component.scss'] +}) +export class ThresholdsAlertComponent implements OnInit { + + @Input() itemData; + Math: any; + + constructor() { + this.Math = Math; + } + + ngOnInit() { + } + +}