add limit by

This commit is contained in:
2019-10-19 08:48:21 +02:00
parent a3099d4981
commit 24bf7c98e3
15 changed files with 286 additions and 110 deletions

View File

@@ -11,11 +11,15 @@ import { HelpPageComponent } from './help-page/help-page.component';
import { UploadPageComponent } from './upload-page/upload-page.component'; import { UploadPageComponent } from './upload-page/upload-page.component';
import { VisualizationPageComponent } from './visualization-page/visualization-page.component'; import { VisualizationPageComponent } from './visualization-page/visualization-page.component';
import {MatSelectModule} from '@angular/material/select';
import {MatAutocompleteModule} from '@angular/material/autocomplete'; import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatButtonModule} from '@angular/material/button';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatSelectModule} from '@angular/material/select';
import {MatFormFieldModule, MatInputModule} from '@angular/material'; import {MatFormFieldModule, MatInputModule} from '@angular/material';
import {MatTooltipModule} from '@angular/material/tooltip';
import { YAxisRangeComponent } from './y-axis-range/y-axis-range.component'; import { YAxisRangeComponent } from './y-axis-range/y-axis-range.component';
import { QueryAutocompleteComponent } from './query-autocomplete/query-autocomplete.component'; import { QueryAutocompleteComponent } from './query-autocomplete/query-autocomplete.component';
import { LimitByComponent } from './limit-by/limit-by.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -26,14 +30,20 @@ import { QueryAutocompleteComponent } from './query-autocomplete/query-autocompl
VisualizationPageComponent, VisualizationPageComponent,
YAxisRangeComponent, YAxisRangeComponent,
QueryAutocompleteComponent, QueryAutocompleteComponent,
LimitByComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
AppRoutingModule, AppRoutingModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
MatSelectModule,MatFormFieldModule, MatInputModule,
MatAutocompleteModule, MatAutocompleteModule,
MatButtonModule,
MatCheckboxModule,
MatFormFieldModule,
MatInputModule,
MatSelectModule,
MatTooltipModule,
BrowserAnimationsModule, BrowserAnimationsModule,
HttpClientModule HttpClientModule
], ],

View File

