From f4b20ab13a5552a299b39a3155a4ff6494ad5be7 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Sun, 5 Dec 2021 18:23:45 +0800 Subject: [PATCH] =?UTF-8?q?[monitor]=20=E6=8F=90=E4=BE=9B=E6=8C=87?= =?UTF-8?q?=E6=A0=87=E5=AE=9E=E6=97=B6=E6=95=B0=E6=8D=AE=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?API=EF=BC=8C=E5=88=9D=E5=A7=8B=E5=8C=96=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E9=A1=B5=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/usthe/common/entity/dto/Field.java | 34 ++++++++ .../usthe/common/entity/dto/MetricsData.java | 41 ++++++++++ .../com/usthe/common/entity/dto/Value.java | 40 +++++++++ .../com/usthe/common/entity/dto/ValueRow.java | 29 +++++++ .../controller/MetricsDataController.java | 82 +++++++++++++++++++ .../warehouse/store/RedisDataStorage.java | 7 ++ .../main/resources/META-INF/spring.factories | 3 +- web-app/package.json | 3 + web-app/src/app/app.module.ts | 6 +- .../monitor-detail.component.html | 22 ++++- .../monitor-detail.component.ts | 76 ++++++++++++++++- .../monitor-edit/monitor-edit.component.ts | 3 + .../monitor-list/monitor-list.component.html | 6 +- .../monitor-new/monitor-new.component.ts | 3 + .../src/app/routes/monitor/monitor.module.ts | 4 +- 15 files changed, 352 insertions(+), 7 deletions(-) create mode 100644 common/src/main/java/com/usthe/common/entity/dto/Field.java create mode 100644 common/src/main/java/com/usthe/common/entity/dto/MetricsData.java create mode 100644 common/src/main/java/com/usthe/common/entity/dto/Value.java create mode 100644 common/src/main/java/com/usthe/common/entity/dto/ValueRow.java create mode 100644 warehouse/src/main/java/com/usthe/warehouse/controller/MetricsDataController.java diff --git a/common/src/main/java/com/usthe/common/entity/dto/Field.java b/common/src/main/java/com/usthe/common/entity/dto/Field.java new file mode 100644 index 0000000..82a5eda --- /dev/null +++ b/common/src/main/java/com/usthe/common/entity/dto/Field.java @@ -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; + +} diff --git a/common/src/main/java/com/usthe/common/entity/dto/MetricsData.java b/common/src/main/java/com/usthe/common/entity/dto/MetricsData.java new file mode 100644 index 0000000..e0e8e5d --- /dev/null +++ b/common/src/main/java/com/usthe/common/entity/dto/MetricsData.java @@ -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 fields; + + @ApiModelProperty(value = "监控指标列表值集合") + private List valueRows; +} diff --git a/common/src/main/java/com/usthe/common/entity/dto/Value.java b/common/src/main/java/com/usthe/common/entity/dto/Value.java new file mode 100644 index 0000000..f0ad60c --- /dev/null +++ b/common/src/main/java/com/usthe/common/entity/dto/Value.java @@ -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; +} diff --git a/common/src/main/java/com/usthe/common/entity/dto/ValueRow.java b/common/src/main/java/com/usthe/common/entity/dto/ValueRow.java new file mode 100644 index 0000000..f758132 --- /dev/null +++ b/common/src/main/java/com/usthe/common/entity/dto/ValueRow.java @@ -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 values; +} diff --git a/warehouse/src/main/java/com/usthe/warehouse/controller/MetricsDataController.java b/warehouse/src/main/java/com/usthe/warehouse/controller/MetricsDataController.java new file mode 100644 index 0000000..ec4f3b0 --- /dev/null +++ b/warehouse/src/main/java/com/usthe/warehouse/controller/MetricsDataController.java @@ -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> 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 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 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> 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); + } +} diff --git a/warehouse/src/main/java/com/usthe/warehouse/store/RedisDataStorage.java b/warehouse/src/main/java/com/usthe/warehouse/store/RedisDataStorage.java index 9f1979b..0287697 100644 --- a/warehouse/src/main/java/com/usthe/warehouse/store/RedisDataStorage.java +++ b/warehouse/src/main/java/com/usthe/warehouse/store/RedisDataStorage.java @@ -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 commands = connection.sync(); + return commands.hget(String.valueOf(monitorId), metric); + } + private void startStorageData() { Runnable runnable = () -> { Thread.currentThread().setName("warehouse-redis-data-storage"); diff --git a/warehouse/src/main/resources/META-INF/spring.factories b/warehouse/src/main/resources/META-INF/spring.factories index d5af525..fc593ec 100644 --- a/warehouse/src/main/resources/META-INF/spring.factories +++ b/warehouse/src/main/resources/META-INF/spring.factories @@ -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 \ No newline at end of file +com.usthe.warehouse.store.RedisDataStorage,\ +com.usthe.warehouse.controller.MetricsDataController \ No newline at end of file diff --git a/web-app/package.json b/web-app/package.json index e86f60d..a18c9d8 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -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", diff --git a/web-app/src/app/app.module.ts b/web-app/src/app/app.module.ts index b20d469..00ba95c 100644 --- a/web-app/src/app/app.module.ts +++ b/web-app/src/app/app.module.ts @@ -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, diff --git a/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html b/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html index 5303fb7..596d8a8 100644 --- a/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html +++ b/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html @@ -1 +1,21 @@ -

monitor-detail works!

+ + + + + + + + + + + 监控列表 + + + + + {{'monitor.app.' + app | i18n}} 监控详情 + + + + +
diff --git a/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts b/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts index c54cc29..947dec5 100644 --- a/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts +++ b/web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts @@ -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, + }; + } } diff --git a/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts b/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts index 31cac2a..481cd57 100644 --- a/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts +++ b/web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts @@ -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) => { 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) diff --git a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html index ac72d9f..0b63061 100644 --- a/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html +++ b/web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html @@ -58,7 +58,11 @@ - {{ data.name }} + + + {{ data.name }} + + diff --git a/web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts b/web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts index eb14b9b..6bff766 100644 --- a/web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts +++ b/web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts @@ -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; diff --git a/web-app/src/app/routes/monitor/monitor.module.ts b/web-app/src/app/routes/monitor/monitor.module.ts index 52deba9..29187b6 100644 --- a/web-app/src/app/routes/monitor/monitor.module.ts +++ b/web-app/src/app/routes/monitor/monitor.module.ts @@ -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[] = [ MonitorNewComponent, @@ -26,7 +27,8 @@ const COMPONENTS: Type[] = [ NzDividerModule, NzSwitchModule, NzTagModule, - NzRadioModule + NzRadioModule, + NgxEchartsModule ], declarations: COMPONENTS, })