ref: 调整修改指标数据记录存储格式(一台主机的某一时刻下的所有指标数据统一使用json格式存储,同时不需要的tag数据会被忽略)

impl: 添加一个数据测试工具(rition-testsuite,用于从测试数据中模拟数据上传情况)
doc: 补充建表sql
todo: 前端面板立项
main
lensfrex 7 months ago
parent c2a0574aa3
commit 1e0af4208a
Signed by: lensfrex
GPG Key ID: B1E395B3C6CA0356
  1. 103
      docs/rition.sql
  2. 37
      rition-center/api/src/main/java/rition/backend/api/v1/collector/DataCollectingController.java
  3. 13
      rition-center/api/src/main/java/rition/backend/api/v1/dto/request/MetricDataRequest.java
  4. 4
      rition-center/api/src/main/java/rition/backend/api/v1/dto/request/MetricDataUploadRequest.java
  5. 18
      rition-center/api/src/main/java/rition/backend/api/v1/panel/MetricsViewController.java
  6. 10
      rition-center/api/src/main/java/rition/backend/api/v1/panel/PanelController.java
  7. 2
      rition-center/api/src/main/java/rition/backend/service/MetricDataCollectingService.java
  8. 2
      rition-center/api/src/main/resources/application.yml
  9. 11
      rition-center/common/pom.xml
  10. 31
      rition-center/common/src/main/java/rition/common/data/dto/MetricDataDto.java
  11. 27
      rition-center/common/src/main/java/rition/common/data/entity/MetricRecordEntity.java
  12. 36
      rition-center/pom.xml
  13. 34
      rition-center/service/collector/pom.xml
  14. 56
      rition-center/service/collector/src/main/java/rition/service/collector/MetricCollectingService.java
  15. 2
      rition-center/service/collector/src/main/java/rition/service/collector/configure/CollectorServiceKafkaConfigure.java
  16. 2
      rition-center/service/collector/src/main/java/rition/service/collector/dao/mapper/MetricRecordMapper.java
  17. 3
      rition-center/service/monitor/pom.xml
  18. 14
      rition-center/service/monitor/src/main/java/rition/service/monitor/MonitorService.java
  19. 25
      rition-center/service/monitor/src/main/java/rition/service/monitor/configure/MonitorServiceKafkaConfigure.java
  20. 31
      rition-center/service/monitor/src/main/resources/application-monitor.yml
  21. 23
      rition-center/service/panel/pom.xml
  22. 7
      rition-center/service/panel/src/main/java/rition/service/panel/PanelService.java
  23. 2
      rition-center/service/pom.xml
  24. 9
      rition-panel/.editorconfig
  25. 7
      rition-panel/.eslintignore
  26. 65
      rition-panel/.eslintrc.cjs
  27. 37
      rition-panel/.gitignore
  28. 5
      rition-panel/.npmrc
  29. 4
      rition-panel/README.md
  30. 24
      rition-panel/index.html
  31. 39
      rition-panel/jsconfig.json
  32. 35
      rition-panel/package.json
  33. 27
      rition-panel/postcss.config.cjs
  34. BIN
      rition-panel/public/favicon.ico
  35. BIN
      rition-panel/public/icons/favicon-128x128.png
  36. BIN
      rition-panel/public/icons/favicon-16x16.png
  37. BIN
      rition-panel/public/icons/favicon-32x32.png
  38. BIN
      rition-panel/public/icons/favicon-96x96.png
  39. 1
      rition-panel/public/vite.svg
  40. 207
      rition-panel/quasar.config.js
  41. 33
      rition-panel/src/App.vue
  42. 15
      rition-panel/src/assets/quasar-logo-vertical.svg
  43. 1
      rition-panel/src/assets/vue.svg
  44. 0
      rition-panel/src/boot/.gitkeep
  45. 24
      rition-panel/src/boot/axios.js
  46. 48
      rition-panel/src/components/EssentialLink.vue
  47. 40
      rition-panel/src/components/HelloWorld.vue
  48. 1
      rition-panel/src/css/app.scss
  49. 25
      rition-panel/src/css/quasar.variables.scss
  50. 106
      rition-panel/src/layouts/MainLayout.vue
  51. 5
      rition-panel/src/main.js
  52. 29
      rition-panel/src/pages/ErrorNotFound.vue
  53. 15
      rition-panel/src/pages/IndexPage.vue
  54. 30
      rition-panel/src/router/index.js
  55. 18
      rition-panel/src/router/routes.js
  56. 79
      rition-panel/src/style.css
  57. 7
      rition-panel/vite.config.js
  58. 3417
      rition-panel/yarn.lock
  59. 15
      rition-testsuite/client/client.go
  60. 54
      rition-testsuite/client/http.go
  61. 5
      rition-testsuite/go.mod
  62. 2
      rition-testsuite/go.sum
  63. 28
      rition-testsuite/main.go
  64. 69
      rition-testsuite/service/probe.go
  65. 27
      rition-testsuite/service/service.go