@@ -13,14 +13,37 @@ This page describes how to use this tool.
<h2>Why not use one of the existing monitoring tools?</h2> <h2>Why not use one of the existing monitoring tools?</h2>
<p> <p>
There are many tools out there that do monitoring in one way or the other. Here are a few: There are many tools out there that do monitoring in one way or the other. There are complete closed source solutions like
<a href="https://newrelic.com" target="_blank" rel="nofollow" class="external-link">New Relic</a>, <a href="https://newrelic.com" target="_blank" rel="nofollow" class="external-link">New Relic</a>,
<a href="https://www.splunk.com" target="_blank" rel="nofollow" class="external-link">Splunk</a>, <a href="https://www.splunk.com" target="_blank" rel="nofollow" class="external-link">Splunk</a>,
and <a href="https://www.appdynamics.com/" target="_blank" rel="nofollow" class="external-link">App Dynamics</a>.
They bring everything you need, a tool to collect logs or metrics, a database, a UI with dashboards and alerting.
</p>
<p>
The OSS community likes to break them down into separate tools.
For example the <a href="https://www.elastic.co/products/elastic-stack" target="_blank" rel="nofollow" class="external-link">ELK</a>-stack
consists of <a href="https://www.elastic.co/products/elasticsearch" target="_blank" rel="nofollow" class="external-link">Elasticsearch</a>
as database, and <a href="https://www.elastic.co/products/kibana" target="_blank" rel="nofollow" class="external-link">Kibana</a>
as the UI. It is usually combined with <a href="https://www.elastic.co/products/logstash" target="_blank" rel="nofollow" class="external-link">Logstash</a>
for logfile collection.<br/>
Another popular UI is <a href="https://grafana.com/" target="_blank" rel="nofollow" class="external-link">Grafana</a>
which can be combined with
<a href="https://grafana.com/oss/loki/" target="_blank" rel="nofollow" class="external-link">Loki</a>,
<a href="https://prometheus.io/" target="_blank" rel="nofollow" class="external-link">Prometheus</a>,
<a href="https://graphiteapp.org/" target="_blank" rel="nofollow" class="external-link">Graphite</a> or
<a href="https://grafana.com/oss/metrictank/" target="_blank" rel="nofollow" class="external-link">Metrictank</a> (which is an engine for Graphite).
<br/>
<br/><br/>
<a href="https://www.elastic.co/products/kibana" target="_blank" rel="nofollow" class="external-link">Kibana</a> (<a href="https://www.elastic.co/products/elastic-stack" target="_blank" rel="nofollow" class="external-link">ELK</a>), <a href="https://www.elastic.co/products/kibana" target="_blank" rel="nofollow" class="external-link">Kibana</a> (<a href="https://www.elastic.co/products/elastic-stack" target="_blank" rel="nofollow" class="external-link">ELK</a>),
<a href="https://www.appdynamics.com/" target="_blank" rel="nofollow" class="external-link">App Dynamics</a>, <a href="https://www.appdynamics.com/" target="_blank" rel="nofollow" class="external-link">App Dynamics</a>,
<a href="https://graphiteapp.org/" target="_blank" rel="nofollow" class="external-link">Graphite</a>, <a href="https://graphiteapp.org/" target="_blank" rel="nofollow" class="external-link">Graphite</a>
<a href="https://prometheus.io/" target="_blank" rel="nofollow" class="external-link">Prometheus</a> (<a href="https://grafana.com/oss/metrictank/" target="_blank" rel="nofollow" class="external-link">Metrictank</a>),
+ <a href="https://grafana.com/" target="_blank" rel="nofollow" class="external-link">Grafana</a>, <a href="https://prometheus.io/" target="_blank" rel="nofollow" class="external-link">Prometheus</a>,
<a href="https://grafana.com/oss/loki/" target="_blank" rel="nofollow" class="external-link">Loki</a>,
<a href="https://grafana.com/" target="_blank" rel="nofollow" class="external-link">Grafana</a>,
<a href="https://github.com/Netflix/atlas" target="_blank" rel="nofollow" class="external-link">Netflix Atlas</a>, <a href="https://github.com/Netflix/atlas" target="_blank" rel="nofollow" class="external-link">Netflix Atlas</a>,
<a href="https://oss.oetiker.ch/rrdtool/" target="_blank" rel="nofollow" class="external-link">RRDtool</a>, <a href="https://oss.oetiker.ch/rrdtool/" target="_blank" rel="nofollow" class="external-link">RRDtool</a>,
<a href="http://ganglia.sourceforge.net/" target="_blank" rel="nofollow" class="external-link">Ganglia</a>, <a href="http://ganglia.sourceforge.net/" target="_blank" rel="nofollow" class="external-link">Ganglia</a>,
@@ -38,6 +61,7 @@ There are many tools out there that do monitoring in one way or the other. Here
<a href="https://www.site24x7.com/" target="_blank" rel="nofollow" class="external-link">Site24x7</a>, <a href="https://www.site24x7.com/" target="_blank" rel="nofollow" class="external-link">Site24x7</a>,
<a href="https://www.datadoghq.com/" target="_blank" rel="nofollow" class="external-link">Datadog</a>, <a href="https://www.datadoghq.com/" target="_blank" rel="nofollow" class="external-link">Datadog</a>,
<a href="https://www.microfocus.com/en-us/products/sitescope-application-monitoring/overview" target="_blank" rel="nofollow" class="external-link">Sitescope</a>, <a href="https://www.microfocus.com/en-us/products/sitescope-application-monitoring/overview" target="_blank" rel="nofollow" class="external-link">Sitescope</a>,
<a href="https://www.signalfx.com/" target="_blank" rel="nofollow" class="external-link">SignalFX</a>,
and many more. None of them provides the visualizations we had in mind. We wanted to plot each value of the time series data individually, so that we can identify the and many more. None of them provides the visualizations we had in mind. We wanted to plot each value of the time series data individually, so that we can identify the
response times of a single request. But tools like Splunk, Kibana, Chronograf or Grafana only plot aggregated data (average, min/max, percentiles). response times of a single request. But tools like Splunk, Kibana, Chronograf or Grafana only plot aggregated data (average, min/max, percentiles).
<p> <p>

