[monitor] 提供指标实时数据查询API,初始化监控详情页代码

This commit is contained in:
tomsun28
2021-12-05 18:23:45 +08:00
parent 5b86e9f48e
commit 206408e80e
15 changed files with 352 additions and 7 deletions

View File

@@ -0,0 +1,34 @@
package com.usthe.common.entity.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 监控指标组指标字段
* @author tom
* @date 2021/12/5 17:29
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "监控指标组指标字段")
public class Field {
@ApiModelProperty(value = "指标采集字符名称", position = 0)
private String name;
@ApiModelProperty(value = "字段类型0-number数字 1-string字符串", position = 1)
private Byte type;
@ApiModelProperty(value = "指标单位", position = 2)
private String unit;
@ApiModelProperty(value = "是否是实例字段", position = 3)
private boolean instance;
}

View File

@@ -0,0 +1,41 @@
package com.usthe.common.entity.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 指标组监控数据
* @author tom
* @date 2021/12/5 17:24
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "指标组监控数据")
public class MetricsData {
@ApiModelProperty(value = "监控ID", position = 0)
private Long id;
@ApiModelProperty(value = "监控类型", position = 1)
private String app;
@ApiModelProperty(value = "监控指标组", position = 2)
private String metric;
@ApiModelProperty(value = "最新采集时间", position = 3)
private Long time;
@ApiModelProperty(value = "监控指标字段列表", position = 4)
private List<Field> fields;
@ApiModelProperty(value = "监控指标列表值集合")
private List<ValueRow> valueRows;
}

View File

@@ -0,0 +1,40 @@
package com.usthe.common.entity.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 监控指标组指标值
* @author tom
* @date 2021/12/5 17:43
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "监控指标组指标值")
public class Value {
public Value(String origin) {
this.origin = origin;
}
@ApiModelProperty(value = "原始值", position = 0)
private String origin;
@ApiModelProperty(value = "平均值", position = 1)
private String mean;
@ApiModelProperty(value = "中位数值", position = 0)
private String median;
@ApiModelProperty(value = "最小值", position = 0)
private String min;
@ApiModelProperty(value = "最大值", position = 0)
private String max;
}

View File

@@ -0,0 +1,29 @@
package com.usthe.common.entity.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 监控指标组的一行指标数据
* @author tom
* @date 2021/12/5 17:39
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "监控指标组的一行指标数据")
public class ValueRow {
@ApiModelProperty(value = "此行数据唯一实例", position = 0)
private String instance;
@ApiModelProperty(value = "监控指标组指标值", position = 1)
private List<Value> values;
}

View File

@@ -0,0 +1,82 @@
package com.usthe.warehouse.controller;
import com.usthe.common.entity.dto.Field;
import com.usthe.common.entity.dto.Message;
import com.usthe.common.entity.dto.MetricsData;
import com.usthe.common.entity.dto.Value;
import com.usthe.common.entity.dto.ValueRow;
import com.usthe.common.entity.message.CollectRep;
import com.usthe.warehouse.store.RedisDataStorage;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
/**
* 指标数据查询接口
* @author tom
* @date 2021/12/5 15:52
*/
@RestController
@RequestMapping(produces = {APPLICATION_JSON_VALUE})
@Api(tags = "监控指标数据API")
public class MetricsDataController {
@Autowired
private RedisDataStorage redisDataStorage;
@GetMapping("/monitors/{monitorId}/metrics/{metric}")
@ApiOperation(value = "查询监控指标组的指标数据", notes = "查询监控指标组的指标数据")
public ResponseEntity<Message<MetricsData>> getMetricsData(
@ApiParam(value = "监控ID", example = "343254354")
@PathVariable Long monitorId,
@ApiParam(value = "监控指标组", example = "cpu")
@PathVariable String metric) {
CollectRep.MetricsData redisData = redisDataStorage.getCurrentMetricsData(monitorId, metric);
if (redisData == null) {
return ResponseEntity.ok().body(new Message<>("query metrics data is empty"));
}
{
MetricsData.MetricsDataBuilder dataBuilder = MetricsData.builder();
dataBuilder.id(redisData.getId()).app(redisData.getApp()).metric(redisData.getMetrics())
.time(redisData.getTime());
List<Field> fields = redisData.getFieldsList().stream().map(redisField ->
Field.builder().name(redisField.getName())
.type(Integer.valueOf(redisField.getType()).byteValue()).build())
.collect(Collectors.toList());
dataBuilder.fields(fields);
List<ValueRow> valueRows = redisData.getValuesList().stream().map(redisValueRow ->
ValueRow.builder().instance(redisValueRow.getInstance())
.values(redisValueRow.getColumnsList().stream().map(Value::new).collect(Collectors.toList()))
.build()).collect(Collectors.toList());
dataBuilder.valueRows(valueRows);
return ResponseEntity.ok().body(new Message<>(dataBuilder.build()));
}
}
@GetMapping("/monitors/{monitorId}/metrics/{metric}/fields/{field}")
@ApiOperation(value = "查询监控指标组的指定指标的历史数据", notes = "查询监控指标组下的指定指标的历史数据")
public ResponseEntity<Message<Void>> getMetricHistoryData(
@ApiParam(value = "监控ID", example = "343254354")
@PathVariable Long monitorId,
@ApiParam(value = "监控指标组", example = "cpu")
@PathVariable String metric,
@ApiParam(value = "监控指标组指标", example = "343254354")
@PathVariable String field,
@ApiParam(value = "查询历史时间段,默认6h-6小时:h-小时, d-天, m-月, y-年", example = "6h")
@RequestParam(required = false) String history
) {
return ResponseEntity.ok().body(null);
}
}