@ -0,0 +1,103 @@
/*
Navicat Premium Data Transfer
Source Server : mysql
Source Server Type : MySQL
Source Server Version : 80027 (8.0.27)
Source Host : 127.0.0.1:3306
Source Schema : rition
Target Server Type : MySQL
Target Server Version : 80027 (8.0.27)
File Encoding : 65001
Date: 08/05/2024 10:15:09
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for alert
-- ----------------------------
DROP TABLE IF EXISTS `alert`;
CREATE TABLE `alert` (
`id` bigint NOT NULL,
`instance_id` varchar(64) NOT NULL COMMENT '出现警告的实例id',
`rule` bigint NOT NULL COMMENT '触发的规则',
`time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '警告出现的时间',
`status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_instance` (`instance_id`),
KEY `idx_rule` (`rule`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Table structure for contract
-- ----------------------------
DROP TABLE IF EXISTS `contract`;
CREATE TABLE `contract` (
`id` bigint NOT NULL,
`instance_id` varchar(64) NOT NULL COMMENT '绑定的实例',
`contract` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '联系方式',
`type` tinyint NOT NULL COMMENT '联系方式类型',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_id` (`id`),
KEY `idx_instance` (`instance_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Table structure for ecs
-- ----------------------------
DROP TABLE IF EXISTS `ecs`;
CREATE TABLE `ecs` (
`id` varchar(128) NOT NULL COMMENT '实例id',
`name` varchar(255) NOT NULL COMMENT '主机名称',
`ip` varchar(15) NOT NULL COMMENT '主机绑定分配的ip',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_id` (`id`),
KEY `idx_ip` (`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Table structure for record
-- ----------------------------
DROP TABLE IF EXISTS `record`;
CREATE TABLE `record` (
`id` bigint NOT NULL,
`instance_id` varchar(64) NOT NULL COMMENT '实例id',
`metric_data` json NOT NULL COMMENT '监控指标值,使用json格式存储',
`time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '数据时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_id` (`id`),
KEY `idx_time` (`time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- ----------------------------
-- Table structure for rule
-- ----------------------------
DROP TABLE IF EXISTS `rule`;
CREATE TABLE `rule` (
`id` bigint NOT NULL COMMENT '规则id',
`instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '规则对应的实例id',
`expression` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '需要计算的指标项或者表达式',
`condition` tinyint NOT NULL COMMENT '触发条件',
`threshold` varchar(32) NOT NULL COMMENT '阈值',
`trigger` tinyint NOT NULL COMMENT '触发方法,实时计算或定时计算',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '规则描述',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_instance` (`instance_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SET FOREIGN_KEY_CHECKS = 1;

@ -1,4 +1,4 @@
package rition.backend.api.v1; package rition.backend.api.v1.collector;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -11,7 +11,6 @@ import rition.backend.api.v1.dto.response.Response;
import rition.backend.service.MetricDataCollectingService; import rition.backend.service.MetricDataCollectingService;
import rition.common.data.dto.MetricDataDto; import rition.common.data.dto.MetricDataDto;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -26,26 +25,34 @@ public class DataCollectingController {
this.metricDataCollectingService = metricDataCollectingService; this.metricDataCollectingService = metricDataCollectingService;
} }
private static final int METRIC_NUMS = 22;
@PostMapping("/put") @PostMapping("/put")
@WithRequestIdResponse @WithRequestIdResponse
public Response<Object> putData(@RequestBody List<MetricDataUploadRequest> uploadDataList, public Response<Object> putData(@RequestBody List<MetricDataUploadRequest> uploadDataList,
@RequestId String requestId) { @RequestId String requestId) {
// dto转换,按时间组装数据 // dto转换,按时间组装数据,timestamp->instanceId->metricDataDto映射
Map<Long, MetricDataDto> metricDataTimestampMap = new HashMap<>(uploadDataList.size() / 20); Map<Long, Map<String, MetricDataDto>> metricDataTimestampMap = new HashMap<>(uploadDataList.size() / METRIC_NUMS);
for (MetricDataUploadRequest uploadData : uploadDataList) { for (MetricDataUploadRequest uploadDataItem : uploadDataList) {
MetricDataDto dataList = metricDataTimestampMap.get(uploadData.getTimestamp());
if (dataList == null) { // instanceId -> MetricDataDto
dataList = MetricDataDto.builder().dataList(new ArrayList<>(20)).build(); var instanceId = uploadDataItem.getTags().get("instanceId");
metricDataTimestampMap.put(uploadData.getTimestamp(), dataList); Map<String, MetricDataDto> instanceMetricDataMap =
} metricDataTimestampMap.computeIfAbsent(
uploadDataItem.getTimestamp(),
key -> new HashMap<>()
);
dataList.getDataList().add(MetricDataDto.MetricData.builder() // metricName->metricData,多余的tag信息就不要了(毕竟用不到)
.metric(uploadData.getMetric()) MetricDataDto instanceMetricData = instanceMetricDataMap.computeIfAbsent(
.value(uploadData.getValue()) instanceId,
.timestamp(uploadData.getTimestamp()) id -> MetricDataDto.builder()
.tags(uploadData.getTags()) .timestamp(uploadDataItem.getTimestamp()).instanceId(id)
.data(new HashMap<>(METRIC_NUMS))
.build() .build()
); );
instanceMetricData.getData().put(uploadDataItem.getMetric(), uploadDataItem.getValue());
} }
metricDataCollectingService.receiveData(requestId, metricDataTimestampMap); metricDataCollectingService.receiveData(requestId, metricDataTimestampMap);

@ -0,0 +1,13 @@
package rition.backend.api.v1.dto.request;
import lombok.Data;
import java.util.List;
@Data
public class MetricDataRequest {
private String instanceId;
private Long start;
private Long end;
private List<String> metricItems;
}

@ -23,7 +23,7 @@ public class MetricDataUploadRequest {
private Long timestamp; private Long timestamp;
/** /**
* * 统一转为double数值
*/ */
private String value; private Double value;
} }

@ -0,0 +1,18 @@
package rition.backend.api.v1.panel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import rition.backend.api.v1.dto.request.MetricDataRequest;
import rition.backend.api.v1.dto.response.Response;
@RestController
@RequestMapping("/panel/metrics")
public class MetricsViewController {
@GetMapping("/list")
public Response<Object> getMetrics(@RequestBody MetricDataRequest metricDataRequest) {
return Response.success();
}
}

@ -0,0 +1,10 @@
package rition.backend.api.v1.panel;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/panel")
public class PanelController {
}