View File

@@ -0,0 +1,22 @@
<mat-form-field id="limitBy">
<mat-label>Limit By:</mat-label>
<mat-select [(value)]="limitBy">
<mat-option value="NO_LIMIT">no limit</mat-option>
<mat-option value="MOST_VALUES">most values</mat-option>
<mat-option value="FEWEST_VALUES">fewest values</mat-option>
<mat-option value="MAX_VALUE">max value</mat-option>
<mat-option value="MIN_VALUE">min value</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field id="limit"
*ngIf="limitBy !== 'NO_LIMIT'">
<input
matInput
type="number"
placeholder="Limit"
min="1"
value="{{limit}}"
>
</mat-form-field>

View File

@@ -0,0 +1,9 @@
#limitBy {
width: 10em;
margin-right: 1ex;
}
#limit {
width: 5em;
margin-right: 0ex;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LimitByComponent } from './limit-by.component';
describe('LimitByComponent', () => {
let component: LimitByComponent;
let fixture: ComponentFixture<LimitByComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LimitByComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LimitByComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import { Component, OnInit, Input} from '@angular/core';
@Component({
selector: 'pdb-limit-by',
templateUrl: './limit-by.component.html',
styleUrls: ['./limit-by.component.scss']
})
export class LimitByComponent implements OnInit {
@Input() limit: number;
@Input() limitBy: string;
constructor() { }
ngOnInit() {
}
}

View File

@@ -33,7 +33,6 @@ export class PlotService {
this.plotTypes.push(new PlotType("Bar", "bar-chart", false, DataType.Other, DataType.Other)); this.plotTypes.push(new PlotType("Bar", "bar-chart", false, DataType.Other, DataType.Other));
this.tagFields = new Array<TagField>(); this.tagFields = new Array<TagField>();
} }
ngOnInit() { ngOnInit() {

View File

@@ -4,7 +4,8 @@
placeholder="Query" placeholder="Query"
[formControl]="query" [formControl]="query"
[matAutocomplete]="auto" [matAutocomplete]="auto"
(keyup)="onKey($event)"/> (keyup)="onKey($event)"
(focus)="onKey($event)"/>
<mat-autocomplete <mat-autocomplete
#auto="matAutocomplete" #auto="matAutocomplete"
[displayWith]="displaySuggestion" [displayWith]="displaySuggestion"

View File

@@ -0,0 +1,12 @@
.mat-option {
/*height: 1.5em;
line-height: 1.5em;
/**/
}
#query-autocomplete-input {
border: solid 1px #ccc;
border-radius: 5px;
box-sizing: border-box;
padding: 5px;
}

View File

@@ -11,7 +11,7 @@ import { PlotService, PlotType, AutocompleteResult, Suggestion } from '../plot.s
}) })
export class QueryAutocompleteComponent implements OnInit { export class QueryAutocompleteComponent implements OnInit {
@Input() query = new FormControl(); @Input() query = new FormControl('');
suggestions = new FormControl(); suggestions = new FormControl();

View File

@@ -1,14 +1,9 @@
<style>
</style>
<div id="visualization"> <div id="visualization">
<div id="query-box"> <div id="query-box">
<!-- [query]="query"-->
<pdb-query-autocomplete ></pdb-query-autocomplete> <pdb-query-autocomplete ></pdb-query-autocomplete>
</div> </div>
<div id="filters"> <div id="filters">
<div id="filterpanel">
<mat-form-field class="mat-field-full-width"> <mat-form-field class="mat-field-full-width">
<mat-label>Date Range:</mat-label> <mat-label>Date Range:</mat-label>
<input matInput [formControl]="dateRange" name="dates" /> <input matInput [formControl]="dateRange" name="dates" />
@@ -35,18 +30,15 @@
<mat-form-field> <mat-form-field>
<mat-label>Group By:</mat-label> <mat-label>Group By:</mat-label>
<mat-select multiple> <mat-select multiple [(value)]="groupBy">
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{tagField.name}}</mat-option> <mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{tagField.name}}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field> <pdb-limit-by
<mat-label>Split By:</mat-label> [(limitBy)]="limitBy"
<mat-select> [limit]="limit"
<mat-option>-</mat-option> ></pdb-limit-by>
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{tagField.name}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>Y-Axis:</mat-label> <mat-label>Y-Axis:</mat-label>
@@ -63,18 +55,38 @@
></pdb-y-axis-range> ></pdb-y-axis-range>
<div> <mat-checkbox [(ngModel)]="enableGallery">Gallery</mat-checkbox>
<button mat-button (click)="plot()">
<mat-form-field *ngIf="enableGallery">
<mat-label>Split By:</mat-label>
<mat-select [(value)]="splitBy">
<mat-option *ngFor="let tagField of tagFields" [value]="tagField">{{tagField.name}}</mat-option>
</mat-select>
<mat-error *ngIf="splitBy == null || true">
Please select a value!
</mat-error>
</mat-form-field>
<div id="plot-button-bar">
<button
*ngIf="!enableGallery"
mat-button
matTooltip="Create Plot"
(click)="plot()">
<img src="assets/img/scatter-chart2.svg" class="icon-inline" aria-hidden="true" title="create plot" /> <img src="assets/img/scatter-chart2.svg" class="icon-inline" aria-hidden="true" title="create plot" />
Plot Plot
</button> </button>
<!-- <button
<button mat-button (click)="gallery()"> *ngIf="enableGallery"
mat-button
matTooltip="Create Gallery"
(click)="plot()"
[disabled]="this.splitBy == null">
<img src="assets/img/four-squares-line.svg" class="icon-inline" aria-hidden="true" title="Create Gallery (only active if 'Split' is set)" /> <img src="assets/img/four-squares-line.svg" class="icon-inline" aria-hidden="true" title="Create Gallery (only active if 'Split' is set)" />
Gallery Gallery
</button> </button>
--> </div>
</div> </div>
</div> </div>
<div id="results"></div> <div id="results"></div>