View File

@@ -8,11 +8,13 @@ import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.sync.RedisCommands;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@@ -43,6 +45,11 @@ public class RedisDataStorage implements DisposableBean {
startStorageData();
}
public CollectRep.MetricsData getCurrentMetricsData(@NonNull Long monitorId, @NonNull String metric) {
RedisCommands<String, CollectRep.MetricsData> commands = connection.sync();
return commands.hget(String.valueOf(monitorId), metric);
}
private void startStorageData() {
Runnable runnable = () -> {
Thread.currentThread().setName("warehouse-redis-data-storage");

View File

@@ -4,4 +4,5 @@ com.usthe.warehouse.MetricsDataQueue,\
com.usthe.warehouse.WarehouseWorkerPool,\
com.usthe.warehouse.entrance.KafkaDataConsume,\
com.usthe.warehouse.store.InfluxdbDataStorage,\
com.usthe.warehouse.store.RedisDataStorage
com.usthe.warehouse.store.RedisDataStorage,\
com.usthe.warehouse.controller.MetricsDataController

View File

@@ -41,8 +41,10 @@
"@delon/util": "^12.4.2",
"ajv": "^8.6.2",
"ajv-formats": "^2.1.1",
"echarts": "^5.2.2",
"ng-alain": "^12.4.2",
"ng-zorro-antd": "^12.0.2",
"ngx-echarts": "^v7.1.0",
"rxjs": "~6.6.0",
"screenfull": "^5.1.0",
"tslib": "^2.3.0",
@@ -60,6 +62,7 @@
"@angular/language-service": "~12.2.0",
"@delon/testing": "^12.4.2",
"@ngx-formly/schematics": "^5.10.23",
"@types/echarts": "^4.9.12",
"@types/jasmine": "~3.8.0",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "~4.29.2",

View File

@@ -84,6 +84,7 @@ import { RoutesModule } from './routes/routes.module';
import { SharedModule } from './shared/shared.module';
import { STWidgetModule } from './shared/st-widget/st-widget.module';
import { ReactiveFormsModule } from '@angular/forms';
import { NgxEchartsModule } from 'ngx-echarts';
@NgModule({
declarations: [
@@ -103,7 +104,10 @@ import { ReactiveFormsModule } from '@angular/forms';
NzNotificationModule,
...FORM_MODULES,
...GLOBAL_THIRD_MODULES,
ReactiveFormsModule
ReactiveFormsModule,
NgxEchartsModule.forRoot({
echarts: () => import('echarts')
})
],
providers: [
...LANG_PROVIDES,

View File

@@ -1 +1,21 @@
<p>monitor-detail works!</p>
<nz-divider></nz-divider>
<nz-breadcrumb>
<nz-breadcrumb-item>
<a [routerLink]="['/']">
<i nz-icon nzType="home"></i>
</a>
</nz-breadcrumb-item>
<nz-breadcrumb-item>
<a [routerLink]="['/monitors']" [queryParams]="{app: app ? app : ''}">
<i nz-icon nzType="monitor"></i>
<span>监控列表</span>
</a>
</nz-breadcrumb-item>
<nz-breadcrumb-item>
<i nz-icon nzType="pie-chart"></i>
<span>{{'monitor.app.' + app | i18n}} 监控详情</span>
</nz-breadcrumb-item>
</nz-breadcrumb>
<nz-divider></nz-divider>
<div echarts [options]="options" class="demo-chart"></div>

View File

@@ -1,4 +1,11 @@
import { Component, OnInit } from '@angular/core';
import {MonitorService} from "../../../service/monitor.service";
import {ActivatedRoute, ParamMap, Router} from "@angular/router";
import {TitleService} from "@delon/theme";
import {switchMap} from "rxjs/operators";
import {Message} from "../../../pojo/Message";
import {Param} from "../../../pojo/Param";
import {throwError} from "rxjs";
@Component({
selector: 'app-monitor-detail',
@@ -8,9 +15,74 @@ import { Component, OnInit } from '@angular/core';
})
export class MonitorDetailComponent implements OnInit {
constructor() { }
constructor(private monitorSvc: MonitorService,
private route: ActivatedRoute,
private router: Router,
private titleSvc: TitleService) { }
isSpinning: boolean = false
monitorId!: number;
app: string | undefined;
options: any;
ngOnInit(): void {
}
this.route.paramMap.pipe(
switchMap((paramMap: ParamMap) => {
this.isSpinning = false;
let id = paramMap.get("monitorId");
this.monitorId = Number(id);
// 查询监控指标组结构信息
return this.monitorSvc.getMonitor(this.monitorId);
})
).subscribe(message => {
if (message.code === 0) {
} else {
console.warn(message.msg);
}
});
const xAxisData = [];
const data1 = [];
const data2 = [];
for (let i = 0; i < 10; i++) {
xAxisData.push('category' + i);
data1.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5);
data2.push((Math.cos(i / 5) * (i / 5 - 10) + i / 6) * 5);
}
this.options = {
legend: {
data: ['bar', 'bar2'],
align: 'left',
},
tooltip: {},
xAxis: {
data: xAxisData,
silent: false,
splitLine: {
show: false,
},
},
yAxis: {},
series: [
{
name: 'bar',
type: 'bar',
data: data1,
animationDelay: (idx: number) => idx * 10,
},
{
name: 'bar2',
type: 'bar',
data: data2,
animationDelay: (idx: number) => idx * 10 + 100,
},
],
animationEasing: 'elasticOut',
animationDelayUpdate: (idx: number) => idx * 5,
};
}
}

View File

@@ -10,6 +10,7 @@ import {Monitor} from "../../../pojo/Monitor";
import {FormGroup} from "@angular/forms";
import {Message} from "../../../pojo/Message";
import {throwError} from "rxjs";
import {TitleService} from "@delon/theme";
@Component({
selector: 'app-monitor-modify',
@@ -23,6 +24,7 @@ export class MonitorEditComponent implements OnInit {
private monitorSvc: MonitorService,
private route: ActivatedRoute,
private router: Router,
private titleSvc: TitleService,
private notifySvc: NzNotificationService,) { }
paramDefines!: ParamDefine[];
@@ -47,6 +49,7 @@ export class MonitorEditComponent implements OnInit {
).pipe(switchMap((message: Message<any>) => {
if (message.code === 0) {
this.monitor = message.data.monitor;
this.titleSvc.setTitleByI18n('monitor.app.' + this.monitor.app)
if (message.data.params != null) {
message.data.params.forEach((item: Param) => {
this.paramValueMap.set(item.field, item)

View File

@@ -58,7 +58,11 @@
<tbody>
<tr *ngFor="let data of fixedTable.data">
<td nzAlign="center" nzLeft [nzChecked]="checkedMonitorIds.has(data.id)" (nzCheckedChange)="onItemChecked(data.id, $event)"></td>
<td nzAlign="center">{{ data.name }}</td>
<td nzAlign="center">
<a [routerLink]="['/monitors/' + data.id]">
<span>{{ data.name }}</span>
</a>
</td>
<td nzAlign="center">
<nz-tag *ngIf="data.status == 0" nzColor="default">
<i nz-icon nzType="robot" nzTheme="outline"></i>

View File

@@ -9,6 +9,7 @@ import {Param} from "../../../pojo/Param";
import {Monitor} from "../../../pojo/Monitor";
import {MonitorService} from "../../../service/monitor.service";
import {NzNotificationService} from "ng-zorro-antd/notification";
import {TitleService} from "@delon/theme";
@Component({
selector: 'app-monitor-add',
@@ -32,6 +33,7 @@ export class MonitorNewComponent implements OnInit {
private notifySvc: NzNotificationService,
private cdr: ChangeDetectorRef,
private i18n: I18NService,
private titleSvc: TitleService,
private formBuilder: FormBuilder) {
this.monitor = new Monitor();
}
@@ -40,6 +42,7 @@ export class MonitorNewComponent implements OnInit {
this.route.queryParamMap.pipe(
switchMap((paramMap: ParamMap) => {
this.monitor.app = paramMap.get("app") || '';
this.titleSvc.setTitleByI18n('monitor.app.' + this.monitor.app)
this.detected = true;
this.passwordVisible = false;
this.isSpinning = false;

View File

@@ -10,6 +10,7 @@ import {NzDividerModule} from "ng-zorro-antd/divider";
import {NzSwitchModule} from "ng-zorro-antd/switch";
import {NzTagModule} from "ng-zorro-antd/tag";
import {NzRadioModule} from "ng-zorro-antd/radio";
import {NgxEchartsModule} from "ngx-echarts";
const COMPONENTS: Type<void>[] = [
MonitorNewComponent,
@@ -26,7 +27,8 @@ const COMPONENTS: Type<void>[] = [
NzDividerModule,
NzSwitchModule,
NzTagModule,
NzRadioModule
NzRadioModule,
NgxEchartsModule
],
declarations: COMPONENTS,
})