@ -40,7 +40,7 @@ public class MetricDataCollectingService {
* @param collectedMetricData 监控指标数据protobuf对象 * @param collectedMetricData 监控指标数据protobuf对象
*/ */
@Async @Async
public void receiveData(String requestId, Map<Long, MetricDataDto> collectedMetricData) { public void receiveData(String requestId, Map<Long, Map<String, MetricDataDto>> collectedMetricData) {
RequestProcessTraceRecord record; RequestProcessTraceRecord record;
try { try {
var start = System.currentTimeMillis(); var start = System.currentTimeMillis();

@ -5,7 +5,7 @@ server:
spring: spring:
profiles: profiles:
include: collector include: collector, monitor
data: data:
redis: redis:
host: ${REDIS_HOST:127.0.0.1} host: ${REDIS_HOST:127.0.0.1}

@ -21,6 +21,17 @@
<dependencies> <dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -6,7 +6,6 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map; import java.util.Map;
@Data @Data
@ -15,34 +14,8 @@ import java.util.Map;
@AllArgsConstructor @AllArgsConstructor
public class MetricDataDto { public class MetricDataDto {
private List<MetricData> dataList; private String instanceId;
/**
* 指标数据
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class MetricData {
/**
* 指标名
*/
private String metric;
/**
* 数据标签
*/
private Map<String, String> tags;
/**
* 时间戳
*/
private Long timestamp; private Long timestamp;
/** private Map<String, Double> data;
*
*/
private String value;
}
} }

@ -1,18 +1,24 @@
package rition.service.collector.dao.entity; package rition.common.data.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.time.Instant; import java.time.Instant;
import java.util.Map;
/**
* 指标数据实体类一条为完整的指标数据
*/
@Data @Data
@Builder @Builder
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@TableName("rition_record") @TableName(value = "record", autoResultMap = true)
public class MetricRecordEntity { public class MetricRecordEntity {
/** /**
@ -26,14 +32,10 @@ public class MetricRecordEntity {
private String instanceId; private String instanceId;
/** /**
* 数据项 * 监控指标值使用json格式存储
*/ */
private String item; @TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Double> metricData;
/**
* 数据值统一使用字符串存储
*/
private String value;
/** /**
* create_time * create_time
@ -49,11 +51,4 @@ public class MetricRecordEntity {
* status * status
*/ */
private Integer status; private Integer status;
public static class RecordValueTypes {
public static final int STRING = 0;
public static final int INTEGER = 1;
public static final int FLOAT = 2;
public static final int BOOLEAN = 3;
}
} }

@ -29,6 +29,10 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<idgen.version>1.0.6</idgen.version> <idgen.version>1.0.6</idgen.version>
<mybatis-sb-starter.version>3.0.3</mybatis-sb-starter.version>
<mybatis.version>3.5.16</mybatis.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
</properties> </properties>
<dependencies> <dependencies>
@ -73,6 +77,38 @@
<artifactId>yitter-idgenerator</artifactId> <artifactId>yitter-idgenerator</artifactId>
<version>${idgen.version}</version> <version>${idgen.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-sb-starter.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>${mybatis-sb-starter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

@ -16,42 +16,8 @@
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis-sb-starter.version>3.0.3</mybatis-sb-starter.version>
<mybatis.version>3.5.16</mybatis.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-sb-starter.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>${mybatis-sb-starter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

@ -5,9 +5,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import rition.common.data.dto.MetricDataDto; import rition.common.data.dto.MetricDataDto;
import rition.common.data.enums.CommonEntityStatus; import rition.common.data.entity.MetricRecordEntity;
import rition.service.collector.configure.KafkaConfigure; import rition.service.collector.configure.CollectorServiceKafkaConfigure;
import rition.service.collector.dao.entity.MetricRecordEntity;
import rition.service.collector.dao.mapper.MetricRecordMapper; import rition.service.collector.dao.mapper.MetricRecordMapper;
import java.time.Instant; import java.time.Instant;
@ -28,33 +27,46 @@ public class MetricCollectingService {
private final MetricRecordMapper metricRecordMapper; private final MetricRecordMapper metricRecordMapper;
public MetricCollectingService(KafkaTemplate<String, MetricDataDto> kafkaTemplate, public MetricCollectingService(KafkaTemplate<String, MetricDataDto> kafkaTemplate,
KafkaConfigure kafkaConfigure, MetricRecordMapper metricRecordMapper) { CollectorServiceKafkaConfigure collectorServiceKafkaConfigure,
MetricRecordMapper metricRecordMapper) {
this.kafkaTemplate = kafkaTemplate; this.kafkaTemplate = kafkaTemplate;
this.collectedDataTopic = kafkaConfigure.getDataCollecting().getTopic(); this.collectedDataTopic = collectorServiceKafkaConfigure.getDataCollecting().getTopic();
this.metricRecordMapper = metricRecordMapper; this.metricRecordMapper = metricRecordMapper;
} }
// 目前设定为40轮作为一个批次 private static final int METRIC_NUMS = 22;
private static final int DEFAULT_DB_BATCH_SAVE_TURN = 40;
// 目前设定为50组数据作为一个批次
private static final int DEFAULT_DB_BATCH_SIZE = 50;
/** /**
* 接收处理好的监控指标数据以时间戳为分组入库和发送mq * 接收处理好的监控指标数据以时间戳为分组入库和发送mq
* *
* @param collectedMetricData 监控指标数据以时间戳为组 * @param collectedMetricData 监控指标数据以时间戳为组
*/ */
public void receiveData(Map<Long, MetricDataDto> collectedMetricData) { public void receiveData(Map<Long, Map<String, MetricDataDto>> collectedMetricData) {
List<MetricRecordEntity> batchSaveEntityList = new ArrayList<>(DEFAULT_DB_BATCH_SAVE_TURN * 23); List<MetricRecordEntity> batchSaveEntityList = new ArrayList<>(DEFAULT_DB_BATCH_SIZE);
int turn = 0; int turn = 0;
for (Long timestamp : collectedMetricData.keySet()) { for (Long timestamp : collectedMetricData.keySet()) {
var metricDataDto = collectedMetricData.get(timestamp); Map<String, MetricDataDto> instanceMetricData = collectedMetricData.get(timestamp);
var metricDataList = metricDataDto.getDataList(); for (String instanceId : instanceMetricData.keySet()) {
var metricRecordEntityList = metricDataList.stream().map(this::convert).toList(); MetricDataDto metricDataDto = instanceMetricData.get(instanceId);
MetricRecordEntity entity = new MetricRecordEntity();
entity.setId(YitIdHelper.nextId());
entity.setInstanceId(instanceId);
entity.setMetricData(metricDataDto.getData());
var time = Instant.ofEpochSecond(metricDataDto.getTimestamp());
entity.setTime(time);
entity.setUpdateTime(time);
entity.setStatus(0);
// kafka发布
kafkaTemplate.send(this.collectedDataTopic, metricDataDto); kafkaTemplate.send(this.collectedDataTopic, metricDataDto);
// 分批入库 // 分批入库
batchSaveEntityList.addAll(metricRecordEntityList); batchSaveEntityList.add(entity);
if (turn >= DEFAULT_DB_BATCH_SAVE_TURN) { if (turn >= DEFAULT_DB_BATCH_SIZE) {
metricRecordMapper.insertBatchSomeColumn(batchSaveEntityList); metricRecordMapper.insertBatchSomeColumn(batchSaveEntityList);
// 下一轮 // 下一轮
batchSaveEntityList.clear(); batchSaveEntityList.clear();
@ -63,25 +75,11 @@ public class MetricCollectingService {
turn++; turn++;
} }
} }
}
// 还有没入库的 // 还有没入库的
if (!batchSaveEntityList.isEmpty()) { if (!batchSaveEntityList.isEmpty()) {
metricRecordMapper.insertBatchSomeColumn(batchSaveEntityList); metricRecordMapper.insertBatchSomeColumn(batchSaveEntityList);
} }
} }
private MetricRecordEntity convert(MetricDataDto.MetricData metricData) {
MetricRecordEntity entity = new MetricRecordEntity();
entity.setId(YitIdHelper.nextId());
entity.setInstanceId(metricData.getTags().get("instanceId"));
entity.setItem(metricData.getMetric());
entity.setValue(metricData.getValue());
entity.setStatus(CommonEntityStatus.STATUS_NORMAL.getValue());
var time = Instant.ofEpochSecond(metricData.getTimestamp());
entity.setTime(time);
entity.setUpdateTime(time);
return entity;
}
} }

@ -7,7 +7,7 @@ import org.springframework.context.annotation.Configuration;
@Data @Data
@Configuration @Configuration
@ConfigurationProperties("rition.kafka") @ConfigurationProperties("rition.kafka")
public class KafkaConfigure { public class CollectorServiceKafkaConfigure {
private DataCollectingKafkaConfig dataCollecting; private DataCollectingKafkaConfig dataCollecting;
@Data @Data

@ -2,7 +2,7 @@ package rition.service.collector.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import rition.service.collector.dao.entity.MetricRecordEntity; import rition.common.data.entity.MetricRecordEntity;
import java.util.List; import java.util.List;

@ -17,4 +17,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies>
</dependencies>
</project> </project>

@ -1,4 +1,18 @@
package rition.service.monitor; package rition.service.monitor;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
import rition.common.data.dto.MetricDataDto;
@Service
public class MonitorService { public class MonitorService {
@KafkaListener(
topics = {"${rition.kafka.data-collecting.topic}"},
groupId = "${rition.kafka.data-collecting.group}"
)
public void processMetricData(ConsumerRecord<String, MetricDataDto> metricDataConsumerRecord) {
}
} }

@ -0,0 +1,25 @@
package rition.service.monitor.configure;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties("rition.kafka")
public class MonitorServiceKafkaConfigure {
private DataCollectingKafkaConfig dataCollecting;
private AlertMessageKafkaConfig alertMessageKafkaConfig;
@Data
public static class DataCollectingKafkaConfig {
private String topic;
private String group;
}
@Data
public static class AlertMessageKafkaConfig {
private String topic;
private String group;
}
}

@ -0,0 +1,31 @@
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/rition?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
username: root
password: Test2333!
kafka:
bootstrap-servers: '127.0.0.1:9092'
consumer:
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring:
json:
trusted:
packages: "rition.common.data.dto"
producer:
retries: 4
compression-type: zstd
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
acks: 1
properties:
linger:
ms: 200
rition:
kafka:
data-collecting:
topic: 'ecs-metric-data-topic'
group: 'ecs-metric-data-group'

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.rition</groupId>
<artifactId>service</artifactId>
<version>${revision}</version>
</parent>
<artifactId>panel</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
</dependencies>
</project>

@ -0,0 +1,7 @@
package rition.service.panel;
import org.springframework.stereotype.Service;
@Service
public class PanelService {
}

@ -15,12 +15,14 @@
<module>collector</module> <module>collector</module>
<module>monitor</module> <module>monitor</module>
<module>notify</module> <module>notify</module>
<module>panel</module>
</modules> </modules>
<properties> <properties>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies> <dependencies>

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

@ -0,0 +1,7 @@
/dist
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.cjs
/quasar.config.*.temporary.compiled*

@ -0,0 +1,65 @@
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
parserOptions: {
ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features
},
env: {
node: true,
browser: true
},
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
// 'eslint:recommended',
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
// https://github.com/prettier/eslint-config-prettier#installation
// usage with Prettier, provided by 'eslint-config-prettier'.
'prettier'
],
plugins: [
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue',
// https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674
// Prettier has not been included as plugin to avoid performance impact
// add it as an extension for your IDE
],
globals: {
ga: 'readonly', // Google Analytics
cordova: 'readonly',
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly'
},
// add your custom rules here
rules: {
'prefer-promise-reject-errors': 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}

@ -1,24 +1,33 @@
# Logs .DS_Store
logs .thumbs.db
*.log node_modules
# Quasar core related directories
.quasar
/dist
/quasar.config.*.temporary.compiled*
# Cordova related directories and files
/src-cordova/node_modules
/src-cordova/platforms
/src-cordova/plugins
/src-cordova/www
# Capacitor related directories and files
/src-capacitor/www
/src-capacitor/node_modules
# Log files
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files # Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea .idea
.DS_Store
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.sw?
# local .env files
.env.local*

@ -0,0 +1,5 @@
# pnpm-related options
shamefully-hoist=true
strict-peer-dependencies=false
# to get the latest compatible packages when creating the project https://github.com/pnpm/pnpm/issues/6463
resolution-mode=highest

@ -1,3 +1,3 @@
# rition-panel # RitionPanel
rition的面板 Panel of Rition project

@ -1,13 +1,21 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html>
<head> <head>
<meta charset="UTF-8" /> <title><%= productName %></title>
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta charset="utf-8">
<title>Vite + Vue</title> <meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
</head> </head>
<body> <body>
<div id="app"></div> <!-- quasar:entry-point -->
<script type="module" src="/src/main.js"></script>
</body> </body>
</html> </html>

@ -0,0 +1,39 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": [
"src/*"
],
"app/*": [
"*"
],
"components/*": [
"src/components/*"
],
"layouts/*": [
"src/layouts/*"
],
"pages/*": [
"src/pages/*"
],
"assets/*": [
"src/assets/*"
],
"boot/*": [
"src/boot/*"
],
"stores/*": [
"src/stores/*"
],
"vue$": [
"node_modules/vue/dist/vue.runtime.esm-bundler.js"
]
}
},
"exclude": [
"dist",
".quasar",
"node_modules"
]
}

@ -1,18 +1,37 @@
{ {
"name": "rition-panel", "name": "rition-panel",
"version": "0.0.1",
"description": "Panel of Rition project",
"productName": "RitionPanel",
"author": "lensfrex <lensferno@outlook.com>",
"private": true, "private": true,
"version": "0.0.0",
"type": "module",
"scripts": { "scripts": {
"dev": "vite", "lint": "eslint --ext .js,.vue ./",
"build": "vite build", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"preview": "vite preview" "test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev",
"build": "quasar build"
}, },
"dependencies": { "dependencies": {
"vue": "^3.4.21" "axios": "^1.2.1",
"@quasar/extras": "^1.16.4",
"quasar": "^2.16.0",
"vue": "^3.4.18",
"vue-router": "^4.0.12"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.4", "eslint": "^8.57.0",
"vite": "^5.2.0" "eslint-plugin-vue": "^9.0.0",
"vite-plugin-checker": "^0.6.4",
"eslint-config-prettier": "^8.1.0",
"prettier": "^2.5.1",
"@quasar/app-vite": "^1.9.0",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.14"
},
"engines": {
"node": "^20 || ^18 || ^16",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
} }
} }

@ -0,0 +1,27 @@
/* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: [
// https://github.com/postcss/autoprefixer
require('autoprefixer')({
overrideBrowserslist: [
'last 4 Chrome versions',
'last 4 Firefox versions',
'last 4 Edge versions',
'last 4 Safari versions',
'last 4 Android versions',
'last 4 ChromeAndroid versions',
'last 4 FirefoxAndroid versions',
'last 4 iOS versions'
]
})
// https://github.com/elchininet/postcss-rtlcss
// If you want to support RTL css, then
// 1. yarn/npm install postcss-rtlcss
// 2. optionally set quasar.config.js > framework > lang to an RTL language
// 3. uncomment the following line:
// require('postcss-rtlcss')
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,207 @@
/* eslint-env node */
/*
* This file runs in a Node context (it's NOT transpiled by Babel), so use only
* the ES6 features that are supported by your Node version. https://node.green/
*/
// Configuration for your app
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
const { configure } = require('quasar/wrappers');
module.exports = configure(function (/* ctx */) {
return {
// https://v2.quasar.dev/quasar-cli-vite/prefetch-feature
// preFetch: true,
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files
boot: [
'axios',
],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: [
'app.scss'
],
// https://github.com/quasarframework/quasar/tree/dev/extras
extras: [
// 'ionicons-v4',
// 'mdi-v7',
// 'fontawesome-v6',
// 'eva-icons',
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it
'material-icons', // optional, you are not bound to it
],
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#build
build: {
target: {
browser: [ 'es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1' ],
node: 'node20'
},
vueRouterMode: 'hash', // available values: 'hash', 'history'
// vueRouterBase,
// vueDevtools,
// vueOptionsAPI: false,
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
// publicPath: '/',
// analyze: true,
// env: {},
// rawDefine: {}
// ignorePublicFolder: true,
// minify: false,
// polyfillModulePreload: true,
// distDir
// extendViteConf (viteConf) {},
// viteVuePluginOptions: {},
vitePlugins: [
['vite-plugin-checker', {
eslint: {
lintCommand: 'eslint "./**/*.{js,mjs,cjs,vue}"'
}
}, { server: false }]
]
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
// https: true
open: true // opens browser window automatically
},
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
framework: {
config: {},
// iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack
// For special cases outside of where the auto-import strategy can have an impact
// (like functional components as one of the examples),
// you can manually specify Quasar components/directives to be available everywhere:
//
// components: [],
// directives: [],
// Quasar plugins
plugins: []
},
// animations: 'all', // --- includes all animations
// https://v2.quasar.dev/options/animations
animations: [],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#property-sourcefiles
// sourceFiles: {
// rootComponent: 'src/App.vue',
// router: 'src/router/index',
// store: 'src/store/index',
// registerServiceWorker: 'src-pwa/register-service-worker',
// serviceWorker: 'src-pwa/custom-service-worker',
// pwaManifestFile: 'src-pwa/manifest.json',
// electronMain: 'src-electron/electron-main',
// electronPreload: 'src-electron/electron-preload'
// },
// https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr
ssr: {
// ssrPwaHtmlFilename: 'offline.html', // do NOT use index.html as name!
// will mess up SSR
// extendSSRWebserverConf (esbuildConf) {},
// extendPackageJson (json) {},
pwa: false,
// manualStoreHydration: true,
// manualPostHydrationTrigger: true,
prodPort: 3000, // The default port that the production server should use
// (gets superseded if process.env.PORT is specified at runtime)
middlewares: [
'render' // keep this as last one
]
},
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
pwa: {
workboxMode: 'generateSW', // or 'injectManifest'
injectPwaMetaTags: true,
swFilename: 'sw.js',
manifestFilename: 'manifest.json',
useCredentialsForManifestTag: false,
// useFilenameHashes: true,
// extendGenerateSWOptions (cfg) {}
// extendInjectManifestOptions (cfg) {},
// extendManifestJson (json) {}
// extendPWACustomSWConf (esbuildConf) {}
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova
cordova: {
// noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
electron: {
// extendElectronMainConf (esbuildConf)
// extendElectronPreloadConf (esbuildConf)
// specify the debugging port to use for the Electron app when running in development mode
inspectPort: 5858,
bundler: 'packager', // 'packager' or 'builder'
packager: {
// https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options
// OS X / Mac App Store
// appBundleId: '',
// appCategoryType: '',
// osxSign: '',
// protocol: 'myapp://path',
// Windows only
// win32metadata: { ... }
},
builder: {
// https://www.electron.build/configuration/configuration
appId: 'rition-panel'
}
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
bex: {
contentScripts: [
'my-content-script'
],
// extendBexScriptsConf (esbuildConf) {}
// extendBexManifestJson (json) {}
}
}
});

@ -1,30 +1,9 @@
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template> <template>
<div> <router-view />
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template> </template>
<style scoped> <script setup>
.logo { defineOptions({
height: 6em; name: 'App'
padding: 1.5em; });
will-change: filter; </script>
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 356 360">
<path
d="M43.4 303.4c0 3.8-2.3 6.3-7.1 6.3h-15v-22h14.4c4.3 0 6.2 2.2 6.2 5.2 0 2.6-1.5 4.4-3.4 5 2.8.4 4.9 2.5 4.9 5.5zm-8-13H24.1v6.9H35c2.1 0 4-1.3 4-3.8 0-2.2-1.3-3.1-3.7-3.1zm5.1 12.6c0-2.3-1.8-3.7-4-3.7H24.2v7.7h11.7c3.4 0 4.6-1.8 4.6-4zm36.3 4v2.7H56v-22h20.6v2.7H58.9v6.8h14.6v2.3H58.9v7.5h17.9zm23-5.8v8.5H97v-8.5l-11-13.4h3.4l8.9 11 8.8-11h3.4l-10.8 13.4zm19.1-1.8V298c0-7.9 5.2-10.7 12.7-10.7 7.5 0 13 2.8 13 10.7v1.4c0 7.9-5.5 10.8-13 10.8s-12.7-3-12.7-10.8zm22.7 0V298c0-5.7-3.9-8-10-8-6 0-9.8 2.3-9.8 8v1.4c0 5.8 3.8 8.1 9.8 8.1 6 0 10-2.3 10-8.1zm37.2-11.6v21.9h-2.9l-15.8-17.9v17.9h-2.8v-22h3l15.6 18v-18h2.9zm37.9 10.2v1.3c0 7.8-5.2 10.4-12.4 10.4H193v-22h11.2c7.2 0 12.4 2.8 12.4 10.3zm-3 0c0-5.3-3.3-7.6-9.4-7.6h-8.4V307h8.4c6 0 9.5-2 9.5-7.7V298zm50.8-7.6h-9.7v19.3h-3v-19.3h-9.7v-2.6h22.4v2.6zm34.4-2.6v21.9h-3v-10.1h-16.8v10h-2.8v-21.8h2.8v9.2H296v-9.2h2.9zm34.9 19.2v2.7h-20.7v-22h20.6v2.7H316v6.8h14.5v2.3H316v7.5h17.8zM24 340.2v7.3h13.9v2.4h-14v9.6H21v-22h20v2.7H24zm41.5 11.4h-9.8v7.9H53v-22h13.3c5.1 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6H66c3.1 0 5.3-1.5 5.3-4.7 0-3.3-2.2-4.1-5.3-4.1H55.7v8.8zm47.9 6.2H89l-2 4.3h-3.2l10.7-22.2H98l10.7 22.2h-3.2l-2-4.3zm-1-2.3l-6.3-13-6 13h12.2zm46.3-15.3v21.9H146v-17.2L135.7 358h-2.1l-10.2-15.6v17h-2.8v-21.8h3l11 16.9 11.3-17h3zm35 19.3v2.6h-20.7v-22h20.6v2.7H166v6.8h14.5v2.3H166v7.6h17.8zm47-19.3l-8.3 22h-3l-7.1-18.6-7 18.6h-3l-8.2-22h3.3L204 356l6.8-18.5h3.4L221 356l6.6-18.5h3.3zm10 11.6v-1.4c0-7.8 5.2-10.7 12.7-10.7 7.6 0 13 2.9 13 10.7v1.4c0 7.9-5.4 10.8-13 10.8-7.5 0-12.7-3-12.7-10.8zm22.8 0v-1.4c0-5.7-4-8-10-8s-9.9 2.3-9.9 8v1.4c0 5.8 3.8 8.2 9.8 8.2 6.1 0 10-2.4 10-8.2zm28.3 2.4h-9.8v7.9h-2.8v-22h13.2c5.2 0 8 1.9 8 6.8 0 3.7-2 6.3-5.6 7l6 8.2h-3.3l-5.8-8zm-9.8-2.6h10.2c3 0 5.2-1.5 5.2-4.7 0-3.3-2.1-4.1-5.2-4.1h-10.2v8.8zm40.3-1.5l-6.8 5.6v6.4h-2.9v-22h2.9v12.3l15.2-12.2h3.7l-9.9 8.1 10.3 13.8h-3.6l-8.9-12z" />
<path fill="#050A14"
d="M188.4 71.7a10.4 10.4 0 01-20.8 0 10.4 10.4 0 1120.8 0zM224.2 45c-2.2-3.9-5-7.5-8.2-10.7l-12 7c-3.7-3.2-8-5.7-12.6-7.3a49.4 49.4 0 00-9.7 13.9 59 59 0 0140.1 14l7.6-4.4a57 57 0 00-5.2-12.5zM178 125.1c4.5 0 9-.6 13.4-1.7v-14a40 40 0 0012.5-7.2 47.7 47.7 0 00-7.1-15.3 59 59 0 01-32.2 27.7v8.7c4.4 1.2 8.9 1.8 13.4 1.8zM131.8 45c-2.3 4-4 8.1-5.2 12.5l12 7a40 40 0 000 14.4c5.7 1.5 11.3 2 16.9 1.5a59 59 0 01-8-41.7l-7.5-4.3c-3.2 3.2-6 6.7-8.2 10.6z" />
<path fill="#00B4FF"
d="M224.2 98.4c2.3-3.9 4-8 5.2-12.4l-12-7a40 40 0 000-14.5c-5.7-1.5-11.3-2-16.9-1.5a59 59 0 018 41.7l7.5 4.4c3.2-3.2 6-6.8 8.2-10.7zm-92.4 0c2.2 4 5 7.5 8.2 10.7l12-7a40 40 0 0012.6 7.3c4-4.1 7.3-8.8 9.7-13.8a59 59 0 01-40-14l-7.7 4.4c1.2 4.3 3 8.5 5.2 12.4zm46.2-80c-4.5 0-9 .5-13.4 1.7V34a40 40 0 00-12.5 7.2c1.5 5.7 4 10.8 7.1 15.4a59 59 0 0132.2-27.7V20a53.3 53.3 0 00-13.4-1.8z" />
<path fill="#00B4FF"
d="M178 9.2a62.6 62.6 0 11-.1 125.2A62.6 62.6 0 01178 9.2m0-9.2a71.7 71.7 0 100 143.5A71.7 71.7 0 00178 0z" />
<path fill="#050A14"
d="M96.6 212v4.3c-9.2-.8-15.4-5.8-15.4-17.8V180h4.6v18.4c0 8.6 4 12.6 10.8 13.5zm16-31.9v18.4c0 8.9-4.3 12.8-10.9 13.5v4.4c9.2-.7 15.5-5.6 15.5-18v-18.3h-4.7zM62.2 199v-2.2c0-12.7-8.8-17.4-21-17.4-12.1 0-20.7 4.7-20.7 17.4v2.2c0 12.8 8.6 17.6 20.7 17.6 1.5 0 3-.1 4.4-.3l11.8 6.2 2-3.3-8.2-4-6.4-3.1a32 32 0 01-3.6.2c-9.8 0-16-3.9-16-13.3v-2.2c0-9.3 6.2-13.1 16-13.1 9.9 0 16.3 3.8 16.3 13.1v2.2c0 5.3-2.1 8.7-5.6 10.8l4.8 2.4c3.4-2.8 5.5-7 5.5-13.2zM168 215.6h5.1L156 179.7h-4.8l17 36zM143 205l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.8-3.7H143zm133.7 10.7h5.2l-17.3-35.9h-4.8l17 36zm-25-10.7l7.4-15.7-2.4-5-15.1 31.4h5.1l3.3-7h18.3l-1.7-3.7h-14.8zm73.8-2.5c6-1.2 9-5.4 9-11.4 0-8-4.5-10.9-12.9-10.9h-21.4v35.5h4.6v-31.3h16.5c5 0 8.5 1.4 8.5 6.7 0 5.2-3.5 7.7-8.5 7.7h-11.4v4.1h10.7l9.3 12.8h5.5l-9.9-13.2zm-117.4 9.9c-9.7 0-14.7-2.5-18.6-6.3l-2.2 3.8c5.1 5 11 6.7 21 6.7 1.6 0 3.1-.1 4.6-.3l-1.9-4h-3zm18.4-7c0-6.4-4.7-8.6-13.8-9.4l-10.1-1c-6.7-.7-9.3-2.2-9.3-5.6 0-2.5 1.4-4 4.6-5l-1.8-3.8c-4.7 1.4-7.5 4.2-7.5 8.9 0 5.2 3.4 8.7 13 9.6l11.3 1.2c6.4.6 8.9 2 8.9 5.4 0 2.7-2.1 4.7-6 5.8l1.8 3.9c5.3-1.6 8.9-4.7 8.9-10zm-20.3-21.9c7.9 0 13.3 1.8 18.1 5.7l1.8-3.9a30 30 0 00-19.6-5.9c-2 0-4 .1-5.7.3l1.9 4 3.5-.2z" />
<path fill="#00B4FF"
d="M.5 251.9c29.6-.5 59.2-.8 88.8-1l88.7-.3 88.7.3 44.4.4 44.4.6-44.4.6-44.4.4-88.7.3-88.7-.3a7981 7981 0 01-88.8-1z" />
<path fill="none" d="M-565.2 324H-252v15.8h-313.2z" />
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

@ -0,0 +1,24 @@
import { boot } from 'quasar/wrappers'
import axios from 'axios'
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: 'https://api.example.com' })
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
})
export { api }

@ -0,0 +1,48 @@
<template>
<q-item
clickable
tag="a"
target="_blank"
:href="props.link"
>
<q-item-section
v-if="props.icon"
avatar
>
<q-icon :name="props.icon" />
</q-item-section>
<q-item-section>
<q-item-label>{{ props.title }}</q-item-label>
<q-item-label caption>{{ props.caption }}</q-item-label>
</q-item-section>
</q-item>
</template>
<script setup>
defineOptions({
name: 'EssentialLink'
})
const props = defineProps({
title: {
type: String,
required: true
},
caption: {
type: String,
default: ''
},
link: {
type: String,
default: '#'
},
icon: {
type: String,
default: ''
}
})
</script>

@ -1,40 +0,0 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

@ -0,0 +1 @@
// app global css in SCSS form

@ -0,0 +1,25 @@
// Quasar SCSS (& Sass) Variables
// --------------------------------------------------
// To customize the look and feel of this app, you can override
// the Sass/SCSS variables found in Quasar's source Sass/SCSS files.
// Check documentation for full list of Quasar variables
// Your own variables (that are declared here) and Quasar's own
// ones will be available out of the box in your .vue/.scss/.sass files
// It's highly recommended to change the default colors
// to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #1976D2;
$secondary : #26A69A;
$accent : #9C27B0;
$dark : #1D1D1D;
$dark-page : #121212;
$positive : #21BA45;
$negative : #C10015;
$info : #31CCEC;
$warning : #F2C037;

@ -0,0 +1,106 @@
<template>
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn
flat
dense
round
icon="menu"
aria-label="Menu"
@click="toggleLeftDrawer"
/>
<q-toolbar-title>
Quasar App
</q-toolbar-title>
<div>Quasar v{{ $q.version }}</div>
</q-toolbar>
</q-header>
<q-drawer
v-model="leftDrawerOpen"
show-if-above
bordered
>
<q-list>
<q-item-label
header
>
Essential Links
</q-item-label>
<EssentialLink
v-for="link in linksList"
:key="link.title"
v-bind="link"
/>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
</q-layout>
</template>
<script setup>
import { ref } from 'vue'
import EssentialLink from 'components/EssentialLink.vue'
defineOptions({
name: 'MainLayout'
})
const linksList = [
{
title: 'Docs',
caption: 'quasar.dev',
icon: 'school',
link: 'https://quasar.dev'
},
{
title: 'Github',
caption: 'github.com/quasarframework',
icon: 'code',
link: 'https://github.com/quasarframework'
},
{
title: 'Discord Chat Channel',
caption: 'chat.quasar.dev',
icon: 'chat',
link: 'https://chat.quasar.dev'
},
{
title: 'Forum',
caption: 'forum.quasar.dev',
icon: 'record_voice_over',
link: 'https://forum.quasar.dev'
},
{
title: 'Twitter',
caption: '@quasarframework',
icon: 'rss_feed',
link: 'https://twitter.quasar.dev'
},
{
title: 'Facebook',
caption: '@QuasarFramework',
icon: 'public',
link: 'https://facebook.quasar.dev'
},
{
title: 'Quasar Awesome',
caption: 'Community Quasar projects',
icon: 'favorite',
link: 'https://awesome.quasar.dev'
}
]
const leftDrawerOpen = ref(false)
function toggleLeftDrawer () {
leftDrawerOpen.value = !leftDrawerOpen.value
}
</script>

@ -1,5 +0,0 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App).mount('#app')

@ -0,0 +1,29 @@
<template>
<div class="fullscreen bg-blue text-white text-center q-pa-md flex flex-center">
<div>
<div style="font-size: 30vh">
404
</div>
<div class="text-h2" style="opacity:.4">
Oops. Nothing here...
</div>
<q-btn
class="q-mt-xl"
color="white"
text-color="blue"
unelevated
to="/"
label="Go Home"
no-caps
/>
</div>
</div>
</template>
<script setup>
defineOptions({
name: 'ErrorNotFound'
});
</script>

@ -0,0 +1,15 @@
<template>
<q-page class="flex flex-center">
<img
alt="Quasar logo"
src="~assets/quasar-logo-vertical.svg"
style="width: 200px; height: 200px"
>
</q-page>
</template>
<script setup>
defineOptions({
name: 'IndexPage'
});
</script>

@ -0,0 +1,30 @@
import { route } from 'quasar/wrappers'
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router'
import routes from './routes'
/*
* If not building with SSR mode, you can
* directly export the Router instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Router instance.
*/
export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER
? createMemoryHistory
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory)
const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
routes,
// Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
history: createHistory(process.env.VUE_ROUTER_BASE)
})
return Router
})

