diff --git a/src/app/app.module.ts b/src/app/app.module.ts index aa54e93f..e0a74aa9 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -65,6 +65,7 @@ import { LabelChartComponent } from './item-detail/label-chart/label-chart.compo import { AnalyzeChartsComponent } from './item-detail/analyze-charts/analyze-charts.component'; import { AddMetricComponent } from './item-detail/analyze-charts/add-metric/add-metric.component'; import { ScenarioTrendsComponent } from './scenario/scenario-trends/scenario-trends.component'; +import { RequestStatsCompareComponent } from './item-detail/request-stats-compare/request-stats-compare.component'; const appRoutes: Routes = [ { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }, @@ -150,6 +151,7 @@ const appRoutes: Routes = [ AnalyzeChartsComponent, AddMetricComponent, ScenarioTrendsComponent, + RequestStatsCompareComponent, ], imports: [ RouterModule.forRoot( diff --git a/src/app/item-detail/item-detail.component.html b/src/app/item-detail/item-detail.component.html index 5d9b565f..038da7ff 100644 --- a/src/app/item-detail/item-detail.component.html +++ b/src/app/item-detail/item-detail.component.html @@ -160,11 +160,12 @@

+
-

{{convertBytesToMbps(itemData.overview.bytesPerSecond + itemData.overview.bytesSentPerSecond)}} Mbps +

{{convertBytesToMbps(itemData.overview.bytesPerSecond + + itemData.overview.bytesSentPerSecond)}} Mbps