View File

@@ -14,14 +14,13 @@
grid: grid:
"query-box query-box" auto "query-box query-box" auto
"filters results" 1fr "filters results" 1fr
/ 23em 3fr; / 25em 3fr;
} }
} }
@media screen and (max-width: 800px) { @media screen and (max-width: 1000px) {
#visualization { #visualization {
display: grid; display: grid;
height: 100%;
margin: 0; margin: 0;
grid: grid:
"query-box" auto "query-box" auto
@@ -29,22 +28,31 @@
"results" 1fr "results" 1fr
/ 1fr; / 1fr;
} }
#results {
height: 600px;
}
} }
#query-box { #query-box {
grid-area: query-box; grid-area: query-box;
padding: 2px; margin: 1em;
border-bottom: 1px solid black;
} }
#filters { #filters {
grid-area: filters; grid-area: filters;
background-color: #fafafa; }
#filterpanel {
background-color: #f8f8f8;/*#fafafa;*/
padding: 1em; padding: 1em;
margin: 0 1em 1em 1em;
border-radius: 5px;
} }
#results { #results {
grid-area: results; grid-area: results;
} }
#plot-button-bar {
text-align: right;
}

View File

@@ -1,7 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { PlotService, PlotType } from '../plot.service'; import { PlotService, PlotType } from '../plot.service';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { FormControl } from '@angular/forms'; import { FormControl, Validators } from '@angular/forms';
@Component({ @Component({
selector: 'pdb-visualization-page', selector: 'pdb-visualization-page',
@@ -22,6 +23,10 @@ export class VisualizationPageComponent implements OnInit {
tagFields: Array<any>; tagFields: Array<any>;
groupBy: Array<any> = [];
limitBy: string = "NO_LIMIT";
limit = 10;
yAxis: string; yAxis: string;
yAxisUnit: string; yAxisUnit: string;
minYValue: number; minYValue: number;
@@ -29,6 +34,12 @@ export class VisualizationPageComponent implements OnInit {
query: string; query: string;
enableGallery = true;
splitBy = new FormControl(null, [
Validators.required
]);
constructor(private plotService: PlotService) { constructor(private plotService: PlotService) {
} }
@@ -48,23 +59,37 @@ export class VisualizationPageComponent implements OnInit {
this.minYValue = 0; this.minYValue = 0;
this.maxYValue = 120; this.maxYValue = 120;
this.selectedPlotType.valueChanges.subscribe(function(selectedMainPlotType){ this.selectedPlotType.valueChanges.subscribe(function(selectedMainPlotType){
that.combinePlotTypes = that.getCombinablePlotTypes(selectedMainPlotType); that.combinePlotTypes = that.getCombinablePlotTypes(selectedMainPlotType);
}); });
} }
getCombinablePlotTypes(selectedMainPlotType) : Array<any>{ getCombinablePlotTypes(selectedMainPlotType) : Array<any>{
// get compatible plot types
const mainPlotType = this.availablePlotTypes[selectedMainPlotType]; const mainPlotType = this.availablePlotTypes[selectedMainPlotType];
const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(mainPlotType)); const compatiblePlotTypes = this.plotTypes.filter(pt => pt.compatible(mainPlotType));
console.log(compatiblePlotTypes);
return compatiblePlotTypes; return compatiblePlotTypes;
} }
plot(){ plot(){
console.log("plot clicked");
var request = {};
request['query'] = this.query;
request['height'] = document.getElementById("results").offsetHeight;
request['width'] = document.getElementById("results").offsetWidth;
request['groupBy'] = this.groupBy.map(o => o.name);
request['limitBy'] = this.limitBy;
request['limit'] = this.limit;
request['dateRange'] = this.dateRange.value;
request['axisScale'] = this.yAxis;
request['aggregate'] = this.selectedCombinePlotType.value;
request['keyOutside'] = false;
request['generateThumbnail'] = this.enableGallery;
request['yRangeMin'] = this.minYValue;
request['yRangeMax'] = this.maxYValue;
request['yRangeUnit'] = this.yAxisUnit;
console.log("plot clicked: "+ JSON.stringify(request));
} }
} }

View File

@@ -1,9 +1,10 @@
<div>
<mat-form-field> <mat-form-field>
<input matInput type="number" placeholder="Min Y-Value" min="0" value="{{minYValue}}"> <input matInput type="number" placeholder="Min Y-Value" min="0" value="{{minYValue}}">
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<input matInput type="number" placeholder="Max Y-Value" min="0" value="{{maxYValue}}"> <input matInput type="number" placeholder="Max Y-Value" min="0" value="{{maxYValue}}">
</mat-form-field> </mat-form-field>
<mat-form-field class="yAxisUnit"> <mat-form-field class="yAxisUnit">
<mat-label>Unit:</mat-label> <mat-label>Unit:</mat-label>
<mat-select [(value)]="yAxisUnit"> <mat-select [(value)]="yAxisUnit">
@@ -14,4 +15,5 @@
<mat-option value="HOURS">hours</mat-option> <mat-option value="HOURS">hours</mat-option>
<mat-option value="DAYS">days</mat-option> <mat-option value="DAYS">days</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div>

View File

@@ -35,6 +35,10 @@ body {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
button[disabled] .icon-inline {
opacity: 0.5;
}
.icon-small { .icon-small {
width: 1.5em; width: 1.5em;
height: 1.5em; height: 1.5em;
@@ -65,6 +69,9 @@ a.external-link:after {
content: ""; content: "";
} }
.inline {
display: inline-block;
}
body .mat-select-panel { body .mat-select-panel {
@@ -78,3 +85,5 @@ body .mat-select-panel {
mat-form-field { mat-form-field {
width: 100%; width: 100%;
} }