@ -0,0 +1,18 @@
const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/IndexPage.vue') }
]
},
// Always leave this as last one,
// but you can also remove it
{
path: '/:catchAll(.*)*',
component: () => import('pages/ErrorNotFound.vue')
}
]
export default routes

@ -1,79 +0,0 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
})

File diff suppressed because it is too large Load Diff

@ -0,0 +1,15 @@
package client
type Config struct {
CenterServer string `json:"centerServer,omitempty"`
}
type Client struct {
config Config
}
func NetClient(config Config) *Client {
return &Client{
config: config,
}
}

@ -0,0 +1,54 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type DataItem struct {
Metric string `json:"metric"`
Tags map[string]string `json:"tags"`
Timestamp int64 `json:"timestamp"`
Value any `json:"value"`
}
type ReportResponse struct {
RequestId string `json:"request_id,omitempty"`
Message string `json:"message,omitempty"`
}
func (c *Client) Report(dataItem []DataItem) (*ReportResponse, error) {
reportJsonBytes, err := json.Marshal(dataItem)
if err != nil {
return nil, err
}
// send report data
resp, err := http.Post(c.config.CenterServer, "application/json", bytes.NewBuffer(reportJsonBytes))
if err != nil {
return nil, err
}
// get response
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
fmt.Println(string(respBytes))
response := ReportResponse{}
err = json.Unmarshal(respBytes, &response)
if err != nil {
return nil, err
}
return &response, nil
}

