Просмотр исходного кода

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

tomsun28 4 лет назад
Родитель
Сommit
206408e80e

+ 34 - 0
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;
+
+}

+ 41 - 0
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<Field> fields;
+
+    @ApiModelProperty(value = "监控指标列表值集合")
+    private List<ValueRow> valueRows;
+}

+ 40 - 0
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;
+}

+ 29 - 0
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<Value> values;
+}

+ 82 - 0
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<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);
+    }
+}

+ 7 - 0
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<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");

+ 2 - 1
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
+com.usthe.warehouse.store.RedisDataStorage,\
+com.usthe.warehouse.controller.MetricsDataController

+ 3 - 0
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",

+ 5 - 1
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,

+ 21 - 1
web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html

@@ -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>

+ 74 - 2
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,
+    };
+  }
 }

+ 3 - 0
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<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)

+ 5 - 1
web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html

@@ -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>

+ 3 - 0
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;

+ 3 - 1
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<void>[] = [
   MonitorNewComponent,
@@ -26,7 +27,8 @@ const COMPONENTS: Type<void>[] = [
         NzDividerModule,
         NzSwitchModule,
         NzTagModule,
-        NzRadioModule
+        NzRadioModule,
+        NgxEchartsModule
     ],
   declarations: COMPONENTS,
 })