@@ -346,126 +347,9 @@
SUT Statistics
- -
+
-
- -
Request Statistics - - Comparing to test: {{comparedMetadata.id}} with - {{comparedMetadata.maxVu}} VU - - -
-
- - -
-
- -
-
-
-
- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- label - - samples - - avg [ms] - - min [ms] - - max [ms] - - P90 [ms] - - P95 [ms] - - P99 [ms] - - requests/s - - mbps - - error rate - - -
{{_.label}}{{_.samples}}{{_.avgResponseTime}} {{_.avgDiff}}{{_.minResponseTime}} {{_.minDiff}}{{_.maxResponseTime}} {{_.maxDiff}}{{_.n0}} {{_.n0Diff}}{{_.n5}} {{_.n5Diff}}{{_.n9}} {{_.n9Diff}}{{_.throughput || "n/a"}} {{_.throughputDiff}}{{convertBytesToMbps(_.bytesPerSecond + _.bytesSentPerSecond) || "n/a"}} {{_.bytesDiff}}{{_.errorRate}} % {{_.errorRateDiff}} - - - - - -
-
-
-
+
diff --git a/src/app/item-detail/item-detail.component.spec.ts b/src/app/item-detail/item-detail.component.spec.ts index 7ab95dd5..82555a1c 100644 --- a/src/app/item-detail/item-detail.component.spec.ts +++ b/src/app/item-detail/item-detail.component.spec.ts @@ -31,7 +31,6 @@ describe('ItemDetailComponent', () => { DeleteItemComponent, ControlPanelComponent, StatsCompareComponent, - LabelTrendComponent, ], imports: [ DataTableModule, diff --git a/src/app/item-detail/item-detail.component.ts b/src/app/item-detail/item-detail.component.ts index f45eb82e..ff016205 100644 --- a/src/app/item-detail/item-detail.component.ts +++ b/src/app/item-detail/item-detail.component.ts @@ -20,9 +20,9 @@ import { catchError, withLatestFrom } from 'rxjs/operators'; import { of } from 'rxjs'; import { SharedMainBarService } from '../shared-main-bar.service'; import { ToastrService } from 'ngx-toastr'; -import { ItemStatusValue } from './item-detail.model'; import { bytesToMbps, roundNumberTwoDecimals } from './calculations'; import { logScaleButton } from '../graphs/log-scale-button'; +import { ItemStatusValue } from './item-detail.model'; @Component({ selector: 'app-item-detail', @@ -51,11 +51,7 @@ export class ItemDetailComponent implements OnInit { monitoringChart; itemParams; hasErrorsAttachment; - comparedData; - comparedMetadata; - labelsData; Math: any; - comparisonWarning = []; token: string; isAnonymous = false; toggleThroughputBandFlag = false; @@ -102,7 +98,6 @@ export class ItemDetailComponent implements OnInit { })) .subscribe((results) => { this.itemData = results; - this.labelsData = this.itemData.statistics; this.hasErrorsAttachment = this.itemData.attachements.find((_) => _ === 'error'); this.monitoringAlerts(); this.generateCharts(); @@ -193,64 +188,6 @@ export class ItemDetailComponent implements OnInit { this.itemData.hostname = hostname; } - itemToCompare(data) { - this.comparedMetadata = { id: data.id, maxVu: data.maxVu }; - if (data.maxVu !== this.itemData.overview.maxVu) { - this.comparisonWarning.push(`VU do differ ${this.itemData.overview.maxVu} vs. ${data.maxVu}`); - } - - this.comparedData = this.labelsData.map((_) => { - const labelToBeCompared = data.statistics.find((__) => __.label === _.label); - if (labelToBeCompared) { - return { - ..._, - avgDiff: (_.avgResponseTime - labelToBeCompared.avgResponseTime), - minDiff: (_.minResponseTime - labelToBeCompared.minResponseTime), - maxDiff: (_.maxResponseTime - labelToBeCompared.maxResponseTime), - // @ts-ignore - bytesDiff: ((_.bytes - labelToBeCompared.bytes) / 1024).toFixed(2), - n0Diff: (_.n0 - labelToBeCompared.n0), - n5Diff: (_.n5 - labelToBeCompared.n5), - n9Diff: (_.n9 - labelToBeCompared.n9), - errorRateDiff: roundNumberTwoDecimals((_.errorRate - labelToBeCompared.errorRate)), - throughputDiff: roundNumberTwoDecimals((_.throughput - labelToBeCompared.throughput)) - }; - } else { - this.comparisonWarning.push(`${_.label} label not found`); - return { - ..._, - avgDiff: null, - minDiff: null, - maxDiff: null, - n0Diff: null, - n5Diff: null, - n9Diff: null, - errorRateDiff: null, - throughputDiff: null - }; - } - }); - if (data.environment !== this.itemData.environment) { - this.comparisonWarning.push('Environments do differ'); - } - this.labelsData = this.comparedData; - - if (this.comparisonWarning.length) { - this.showComparisonWarnings(); - } - } - - showComparisonWarnings() { - this.toastr.warning(this.comparisonWarning.join('
'), 'Comparison Warning!', - { - closeButton: true, - enableHtml: true, - timeOut: 15000, - positionClass: 'toast-bottom-right' - }); - this.comparisonWarning = []; - } - monitoringAlerts() { const alertMessages = []; const { maxCpu, maxMem } = this.itemData.monitoringData; @@ -269,23 +206,6 @@ export class ItemDetailComponent implements OnInit { enableHtml: true, }); } - - } - - resetStatsData() { - this.comparedData = null; - this.labelsData = this.itemData.statistics; - } - - search(term: string) { - const dataToFilter = this.comparedData || this.itemData.statistics; - if (term) { - this.labelsData = dataToFilter.filter(x => - x.label.trim().toLowerCase().includes(term.trim().toLowerCase()) - ); - } else { - this.labelsData = dataToFilter; - } } getTextStatus(status) { @@ -296,18 +216,6 @@ export class ItemDetailComponent implements OnInit { } } - quickBaseComparison(id) { - this.itemsService.fetchItemDetail( - this.itemParams.projectName, - this.itemParams.scenarioName, - id).subscribe(_ => this.itemToCompare({ - statistics: _.statistics, - maxVu: _.overview.maxVu, - environment: _.environment, - id - })); - } - toggleThroughputBand({ element, perfAnalysis }) { this.overallChartOptions.series.forEach(serie => { if (['response time', 'errors'].includes(serie.name)) { @@ -344,4 +252,5 @@ export class ItemDetailComponent implements OnInit { convertBytesToMbps(bytes) { return bytesToMbps(bytes); } + } diff --git a/src/app/item-detail/request-stats-compare/request-stats-compare.component.css b/src/app/item-detail/request-stats-compare/request-stats-compare.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/item-detail/request-stats-compare/request-stats-compare.component.html b/src/app/item-detail/request-stats-compare/request-stats-compare.component.html new file mode 100644 index 00000000..60e5b16c --- /dev/null +++ b/src/app/item-detail/request-stats-compare/request-stats-compare.component.html @@ -0,0 +1,116 @@ +
+ +
Request Statistics + + The values shown are in %. + Comparing to test: {{comparedMetadata.id}} with + {{comparedMetadata.maxVu}} VU + + + +
+
+ + +
+
+ +
+
+
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ label + + samples + + avg [{{getUnit()}}] + + min [ms] + + max [ms] + + P90 [ms] + + P95 [ms] + + P99 [ms] + + requests/s + + mbps + + error rate + + +
{{_.label}}{{_.samples}}{{_.avgResponseTime}}{{_.minResponseTime}}{{_.maxResponseTime}}{{_.n0}}{{_.n5}}{{_.n9}} + {{_.throughput}} + + {{convertBytesToMbps(_.bytesPerSecond + _.bytesSentPerSecond) || 0 }} + + {{_.errorRate}} + + + + + +
+
+
+
diff --git a/src/app/item-detail/request-stats-compare/request-stats-compare.component.spec.ts b/src/app/item-detail/request-stats-compare/request-stats-compare.component.spec.ts new file mode 100644 index 00000000..dfeef435 --- /dev/null +++ b/src/app/item-detail/request-stats-compare/request-stats-compare.component.spec.ts @@ -0,0 +1,87 @@ +import { HttpClientModule } from '@angular/common/http'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { DataTableModule } from '@rushvora/ng-datatable'; +import { HighchartsChartModule } from 'highcharts-angular'; +import { ToastrModule } from 'ngx-toastr'; +import { LabelErrorComponent } from '../label-error/label-error.component'; +import { LabelTrendComponent } from '../label-trend/label-trend.component'; +import { StatsCompareComponent } from '../stats-compare/stats-compare.component'; + +import { RequestStatsCompareComponent } from './request-stats-compare.component'; + +describe('RequestStatsCompareComponent', () => { + let component: RequestStatsCompareComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + RequestStatsCompareComponent, + StatsCompareComponent, + LabelErrorComponent, + LabelTrendComponent, + ], + imports: [ + DataTableModule, + NgbModule, + ToastrModule.forRoot(), + HighchartsChartModule, + HttpClientModule, + RouterTestingModule, + + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RequestStatsCompareComponent); + component = fixture.componentInstance; + component.itemData = { + overview: { + maxVu: 100 + }, + statistics: [{ + avgResponseTime: 10, + bytes: 758, + errorRate: 0, + label: '02 - Click_Log_In-22', + maxResponseTime: 38, + minResponseTime: 8, + n0: 33, + n5: 34, + n9: 37, + samples: 200, + throughput: 0.17, + }] + }; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + it('should call resetStatsData when itemToCompare triggered', () => { + const spy = spyOn(component, 'resetStatsData').and.callThrough(); + component.itemToCompare({ + maxVu: 100, + id: '123-123', + statistics: [{ + avgResponseTime: 109, + bytes: 7587, + errorRate: 0, + label: '02 - Click_Log_In-22', + maxResponseTime: 380, + minResponseTime: 81, + n0: 330, + n5: 344, + n9: 372, + samples: 200, + throughput: 0.17, + }] + }); + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/src/app/item-detail/request-stats-compare/request-stats-compare.component.ts b/src/app/item-detail/request-stats-compare/request-stats-compare.component.ts new file mode 100644 index 00000000..185c4e59 --- /dev/null +++ b/src/app/item-detail/request-stats-compare/request-stats-compare.component.ts @@ -0,0 +1,190 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; +import { ItemsApiService } from 'src/app/items-api.service'; +import { ItemParams } from 'src/app/scenario/item-controls/item-controls.model'; +import { bytesToMbps, roundNumberTwoDecimals } from '../calculations'; + +@Component({ + selector: 'app-request-stats-compare', + templateUrl: './request-stats-compare.component.html', + styleUrls: ['./request-stats-compare.component.css', '../item-detail.component.scss'] +}) +export class RequestStatsCompareComponent implements OnInit { + + @Input() itemData; + @Input() isAnonymous: boolean; + @Input() params: ItemParams; + + comparingData; + comparedData; + comparedDataMs; + compareMode = false; + labelsData; + comparisonWarning = []; + comparedMetadata; + comparisonMs = true; + unitDesc = 'ms'; + + constructor( + private itemsService: ItemsApiService, + private toastr: ToastrService + ) { + } + + ngOnInit() { + this.labelsData = this.itemData.statistics; + } + + resetStatsData() { + this.comparedData = null; + this.labelsData = this.itemData.statistics; + this.comparisonMs = true; + } + + search(term: string) { + const dataToFilter = this.comparedData || this.itemData.statistics; + if (term) { + this.labelsData = dataToFilter.filter(x => + x.label.trim().toLowerCase().includes(term.trim().toLowerCase()) + ); + } else { + this.labelsData = dataToFilter; + } + } + + itemToCompare(data) { + this.resetStatsData(); + this.comparingData = data; + this.comparedMetadata = { id: data.id, maxVu: data.maxVu }; + if (data.maxVu !== this.itemData.overview.maxVu) { + this.comparisonWarning.push(`VU do differ ${this.itemData.overview.maxVu} vs. ${data.maxVu}`); + } + + this.comparedDataMs = this.labelsData.map((_) => { + const labelToBeCompared = data.statistics.find((__) => __.label === _.label); + if (labelToBeCompared) { + return { + ..._, + avgResponseTime: (_.avgResponseTime - labelToBeCompared.avgResponseTime), + minResponseTime: (_.minResponseTime - labelToBeCompared.minResponseTime), + maxResponseTime: (_.maxResponseTime - labelToBeCompared.maxResponseTime), + // @ts-ignore + bytes: ((_.bytes - labelToBeCompared.bytes) / 1024).toFixed(2), + n0: (_.n0 - labelToBeCompared.n0), + n5: (_.n5 - labelToBeCompared.n5), + n9: (_.n9 - labelToBeCompared.n9), + errorRate: roundNumberTwoDecimals((_.errorRate - labelToBeCompared.errorRate)), + throughput: roundNumberTwoDecimals((_.throughput - labelToBeCompared.throughput)) + }; + } else { + this.comparisonWarning.push(`${_.label} label not found`); + return { + ..._, + avgResponseTime: null, + minResponseTime: null, + maxResponseTime: null, + n0: null, + n5: null, + n9: null, + errorRate: null, + throughput: null, + bytes: null, + }; + } + }); + if (data.environment !== this.itemData.environment) { + this.comparisonWarning.push('Environments do differ'); + } + this.comparedData = this.comparedDataMs; + this.labelsData = this.comparedData; + + if (this.comparisonWarning.length) { + this.showComparisonWarnings(); + } + } + + quickBaseComparison(id) { + this.itemsService.fetchItemDetail( + this.params.projectName, + this.params.scenarioName, + id).subscribe(_ => this.itemToCompare({ + statistics: _.statistics, + maxVu: _.overview.maxVu, + environment: _.environment, + id + })); + } + + showComparisonWarnings() { + this.toastr.warning(this.comparisonWarning.join('
'), 'Comparison Warning!', + { + closeButton: true, + enableHtml: true, + timeOut: 15000, + positionClass: 'toast-bottom-right' + }); + this.comparisonWarning = []; + } + + convertBytesToMbps(bytes) { + return bytesToMbps(bytes); + } + + switchComparisonDataUnit() { + if (this.comparisonMs) { + this.comparedData = this.labelsData.map((_) => { + const labelToBeCompared = this.comparingData.statistics.find((__) => __.label === _.label); + if (labelToBeCompared) { + return { + ..._, + avgResponseTime: this.calculatePercDifference(_.avgResponseTime, labelToBeCompared.avgResponseTime), + minResponseTime: this.calculatePercDifference(_.minResponseTime, labelToBeCompared.minResponseTime), + maxResponseTime: this.calculatePercDifference(_.maxResponseTime, labelToBeCompared.maxResponseTime), + // @ts-ignore + bytes: this.calculatePercDifference(_.bytes, labelToBeCompared.bytes) / 1024, + n0: this.calculatePercDifference(_.n0, labelToBeCompared.n0), + n5: this.calculatePercDifference(_.n5, labelToBeCompared.n5), + n9: this.calculatePercDifference(_.n9, labelToBeCompared.n9), + errorRate: this.calculatePercDifference(_.errorRate, labelToBeCompared.errorRate), + throughput: this.calculatePercDifference(_.throughput, labelToBeCompared.throughput) + }; + } else { + this.comparisonWarning.push(`${_.label} label not found`); + return { + ..._, + avgResponseTime: null, + minResponseTime: null, + maxResponseTime: null, + n0: null, + n5: null, + n9: null, + errorRate: null, + throughput: null, + bytes: null, + }; + } + }); + } else { + this.comparedData = this.comparedDataMs; + } + + this.comparisonMs = !this.comparisonMs; + this.labelsData = this.comparedData; + } + + private calculatePercDifference(x, y) { + const percDiff = (x / y) * 100; + if (percDiff === Infinity || isNaN(percDiff)) { + return 0; + } + return roundNumberTwoDecimals(percDiff); + } + + getUnit() { + if (this.comparisonMs) { + return 'ms'; + } + return '%'; + } + +}