@ -0,0 +1,5 @@
module rition-testsuite
go 1.22.0
require github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect

@ -0,0 +1,2 @@
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=

@ -0,0 +1,28 @@
package main
import (
"flag"
"rition-testsuite/client"
"rition-testsuite/service"
"time"
)
func main() {
// --report '127.0.0.1:22019' --disk_dev nvme0n1 --mount / --net_dev wlp0s20f3 --interval 30
centerServer := flag.String("report", "http://localhost:8000/api/metric/put", "上报中心")
interval := flag.Int("interval", 5, "上报间隔,单位为秒")
dataFile := flag.String("data", "./data/abnormal_data_22000_500_171.json", "数据文件")
flag.Parse()
config := service.Config{
ClientConfig: client.Config{
CenterServer: *centerServer,
},
ReportInterval: time.Duration(*interval) * time.Second,
DataFileLocation: *dataFile,
}
srv := service.NewService(config)
srv.Run()
//srv.Report()
}

@ -0,0 +1,69 @@
package service
import (
"encoding/json"
"fmt"
"github.com/emirpasic/gods/v2/sets/hashset"
"os"
"rition-testsuite/client"
"sort"
"time"
)
func (s *Service) Run() {
data, err := os.ReadFile(s.config.DataFileLocation)
if err != nil {
fmt.Println(err.Error())
return
}
fileDataItemList := make([]client.DataItem, 0)
err = json.Unmarshal(data, &fileDataItemList)
if err != nil {
fmt.Println(err.Error())
return
}
sort.Slice(fileDataItemList, func(i, j int) bool {
return fileDataItemList[i].Timestamp < fileDataItemList[j].Timestamp
})
times := hashset.New[int64]()
fileDataMap := map[int64][]client.DataItem{}
for _, dataItem := range fileDataItemList {
if _, has := fileDataMap[dataItem.Timestamp]; !has {
fileDataMap[dataItem.Timestamp] = make([]client.DataItem, 0, 22)
}
fileDataMap[dataItem.Timestamp] = append(fileDataMap[dataItem.Timestamp], dataItem)
times.Add(dataItem.Timestamp)
}
i := 0
timesValue := times.Values()
sort.Slice(timesValue, func(i, j int) bool {
return timesValue[i] < timesValue[j]
})
fileDataItemList = nil
times = nil
ticker := time.NewTicker(s.config.ReportInterval)
defer ticker.Stop()
for range ticker.C {
if i < len(timesValue) {
fmt.Printf("reporting: %d\n", timesValue[i])
s.Report(fileDataMap[timesValue[i]])
i++
} else {
return
}
}
}
func (s *Service) Report(dataList []client.DataItem) {
_, err := s.client.Report(dataList)
if err != nil {
fmt.Println("err: ", err.Error())
}
}

@ -0,0 +1,27 @@
package service
import (
"rition-testsuite/client"
"time"
)
type Config struct {
ClientConfig client.Config
DataFileLocation string
ReportInterval time.Duration `json:"reportInterval,omitempty"`
}
type Service struct {
config Config
client *client.Client
}
func NewService(config Config) *Service {
service := Service{
config: config,
client: client.NetClient(config.ClientConfig),
}
return &service
}
Loading…
Cancel
Save