Compare commits

..

71 Commits
v3.1 ... v3.0

Author SHA1 Message Date
EricZeng
508402d8ec Merge pull request #717 from didi/master
合并主分支
2022-10-21 15:09:52 +08:00
EricZeng
eb3e573b22 Merge branch 'v3.0' into master 2022-10-21 15:07:31 +08:00
zengqiao
5e7fbcf078 增加v3.0.1变更内容 2022-10-21 14:46:41 +08:00
zengqiao
3fb35d1fcc 补充v3.0.1版本升级信息 2022-10-21 14:46:41 +08:00
zengqiao
538d54cae0 安装包中,去除docs相关的文档 2022-10-21 14:46:41 +08:00
zengqiao
78b02f80ba [Bugfix] 修复指标版本信息list转map时出现key冲突从而抛出异常的问题 2022-10-21 14:46:41 +08:00
zengqiao
f9ec890e1d [Optimize] 集群Broker列表中,补充Jmx是否成功连接的信息
1、当前页面无数据时,一部分的原因是JMX连接失败导致;
2、Broker列表中增加是否连接成功的信息,便于问题的排查;
2022-10-21 14:46:41 +08:00
zengqiao
af1bb2ccbd [Optimize] 删除Replica指标采集任务
1、当集群存在较多副本时,指标采集的性能会严重降低;
2、Replica的指标基本上都是在实时获取时才需要,因此当前先将Replica指标采集任务关闭,后续依据产品需要再看是否开启;
2022-10-21 14:46:41 +08:00
zengqiao
714e9a56a3 [Optimize] 优化ZK指标的获取,减少重复采集的出现 (#709)
1、避免不同集群,相同的ZK地址时,指标重复获取的情况;
2、避免集群某个ZK地址获取指标失败时,下一个周期还会继续尝试从该地址获取指标;
2022-10-21 14:46:41 +08:00
_haoqi
88d0a60182 [ISSUE #677] 重启会导致部分信息采集抛出空指针 2022-10-21 14:46:41 +08:00
zengqiao
05c52cd672 [Feature] 集群Group列表按照Group维度进行展示 (#580) 2022-10-21 14:46:41 +08:00
Richard
586b37caa0 fix issue:
* [issue #700] Adjust the prompt and replace the Arrays.asList() with the Collections.singletonList()
2022-10-21 14:46:41 +08:00
dianyang12138
d8aa3d64df fix:修复es模版错误 2022-10-21 14:46:41 +08:00
night.liang
13d8fd55c8 fix ldap bug 2022-10-21 14:46:41 +08:00
zengqiao
4133981048 补充Kafka-Group表 2022-10-21 14:46:41 +08:00
chenzy
2f0b18b005 修复时间展示有误的bug,由原先的12小时制改为24小时制 2022-10-21 14:46:41 +08:00
Richard
44134ce0d6 fix issue:
* [issue #662] Fix deadlocks caused by adding data using MySQL's REPLACE method
2022-10-21 14:46:41 +08:00
_haoqi
5f21e5a728 修改zk-Latency avg为小数时的数值转换异常问题 2022-10-21 14:46:41 +08:00
zengqiao
d5079a1b75 修复ZK元信息表role字段类型错误问题 2022-10-21 14:46:41 +08:00
shirenchuang
656dfc2285 update readme 2022-10-21 14:46:41 +08:00
shirenchuang
99be2d704f update readme 2022-10-21 14:46:41 +08:00
Richard
d071e31106 fix issue:
* [issue #666] Fix the type of role phase in ks_km_zookeeper table
2022-10-21 14:46:41 +08:00
shirenchuang
55b34d08dd update readme 2022-10-21 14:46:41 +08:00
赤月
7a29e58453 Update faq.md 2022-10-21 14:46:41 +08:00
shirenchuang
8892b5250e update readme add who's using know streaming 2022-10-21 14:46:41 +08:00
zengqiao
75e53a9617 修复集群ZK列表中缺少返回服务状态字段的问题 2022-10-21 14:46:41 +08:00
zengqiao
7294aba59f 指标信息中,增加返回ZK的指标信息 2022-10-21 14:46:41 +08:00
zengqiao
a8c779675a 删除未被使用的import 2022-10-21 14:46:41 +08:00
zengqiao
facae65f61 健康检查任务优化 2022-10-21 14:46:41 +08:00
zengqiao
0c6475b063 application.yml文件中增加ES用户名密码的配置项 2022-10-21 14:46:41 +08:00
zengqiao
92d6214f4f 增加ZK指标上报普罗米修斯 2022-10-21 14:46:41 +08:00
zengqiao
6ad29b9565 ZookeeperService中增加服务存活统计方法 2022-10-21 14:46:41 +08:00
zengqiao
f3b64ca463 增加float转integer方法 2022-10-21 14:46:41 +08:00
shirenchuang
9340e07662 update contribuer document 2022-10-21 14:46:41 +08:00
zengqiao
50482c40d5 修复获取TopN的Broker指标时,会出现部分指标缺失的问题 2022-10-21 14:46:41 +08:00
zengqiao
12ebc32cec Broker增加服务是否存活接口 2022-10-21 14:46:41 +08:00
zengqiao
215602bb84 调整贡献者名单 2022-10-21 14:46:41 +08:00
zengqiao
5355c5c1f3 修复DSL错误导致ZK指标查询失败问题 2022-10-21 14:46:41 +08:00
shirenchuang
e13d77c81d 贡献者相关文档 2022-10-21 14:46:41 +08:00
shirenchuang
103db39460 贡献者相关文档 2022-10-21 14:46:41 +08:00
shirenchuang
750da7c9d7 贡献者相关文档 2022-10-21 14:46:41 +08:00
shirenchuang
0fea002142 贡献者相关文档 2022-10-21 14:46:41 +08:00
shirenchuang
7163c74cba 贡献者相关文档 2022-10-21 14:46:41 +08:00
石臻臻的杂货铺
2fb3aa1c14 Update CONTRIBUTING.md 2022-10-21 14:46:41 +08:00
石臻臻的杂货铺
dc8604ad81 Update CONTRIBUTING.md 2022-10-21 14:46:41 +08:00
石臻臻的杂货铺
9c67afd170 Update CONTRIBUTING.md 2022-10-21 14:46:41 +08:00
shirenchuang
bd48bc6a3d readme 2022-10-21 14:46:41 +08:00
shirenchuang
b75e630bac Issue 模板 2022-10-21 14:46:41 +08:00
shirenchuang
ebd4e4735d PR 模板 2022-10-21 14:46:41 +08:00
shirenchuang
b3ad6a71ca 贡献者规约文档 2022-10-21 14:46:41 +08:00
shirenchuang
91e2189864 issue template 2022-10-21 14:46:41 +08:00
shirenchuang
ddd5d1b892 issue template 2022-10-21 14:46:41 +08:00
shirenchuang
8aa877071c issue template 2022-10-21 14:46:41 +08:00
shirenchuang
efa253fac8 issue template 2022-10-21 14:46:41 +08:00
shirenchuang
3744c0e97d issue template 2022-10-21 14:46:41 +08:00
shirenchuang
d510640e43 issue template 2022-10-21 14:46:41 +08:00
EricZeng
d7986ad8dd 恢复为原先代码
恢复为原先代码
2022-10-21 14:46:41 +08:00
zengqiao
fbc4d4a540 调整接入带Kerberos认证的ZK集群的文档 2022-10-21 14:46:41 +08:00
zengqiao
bc32c71048 ZK-增加ZK信息查询接口 2022-10-21 14:46:41 +08:00
zengqiao
c4910964db ZK-指标采集入ES 2022-10-21 14:46:41 +08:00
zengqiao
1bc725bd62 ZK-同步ZK元信息至DB 2022-10-21 14:46:41 +08:00
zengqiao
34b7c6746b ZK-增加配置的默认值 2022-10-21 14:46:41 +08:00
zengqiao
20d5b27bb6 ZK-增加四字命令信息的获取 2022-10-21 14:46:41 +08:00
zengqiao
a4abb4069d 删除无效的健康分计算代码 2022-10-21 14:46:41 +08:00
zengqiao
c73cfce780 bump version to 3.1.0 2022-10-21 14:46:41 +08:00
luhe
dfb9b6136b 修改代码支持ZK-Kerberos认证与配置文档 2022-10-21 14:46:41 +08:00
luhe
341bd58d51 修改代码支持ZK-Kerberos认证与配置文档 2022-10-21 14:46:41 +08:00
luhe
4386181304 修改代码支持ZK-Kerberos认证与配置文档 2022-10-21 14:46:41 +08:00
luhe
fb21d8135c 修改代码支持ZK-Kerberos认证 2022-10-21 14:46:41 +08:00
luhe
b4580277a9 修改代码支持ZK-Kerberos认证 2022-10-21 14:46:41 +08:00
EricZeng
045f65204b Merge pull request #633 from didi/master
合并主分支
2022-09-29 13:09:19 +08:00
119 changed files with 1371 additions and 3324 deletions

View File

@@ -1,39 +1,4 @@
## v3.1.0
**Bug修复**
- 修复重置 Group Offset 的提示信息中缺少Dead状态也可进行重置的描述
- 修复新建 Topic 后,立即查看 Topic Messages 信息时,会提示 Topic 不存在的问题;
- 修复副本变更时,优先副本选举未被正常处罚执行的问题;
- 修复 git 目录不存在时,打包不能正常进行的问题;
- 修复 KRaft 模式的 Kafka 集群JMX PORT 显示 -1 的问题;
**体验优化**
- 优化Cluster、Broker、Topic、Group的健康分为健康状态
- 去除健康巡检配置中的权重信息;
- 错误提示页面展示优化;
- 前端打包编译依赖默认使用 taobao 镜像;
- 重新设计优化导航栏的 icon
**新增**
- 个人头像下拉信息中,新增产品版本信息;
- 多集群列表页面,新增集群健康状态分布信息;
**Kafka ZK 部分 (v3.1.0版本正式发布)**
- 新增 ZK 集群的指标大盘信息;
- 新增 ZK 集群的服务状态概览信息;
- 新增 ZK 集群的服务节点列表信息;
- 新增 Kafka 在 ZK 的存储数据查看功能;
- 新增 ZK 的健康巡检及健康状态计算;
---
## v3.0.1
**Bug修复**

View File

@@ -8,19 +8,7 @@
暂无
### 6.2.1、升级至 `v3.1.0` 版本
```sql
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_BRAIN_SPLIT', '{ \"value\": 1} ', 'ZK 脑裂', 'admin');
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_OUTSTANDING_REQUESTS', '{ \"amount\": 100, \"ratio\":0.8} ', 'ZK Outstanding 请求堆积数', 'admin');
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_WATCH_COUNT', '{ \"amount\": 100000, \"ratio\": 0.8 } ', 'ZK WatchCount 数', 'admin');
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_ALIVE_CONNECTIONS', '{ \"amount\": 10000, \"ratio\": 0.8 } ', 'ZK 连接数', 'admin');
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_APPROXIMATE_DATA_SIZE', '{ \"amount\": 524288000, \"ratio\": 0.8 } ', 'ZK 数据大小(Byte)', 'admin');
INSERT INTO `ks_km_platform_cluster_config` (`cluster_id`, `value_group`, `value_name`, `value`, `description`, `operator`) VALUES ('-1', 'HEALTH', 'HC_ZK_SENT_RATE', '{ \"amount\": 500000, \"ratio\": 0.8 } ', 'ZK 发包数', 'admin');
```
### 6.2.2、升级至 `v3.0.1` 版本
### 6.2.1、升级至 `v3.0.1` 版本
**ES 索引模版**
```bash
@@ -154,8 +142,10 @@ CREATE TABLE `ks_km_group` (
```
---
### 6.2.3、升级至 `v3.0.0` 版本
### 6.2.2、升级至 `v3.0.0` 版本
**SQL 变更**
@@ -167,7 +157,7 @@ ADD COLUMN `zk_properties` TEXT NULL COMMENT 'ZK配置' AFTER `jmx_properties`;
---
### 6.2.4、升级至 `v3.0.0-beta.2`版本
### 6.2.3、升级至 `v3.0.0-beta.2`版本
**配置变更**
@@ -238,7 +228,7 @@ ALTER TABLE `logi_security_oplog`
---
### 6.2.5、升级至 `v3.0.0-beta.1`版本
### 6.2.4、升级至 `v3.0.0-beta.1`版本
**SQL 变更**
@@ -257,7 +247,7 @@ ALTER COLUMN `operation_methods` set default '';
---
### 6.2.6、`2.x`版本 升级至 `v3.0.0-beta.0`版本
### 6.2.5、`2.x`版本 升级至 `v3.0.0-beta.0`版本
**升级步骤:**

View File

@@ -1,6 +1,5 @@
package com.xiaojukeji.know.streaming.km.biz.cluster;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysHealthState;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysState;
import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.MultiClusterDashboardDTO;
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult;
@@ -16,8 +15,6 @@ public interface MultiClusterPhyManager {
*/
ClusterPhysState getClusterPhysState();
ClusterPhysHealthState getClusterPhysHealthState();
/**
* 查询多集群大盘
* @param dto 分页信息

View File

@@ -5,7 +5,9 @@ import com.didiglobal.logi.log.LogFactory;
import com.xiaojukeji.know.streaming.km.biz.cluster.ClusterZookeepersManager;
import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.ClusterZookeepersOverviewDTO;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ZookeeperMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.ZookeeperMetricParam;
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.PaginationResult;
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result;
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus;
@@ -18,6 +20,7 @@ import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant;
import com.xiaojukeji.know.streaming.km.common.enums.zookeeper.ZKRoleEnum;
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
import com.xiaojukeji.know.streaming.km.common.utils.PaginationUtil;
import com.xiaojukeji.know.streaming.km.common.utils.Tuple;
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService;
import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ZookeeperMetricVersionItems;
import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZnodeService;
@@ -27,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service
@@ -52,6 +56,11 @@ public class ClusterZookeepersManagerImpl implements ClusterZookeepersManager {
return Result.buildFromRSAndMsg(ResultStatus.CLUSTER_NOT_EXIST, MsgConstant.getClusterPhyNotExist(clusterPhyId));
}
// // TODO
// private Integer healthState;
// private Integer healthCheckPassed;
// private Integer healthCheckTotal;
List<ZookeeperInfo> infoList = zookeeperService.listFromDBByCluster(clusterPhyId);
ClusterZookeepersStateVO vo = new ClusterZookeepersStateVO();
@@ -81,17 +90,12 @@ public class ClusterZookeepersManagerImpl implements ClusterZookeepersManager {
}
}
// 指标获取
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.batchCollectMetricsFromZookeeper(
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(new ZookeeperMetricParam(
clusterPhyId,
Arrays.asList(
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT,
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_STATE,
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_CHECK_PASSED,
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL
)
);
infoList.stream().filter(elem -> elem.alive()).map(item -> new Tuple<String, Integer>(item.getHost(), item.getPort())).collect(Collectors.toList()),
ConvertUtil.str2ObjByJson(clusterPhy.getZkProperties(), ZKConfig.class),
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT
));
if (metricsResult.failed()) {
LOGGER.error(
"class=ClusterZookeepersManagerImpl||method=getClusterPhyZookeepersState||clusterPhyId={}||errMsg={}",
@@ -99,12 +103,8 @@ public class ClusterZookeepersManagerImpl implements ClusterZookeepersManager {
);
return Result.buildSuc(vo);
}
ZookeeperMetrics metrics = metricsResult.getData();
vo.setWatchCount(ConvertUtil.float2Integer(metrics.getMetrics().get(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT)));
vo.setHealthState(ConvertUtil.float2Integer(metrics.getMetrics().get(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_STATE)));
vo.setHealthCheckPassed(ConvertUtil.float2Integer(metrics.getMetrics().get(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_CHECK_PASSED)));
vo.setHealthCheckTotal(ConvertUtil.float2Integer(metrics.getMetrics().get(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_HEALTH_CHECK_TOTAL)));
Float watchCount = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT);
vo.setWatchCount(watchCount != null? watchCount.intValue(): null);
return Result.buildSuc(vo);
}

View File

@@ -5,7 +5,6 @@ import com.didiglobal.logi.log.LogFactory;
import com.xiaojukeji.know.streaming.km.biz.cluster.MultiClusterPhyManager;
import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricDTO;
import com.xiaojukeji.know.streaming.km.common.bean.dto.metrices.MetricsClusterPhyDTO;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysHealthState;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhysState;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
import com.xiaojukeji.know.streaming.km.common.bean.dto.cluster.MultiClusterDashboardDTO;
@@ -17,7 +16,6 @@ import com.xiaojukeji.know.streaming.km.common.bean.vo.cluster.ClusterPhyDashboa
import com.xiaojukeji.know.streaming.km.common.bean.vo.metrics.line.MetricMultiLinesVO;
import com.xiaojukeji.know.streaming.km.common.constant.Constant;
import com.xiaojukeji.know.streaming.km.common.converter.ClusterVOConverter;
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthStateEnum;
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
import com.xiaojukeji.know.streaming.km.common.utils.PaginationUtil;
import com.xiaojukeji.know.streaming.km.common.utils.PaginationMetricsUtil;
@@ -77,32 +75,6 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager {
return physState;
}
@Override
public ClusterPhysHealthState getClusterPhysHealthState() {
List<ClusterPhy> clusterPhyList = clusterPhyService.listAllClusters();
ClusterPhysHealthState physState = new ClusterPhysHealthState(clusterPhyList.size());
for (ClusterPhy clusterPhy: clusterPhyList) {
ClusterMetrics metrics = clusterMetricService.getLatestMetricsFromCache(clusterPhy.getId());
Float state = metrics.getMetric(ClusterMetricVersionItems.CLUSTER_METRIC_HEALTH_STATE);
if (state == null) {
physState.setUnknownCount(physState.getUnknownCount() + 1);
} else if (state.intValue() == HealthStateEnum.GOOD.getDimension()) {
physState.setGoodCount(physState.getGoodCount() + 1);
} else if (state.intValue() == HealthStateEnum.MEDIUM.getDimension()) {
physState.setMediumCount(physState.getMediumCount() + 1);
} else if (state.intValue() == HealthStateEnum.POOR.getDimension()) {
physState.setPoorCount(physState.getPoorCount() + 1);
} else if (state.intValue() == HealthStateEnum.DEAD.getDimension()) {
physState.setDeadCount(physState.getDeadCount() + 1);
} else {
physState.setUnknownCount(physState.getUnknownCount() + 1);
}
}
return physState;
}
@Override
public PaginationResult<ClusterPhyDashboardVO> getClusterPhysDashboard(MultiClusterDashboardDTO dto) {
// 获取集群
@@ -176,7 +148,16 @@ public class MultiClusterPhyManagerImpl implements MultiClusterPhyManager {
// 获取所有的metrics
List<ClusterMetrics> metricsList = new ArrayList<>();
for (ClusterPhyDashboardVO vo: voList) {
metricsList.add(clusterMetricService.getLatestMetricsFromCache(vo.getId()));
ClusterMetrics clusterMetrics = clusterMetricService.getLatestMetricsFromCache(vo.getId());
if (!clusterMetrics.getMetrics().containsKey(ClusterMetricVersionItems.CLUSTER_METRIC_HEALTH_SCORE)) {
Float alive = clusterMetrics.getMetrics().get(ClusterMetricVersionItems.CLUSTER_METRIC_ALIVE);
// 如果集群没有健康分,则设置一个默认的健康分数值
clusterMetrics.putMetric(ClusterMetricVersionItems.CLUSTER_METRIC_HEALTH_SCORE,
(alive != null && alive <= 0)? 0.0f: Constant.DEFAULT_CLUSTER_HEALTH_SCORE.floatValue()
);
}
metricsList.add(clusterMetrics);
}
// 范围搜索

View File

@@ -209,7 +209,7 @@ public class GroupManagerImpl implements GroupManager {
}
if (!ConsumerGroupState.EMPTY.equals(description.state()) && !ConsumerGroupState.DEAD.equals(description.state())) {
return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, String.format("group处于%s, 重置失败(仅Empty | Dead 情况可重置)", GroupStateEnum.getByRawState(description.state()).getState()));
return Result.buildFromRSAndMsg(ResultStatus.KAFKA_OPERATE_FAILED, String.format("group处于%s, 重置失败(仅Empty情况可重置)", GroupStateEnum.getByRawState(description.state()).getState()));
}
// 获取offset

View File

@@ -10,18 +10,14 @@ import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicCreateParam;
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicParam;
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.topic.TopicPartitionExpandParam;
import com.xiaojukeji.know.streaming.km.common.bean.entity.partition.Partition;
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result;
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.ResultStatus;
import com.xiaojukeji.know.streaming.km.common.bean.entity.topic.Topic;
import com.xiaojukeji.know.streaming.km.common.constant.MsgConstant;
import com.xiaojukeji.know.streaming.km.common.utils.BackoffUtils;
import com.xiaojukeji.know.streaming.km.common.utils.FutureUtil;
import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils;
import com.xiaojukeji.know.streaming.km.common.utils.kafka.KafkaReplicaAssignUtil;
import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService;
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService;
import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService;
import com.xiaojukeji.know.streaming.km.core.service.topic.OpTopicService;
import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService;
import kafka.admin.AdminUtils;
@@ -56,9 +52,6 @@ public class OpTopicManagerImpl implements OpTopicManager {
@Autowired
private ClusterPhyService clusterPhyService;
@Autowired
private PartitionService partitionService;
@Override
public Result<Void> createTopic(TopicCreateDTO dto, String operator) {
log.info("method=createTopic||param={}||operator={}.", dto, operator);
@@ -87,7 +80,7 @@ public class OpTopicManagerImpl implements OpTopicManager {
);
// 创建Topic
Result<Void> createTopicRes = opTopicService.createTopic(
return opTopicService.createTopic(
new TopicCreateParam(
dto.getClusterId(),
dto.getTopicName(),
@@ -97,21 +90,6 @@ public class OpTopicManagerImpl implements OpTopicManager {
),
operator
);
if (createTopicRes.successful()){
try{
FutureUtil.quickStartupFutureUtil.submitTask(() -> {
BackoffUtils.backoff(3000);
Result<List<Partition>> partitionsResult = partitionService.listPartitionsFromKafka(clusterPhy, dto.getTopicName());
if (partitionsResult.successful()){
partitionService.updatePartitions(clusterPhy.getId(), dto.getTopicName(), partitionsResult.getData(), new ArrayList<>());
}
});
}catch (Exception e) {
log.error("method=createTopic||param={}||operator={}||msg=add partition to db failed||errMsg=exception", dto, operator, e);
return Result.buildFromRSAndMsg(ResultStatus.MYSQL_OPERATE_FAILED, "Topic创建成功但记录Partition到DB中失败等待定时任务同步partition信息");
}
}
return createTopicRes;
}
@Override

View File

@@ -47,7 +47,7 @@ public class VersionControlManagerImpl implements VersionControlManager {
@PostConstruct
public void init(){
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_HEALTH_STATE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_HEALTH_SCORE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_FAILED_FETCH_REQ, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_FAILED_PRODUCE_REQ, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_UNDER_REPLICA_PARTITIONS, true));
@@ -57,7 +57,7 @@ public class VersionControlManagerImpl implements VersionControlManager {
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_BYTES_REJECTED, true));
defaultMetrics.add(new UserMetricConfig(METRIC_TOPIC.getCode(), TOPIC_METRIC_MESSAGE_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_HEALTH_STATE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_HEALTH_SCORE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_ACTIVE_CONTROLLER_COUNT, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_BYTES_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_CLUSTER.getCode(), CLUSTER_METRIC_BYTES_OUT, true));
@@ -75,9 +75,9 @@ public class VersionControlManagerImpl implements VersionControlManager {
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_OFFSET_CONSUMED, true));
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_LAG, true));
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_STATE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_HEALTH_STATE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_GROUP.getCode(), GROUP_METRIC_HEALTH_SCORE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_HEALTH_STATE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_HEALTH_SCORE, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_CONNECTION_COUNT, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_MESSAGE_IN, true));
defaultMetrics.add(new UserMetricConfig(METRIC_BROKER.getCode(), BROKER_METRIC_NETWORK_RPO_AVG_IDLE, true));

View File

@@ -3,7 +3,6 @@ package com.xiaojukeji.know.streaming.km.common.bean.entity.broker;
import com.alibaba.fastjson.TypeReference;
import com.xiaojukeji.know.streaming.km.common.bean.entity.common.IpPortData;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.JmxConfig;
import com.xiaojukeji.know.streaming.km.common.bean.po.broker.BrokerPO;
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
import lombok.AllArgsConstructor;
@@ -66,13 +65,13 @@ public class Broker implements Serializable {
*/
private Map<String, IpPortData> endpointMap;
public static Broker buildFrom(Long clusterPhyId, Node node, Long startTimestamp, JmxConfig jmxConfig) {
public static Broker buildFrom(Long clusterPhyId, Node node, Long startTimestamp) {
Broker metadata = new Broker();
metadata.setClusterPhyId(clusterPhyId);
metadata.setBrokerId(node.id());
metadata.setHost(node.host());
metadata.setPort(node.port());
metadata.setJmxPort(jmxConfig != null ? jmxConfig.getJmxPort() : -1);
metadata.setJmxPort(-1);
metadata.setStartTimestamp(startTimestamp);
metadata.setRack(node.rack());
metadata.setStatus(1);

View File

@@ -1,37 +0,0 @@
package com.xiaojukeji.know.streaming.km.common.bean.entity.cluster;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 集群状态信息
* @author zengqiao
* @date 22/02/24
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ClusterPhysHealthState {
private Integer unknownCount;
private Integer goodCount;
private Integer mediumCount;
private Integer poorCount;
private Integer deadCount;
private Integer total;
public ClusterPhysHealthState(Integer total) {
this.unknownCount = 0;
this.goodCount = 0;
this.mediumCount = 0;
this.poorCount = 0;
this.deadCount = 0;
this.total = total;
}
}

View File

@@ -13,4 +13,9 @@ public class BaseClusterHealthConfig extends BaseClusterConfigValue {
* 健康检查名称
*/
protected HealthCheckNameEnum checkNameEnum;
/**
* 权重
*/
protected Float weight;
}

View File

@@ -1,19 +0,0 @@
package com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck;
import lombok.Data;
/**
* @author wyb
* @date 2022/10/26
*/
@Data
public class HealthAmountRatioConfig extends BaseClusterHealthConfig {
/**
* 总数
*/
private Integer amount;
/**
* 比例
*/
private Double ratio;
}

View File

@@ -1,83 +0,0 @@
package com.xiaojukeji.know.streaming.km.common.bean.entity.health;
import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO;
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum;
import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Data
@NoArgsConstructor
public class HealthCheckAggResult {
private HealthCheckNameEnum checkNameEnum;
private List<HealthCheckResultPO> poList;
private Boolean passed;
public HealthCheckAggResult(HealthCheckNameEnum checkNameEnum, List<HealthCheckResultPO> poList) {
this.checkNameEnum = checkNameEnum;
this.poList = poList;
if (!ValidateUtils.isEmptyList(poList) && poList.stream().filter(elem -> elem.getPassed() <= 0).count() <= 0) {
passed = true;
} else {
passed = false;
}
}
public Integer getTotalCount() {
if (poList == null) {
return 0;
}
return poList.size();
}
public Integer getPassedCount() {
if (poList == null) {
return 0;
}
return (int) (poList.stream().filter(elem -> elem.getPassed() > 0).count());
}
/**
* 计算当前检查的健康分
* 比如计算集群Broker健康检查中的某一项的健康分
*/
public Integer calRawHealthScore() {
if (poList == null || poList.isEmpty()) {
return 100;
}
return 100 * this.getPassedCount() / this.getTotalCount();
}
public List<String> getNotPassedResNameList() {
if (poList == null) {
return new ArrayList<>();
}
return poList.stream().filter(elem -> elem.getPassed() <= 0).map(elem -> elem.getResName()).collect(Collectors.toList());
}
public Date getCreateTime() {
if (ValidateUtils.isEmptyList(poList)) {
return null;
}
return poList.get(0).getCreateTime();
}
public Date getUpdateTime() {
if (ValidateUtils.isEmptyList(poList)) {
return null;
}
return poList.get(0).getUpdateTime();
}
}

View File

@@ -17,6 +17,10 @@ import java.util.stream.Collectors;
public class HealthScoreResult {
private HealthCheckNameEnum checkNameEnum;
private Float presentDimensionTotalWeight;
private Float allDimensionTotalWeight;
private BaseClusterHealthConfig baseConfig;
private List<HealthCheckResultPO> poList;
@@ -24,11 +28,15 @@ public class HealthScoreResult {
private Boolean passed;
public HealthScoreResult(HealthCheckNameEnum checkNameEnum,
Float presentDimensionTotalWeight,
Float allDimensionTotalWeight,
BaseClusterHealthConfig baseConfig,
List<HealthCheckResultPO> poList) {
this.checkNameEnum = checkNameEnum;
this.baseConfig = baseConfig;
this.poList = poList;
this.presentDimensionTotalWeight = presentDimensionTotalWeight;
this.allDimensionTotalWeight = allDimensionTotalWeight;
if (!ValidateUtils.isEmptyList(poList) && poList.stream().filter(elem -> elem.getPassed() <= 0).count() <= 0) {
passed = true;
} else {
@@ -51,6 +59,32 @@ public class HealthScoreResult {
return (int) (poList.stream().filter(elem -> elem.getPassed() > 0).count());
}
/**
* 计算所有检查结果的健康分
* 比如:计算集群健康分
*/
public Float calAllWeightHealthScore() {
Float healthScore = 100 * baseConfig.getWeight() / allDimensionTotalWeight;
if (poList == null || poList.isEmpty()) {
return 0.0f;
}
return healthScore * this.getPassedCount() / this.getTotalCount();
}
/**
* 计算当前维度的健康分
* 比如计算集群Broker健康分
*/
public Float calDimensionWeightHealthScore() {
Float healthScore = 100 * baseConfig.getWeight() / presentDimensionTotalWeight;
if (poList == null || poList.isEmpty()) {
return 0.0f;
}
return healthScore * this.getPassedCount() / this.getTotalCount();
}
/**
* 计算当前检查的健康分
* 比如计算集群Broker健康检查中的某一项的健康分
@@ -68,7 +102,7 @@ public class HealthScoreResult {
return new ArrayList<>();
}
return poList.stream().filter(elem -> elem.getPassed() <= 0 && !ValidateUtils.isBlank(elem.getResName())).map(elem -> elem.getResName()).collect(Collectors.toList());
return poList.stream().filter(elem -> elem.getPassed() <= 0).map(elem -> elem.getResName()).collect(Collectors.toList());
}
public Date getCreateTime() {

View File

@@ -1,26 +0,0 @@
package com.xiaojukeji.know.streaming.km.common.bean.entity.param.zookeeper;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam;
import com.xiaojukeji.know.streaming.km.common.utils.Tuple;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author didi
*/
@Data
@NoArgsConstructor
public class ZookeeperParam extends ClusterPhyParam {
private List<Tuple<String, Integer>> zkAddressList;
private ZKConfig zkConfig;
public ZookeeperParam(Long clusterPhyId, List<Tuple<String, Integer>> zkAddressList, ZKConfig zkConfig) {
super(clusterPhyId);
this.zkAddressList = zkAddressList;
this.zkConfig = zkConfig;
}
}

View File

@@ -1,6 +1,5 @@
package com.xiaojukeji.know.streaming.km.common.bean.entity.reassign;
import com.xiaojukeji.know.streaming.km.common.utils.CommonUtils;
import lombok.Data;
import org.apache.kafka.common.TopicPartition;
@@ -20,10 +19,4 @@ public class ReassignResult {
return state.isDone();
}
public boolean checkPreferredReplicaElectionUnNeed(String reassignBrokerIds, String originalBrokerIds) {
Integer targetLeader = CommonUtils.string2IntList(reassignBrokerIds).get(0);
Integer originalLeader = CommonUtils.string2IntList(originalBrokerIds).get(0);
return originalLeader.equals(targetLeader);
}
}

View File

@@ -16,7 +16,4 @@ public class Znode {
@ApiModelProperty(value = "节点属性", example = "")
private Stat stat;
@ApiModelProperty(value = "节点路径", example = "")
private String namespace;
}

View File

@@ -1,32 +0,0 @@
package com.xiaojukeji.know.streaming.km.common.bean.vo.cluster;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author zengqiao
* @date 22/02/24
*/
@Data
@ApiModel(description = "集群健康状态信息")
public class ClusterPhysHealthStateVO {
@ApiModelProperty(value = "未知", example = "30")
private Integer unknownCount;
@ApiModelProperty(value = "", example = "30")
private Integer goodCount;
@ApiModelProperty(value = "", example = "30")
private Integer mediumCount;
@ApiModelProperty(value = "", example = "30")
private Integer poorCount;
@ApiModelProperty(value = "down", example = "30")
private Integer deadCount;
@ApiModelProperty(value = "总数", example = "150")
private Integer total;
}

View File

@@ -32,6 +32,9 @@ public class HealthCheckConfigVO {
@ApiModelProperty(value="检查说明", example = "Group延迟")
private String configDesc;
@ApiModelProperty(value="权重", example = "10")
private Float weight;
@ApiModelProperty(value="检查配置", example = "100")
private String value;
}

View File

@@ -18,9 +18,6 @@ public class HealthScoreBaseResultVO extends BaseTimeVO {
@ApiModelProperty(value="检查维度", example = "1")
private Integer dimension;
@ApiModelProperty(value="检查维度名称", example = "cluster")
private String dimensionName;
@ApiModelProperty(value="检查名称", example = "Group延迟")
private String configName;
@@ -30,6 +27,9 @@ public class HealthScoreBaseResultVO extends BaseTimeVO {
@ApiModelProperty(value="检查说明", example = "Group延迟")
private String configDesc;
@ApiModelProperty(value="权重百分比[0-100]", example = "10")
private Integer weightPercent;
@ApiModelProperty(value="得分", example = "100")
private Integer score;

View File

@@ -19,7 +19,4 @@ public class ZnodeVO {
@ApiModelProperty(value = "节点属性", example = "")
private ZnodeStatVO stat;
@ApiModelProperty(value = "节点路径", example = "/cluster")
private String namespace;
}

View File

@@ -35,9 +35,14 @@ public class Constant {
public static final Integer DEFAULT_SESSION_TIMEOUT_UNIT_MS = 15000;
public static final Integer DEFAULT_REQUEST_TIMEOUT_UNIT_MS = 5000;
public static final Float MIN_HEALTH_SCORE = 10f;
/**
* 指标相关
*/
public static final Integer DEFAULT_CLUSTER_HEALTH_SCORE = 90;
public static final Integer PER_BATCH_MAX_VALUE = 100;
public static final String DEFAULT_USER_NAME = "know-streaming-app";

View File

@@ -15,15 +15,24 @@ public class HealthScoreVOConverter {
private HealthScoreVOConverter() {
}
public static List<HealthScoreResultDetailVO> convert2HealthScoreResultDetailVOList(List<HealthScoreResult> healthScoreResultList) {
public static List<HealthScoreResultDetailVO> convert2HealthScoreResultDetailVOList(List<HealthScoreResult> healthScoreResultList, boolean useGlobalWeight) {
Float globalWeightSum = 1f;
if (!healthScoreResultList.isEmpty()) {
globalWeightSum = healthScoreResultList.get(0).getAllDimensionTotalWeight();
}
List<HealthScoreResultDetailVO> voList = new ArrayList<>();
for (HealthScoreResult healthScoreResult: healthScoreResultList) {
HealthScoreResultDetailVO vo = new HealthScoreResultDetailVO();
vo.setDimension(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension());
vo.setDimensionName(healthScoreResult.getCheckNameEnum().getDimensionEnum().getMessage());
vo.setConfigName(healthScoreResult.getCheckNameEnum().getConfigName());
vo.setConfigItem(healthScoreResult.getCheckNameEnum().getConfigItem());
vo.setConfigDesc(healthScoreResult.getCheckNameEnum().getConfigDesc());
if (useGlobalWeight) {
vo.setWeightPercent(healthScoreResult.getBaseConfig().getWeight().intValue() * 100 / globalWeightSum.intValue());
} else {
vo.setWeightPercent(healthScoreResult.getBaseConfig().getWeight().intValue() * 100 / healthScoreResult.getPresentDimensionTotalWeight().intValue());
}
vo.setScore(healthScoreResult.calRawHealthScore());
if (healthScoreResult.getTotalCount() <= 0) {
@@ -48,9 +57,9 @@ public class HealthScoreVOConverter {
for (HealthScoreResult healthScoreResult: healthScoreResultList) {
HealthScoreBaseResultVO vo = new HealthScoreBaseResultVO();
vo.setDimension(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension());
vo.setDimensionName(healthScoreResult.getCheckNameEnum().getDimensionEnum().getMessage());
vo.setConfigName(healthScoreResult.getCheckNameEnum().getConfigName());
vo.setConfigDesc(healthScoreResult.getCheckNameEnum().getConfigDesc());
vo.setWeightPercent(healthScoreResult.getBaseConfig().getWeight().intValue() * 100 / healthScoreResult.getPresentDimensionTotalWeight().intValue());
vo.setScore(healthScoreResult.calRawHealthScore());
vo.setPassed(healthScoreResult.getPassedCount().equals(healthScoreResult.getTotalCount()));
vo.setCheckConfig(convert2HealthCheckConfigVO(ConfigGroupEnum.HEALTH.name(), healthScoreResult.getBaseConfig()));
@@ -77,6 +86,7 @@ public class HealthScoreVOConverter {
vo.setConfigName(config.getCheckNameEnum().getConfigName());
vo.setConfigItem(config.getCheckNameEnum().getConfigItem());
vo.setConfigDesc(config.getCheckNameEnum().getConfigDesc());
vo.setWeight(config.getWeight());
vo.setValue(ConvertUtil.obj2Json(config));
return vo;
}

View File

@@ -1,9 +1,7 @@
package com.xiaojukeji.know.streaming.km.common.converter;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
import com.xiaojukeji.know.streaming.km.common.bean.entity.zookeeper.Znode;
import com.xiaojukeji.know.streaming.km.common.utils.Tuple;
import com.xiaojukeji.know.streaming.km.common.utils.zookeeper.ZookeeperUtils;
import org.apache.zookeeper.data.Stat;
public class ZnodeConverter {
@@ -11,13 +9,11 @@ public class ZnodeConverter {
}
public static Znode convert2Znode(ClusterPhy clusterPhy, Tuple<byte[], Stat> dataAndStat, String path) {
public static Znode convert2Znode(Tuple<byte[], Stat> dataAndStat, String path) {
Znode znode = new Znode();
znode.setStat(dataAndStat.getV2());
znode.setData(dataAndStat.getV1() == null ? null : new String(dataAndStat.getV1()));
znode.setName(path.substring(path.lastIndexOf('/') + 1));
znode.setNamespace(ZookeeperUtils.getNamespace(clusterPhy.getZookeeper()));
return znode;
}
}

View File

@@ -10,15 +10,13 @@ import lombok.Getter;
public enum HealthCheckDimensionEnum {
UNKNOWN(-1, "未知"),
CLUSTER(0, "Cluster"),
CLUSTER(0, "Cluster维度"),
BROKER(1, "Broker"),
BROKER(1, "Broker维度"),
TOPIC(2, "Topic"),
TOPIC(2, "Topic维度"),
GROUP(3, "Group"),
ZOOKEEPER(4, "Zookeeper"),
GROUP(3, "消费组维度"),
;

View File

@@ -1,7 +1,6 @@
package com.xiaojukeji.know.streaming.km.common.enums.health;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthAmountRatioConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthCompareValueConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthDetectedInLatestMinutesConfig;
import com.xiaojukeji.know.streaming.km.common.constant.Constant;
@@ -20,8 +19,7 @@ public enum HealthCheckNameEnum {
"未知",
Constant.HC_CONFIG_NAME_PREFIX + "UNKNOWN",
"未知",
BaseClusterHealthConfig.class,
false
BaseClusterHealthConfig.class
),
CLUSTER_NO_CONTROLLER(
@@ -29,8 +27,7 @@ public enum HealthCheckNameEnum {
"Controller",
Constant.HC_CONFIG_NAME_PREFIX + "CLUSTER_NO_CONTROLLER",
"集群Controller数正常",
HealthCompareValueConfig.class,
true
HealthCompareValueConfig.class
),
BROKER_REQUEST_QUEUE_FULL(
@@ -38,8 +35,7 @@ public enum HealthCheckNameEnum {
"RequestQueueSize",
Constant.HC_CONFIG_NAME_PREFIX + "BROKER_REQUEST_QUEUE_FULL",
"Broker-RequestQueueSize指标",
HealthCompareValueConfig.class,
false
HealthCompareValueConfig.class
),
BROKER_NETWORK_PROCESSOR_AVG_IDLE_TOO_LOW(
@@ -47,8 +43,7 @@ public enum HealthCheckNameEnum {
"NetworkProcessorAvgIdlePercent",
Constant.HC_CONFIG_NAME_PREFIX + "BROKER_NETWORK_PROCESSOR_AVG_IDLE_TOO_LOW",
"Broker-NetworkProcessorAvgIdlePercent指标",
HealthCompareValueConfig.class,
false
HealthCompareValueConfig.class
),
GROUP_RE_BALANCE_TOO_FREQUENTLY(
@@ -56,8 +51,7 @@ public enum HealthCheckNameEnum {
"Group Re-Balance",
Constant.HC_CONFIG_NAME_PREFIX + "GROUP_RE_BALANCE_TOO_FREQUENTLY",
"Group re-balance频率",
HealthDetectedInLatestMinutesConfig.class,
false
HealthDetectedInLatestMinutesConfig.class
),
TOPIC_NO_LEADER(
@@ -65,8 +59,7 @@ public enum HealthCheckNameEnum {
"NoLeader",
Constant.HC_CONFIG_NAME_PREFIX + "TOPIC_NO_LEADER",
"Topic 无Leader数",
HealthCompareValueConfig.class,
false
HealthCompareValueConfig.class
),
TOPIC_UNDER_REPLICA_TOO_LONG(
@@ -74,66 +67,9 @@ public enum HealthCheckNameEnum {
"UnderReplicaTooLong",
Constant.HC_CONFIG_NAME_PREFIX + "TOPIC_UNDER_REPLICA_TOO_LONG",
"Topic 未同步持续时间",
HealthDetectedInLatestMinutesConfig.class,
false
HealthDetectedInLatestMinutesConfig.class
),
ZK_BRAIN_SPLIT(
HealthCheckDimensionEnum.ZOOKEEPER,
"BrainSplit",
Constant.HC_CONFIG_NAME_PREFIX + "ZK_BRAIN_SPLIT",
"ZK 脑裂",
HealthCompareValueConfig.class,
true
),
ZK_OUTSTANDING_REQUESTS(
HealthCheckDimensionEnum.ZOOKEEPER,
"OutstandingRequests",
Constant.HC_CONFIG_NAME_PREFIX + "ZK_OUTSTANDING_REQUESTS",
"ZK Outstanding 请求堆积数",
HealthAmountRatioConfig.class,
false
),
ZK_WATCH_COUNT(
HealthCheckDimensionEnum.ZOOKEEPER,
"WatchCount",
Constant.HC_CONFIG_NAME_PREFIX + "ZK_WATCH_COUNT",
"ZK WatchCount 数",
HealthAmountRatioConfig.class,
false
),
ZK_ALIVE_CONNECTIONS(
HealthCheckDimensionEnum.ZOOKEEPER,
"AliveConnections",
Constant.HC_CONFIG_NAME_PREFIX + "ZK_ALIVE_CONNECTIONS",
"ZK 连接数",
HealthAmountRatioConfig.class,
false
),
ZK_APPROXIMATE_DATA_SIZE(
HealthCheckDimensionEnum.ZOOKEEPER,
"ApproximateDataSize",
Constant.HC_CONFIG_NAME_PREFIX + "ZK_APPROXIMATE_DATA_SIZE",
"ZK 数据大小(Byte)",
HealthAmountRatioConfig.class,
false
),
ZK_SENT_RATE(
HealthCheckDimensionEnum.ZOOKEEPER,
"SentRate",
Constant.HC_CONFIG_NAME_PREFIX + "ZK_SENT_RATE",
"ZK 发包数",
HealthAmountRatioConfig.class,
false
),
;
/**
@@ -161,18 +97,12 @@ public enum HealthCheckNameEnum {
*/
private final Class configClazz;
/**
* 是可用性检查?
*/
private final boolean availableChecker;
HealthCheckNameEnum(HealthCheckDimensionEnum dimensionEnum, String configItem, String configName, String configDesc, Class configClazz, boolean availableChecker) {
HealthCheckNameEnum(HealthCheckDimensionEnum dimensionEnum, String configItem, String configName, String configDesc, Class configClazz) {
this.dimensionEnum = dimensionEnum;
this.configItem = configItem;
this.configName = configName;
this.configDesc = configDesc;
this.configClazz = configClazz;
this.availableChecker = availableChecker;
}
public static HealthCheckNameEnum getByName(String configName) {

View File

@@ -16,7 +16,7 @@ public enum HealthStateEnum {
POOR(2, ""),
DEAD(3, "Down"),
DEAD(3, "宕机"),
;

View File

@@ -16,7 +16,7 @@ public class ZookeeperUtils {
* 解析ZK地址
* @see ConnectStringParser
*/
public static List<Tuple<String, Integer>> connectStringParser(String connectString) {
public static List<Tuple<String, Integer>> connectStringParser(String connectString) throws Exception {
List<Tuple<String, Integer>> ipPortList = new ArrayList<>();
if (connectString == null) {
@@ -55,14 +55,5 @@ public class ZookeeperUtils {
return ipPortList;
}
public static String getNamespace(String zookeeperAddress) {
int index = zookeeperAddress.indexOf('/');
String namespace = "/";
if (index != -1) {
namespace = zookeeperAddress.substring(index);
}
return namespace;
}
}

View File

@@ -24,7 +24,7 @@ npm install -g lerna
npm run i
```
我们默认保留了 `package-lock.json` 文件,以防止可能的依赖包自动升级导致的问题。依赖默认会通过 taobao 镜像 `https://registry.npmmirror.com/` 服务下载(如需修改下载源,请见当前目录下 package.json 文件)
我们默认保留了 `package-lock.json` 文件,以防止可能的依赖包自动升级导致的问题。依赖默认会通过 taobao 镜像 `https://registry.npmmirror.com/` 服务下载。
## 三、启动项目(可选,打包构建请直接看步骤三)

View File

@@ -22,7 +22,7 @@
"prettier": "2.3.2"
},
"scripts": {
"i": "npm config set registry https://registry.npmmirror.com/ && npm install && lerna bootstrap",
"i": "npm install && lerna bootstrap",
"clean": "rm -rf node_modules package-lock.json packages/*/node_modules packages/*/package-lock.json",
"start": "lerna run start",
"build": "lerna run build",

View File

@@ -1345,9 +1345,9 @@
}
},
"@knowdesign/icons": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz",
"integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==",
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.0.tgz",
"integrity": "sha512-7c+h2TSbh2ihTkXIivuO+DddNC5wG7hVv9SS4ccmkvTKls2ZTLitPu+U0wpufDxPhkPMaKEQfsECsVJ+7jLMiw==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",

View File

@@ -21,7 +21,7 @@
"build": "cross-env NODE_ENV=production webpack --max_old_space_size=8000"
},
"dependencies": {
"@knowdesign/icons": "^1.0.2",
"@knowdesign/icons": "^1.0.0",
"babel-preset-react-app": "^10.0.0",
"classnames": "^2.2.6",
"dotenv": "^16.0.1",

View File

@@ -1388,9 +1388,9 @@
}
},
"@knowdesign/icons": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.2.tgz",
"integrity": "sha512-eQuUQZbPRvC1xU4ouzgrk8j6UE39Cui+eEkYkLbfGLpVbGPFKJ7yEmUyKhIjG9zhf1qS7/h08yzq0hAHajBi8g==",
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@knowdesign/icons/-/icons-1.0.1.tgz",
"integrity": "sha512-EI3s25BJt+Slv7/t6B3K3zv7I6TKkk2Wf1y68zuxK80MMkWf8lqqUtyAZbFDoPUfXAjw6vHktMBH44gbMHMRFA==",
"requires": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",

View File

@@ -35,7 +35,7 @@
"dependencies": {
"@ant-design/compatible": "^1.0.8",
"@ant-design/icons": "^4.6.2",
"@knowdesign/icons": "^1.0.2",
"@knowdesign/icons": "^1.0.1",
"@types/react": "^17.0.39",
"@types/react-copy-to-clipboard": "^5.0.2",
"@types/react-dom": "^17.0.11",

View File

@@ -24,7 +24,6 @@ const api = {
logout: `${securityPrefix}/account/logout`,
// 全局信息
getVersionInfo: () => getApi('/self/version'),
getUserInfo: (userId: number) => `${securityPrefix}/user/${userId}`,
getPermissionTree: `${securityPrefix}/permission/tree`,
getKafkaVersionItems: () => getApi('/kafka-versions-items'),
@@ -61,7 +60,6 @@ const api = {
phyClustersDashbord: getApi(`/physical-clusters/dashboard`),
supportKafkaVersion: getApi(`/support-kafka-versions`),
phyClusterState: getApi(`/physical-clusters/state`),
phyClusterHealthState: getApi(`/physical-clusters/health-state`),
getOperatingStateList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/groups-overview`),
getGroupTopicList: (clusterPhyId: number, groupName: string) => getApi(`/clusters/${clusterPhyId}/groups/${groupName}/topics-overview`),
@@ -203,14 +201,6 @@ const api = {
getJobsTaskData: (clusterPhyId: string, jobId: string | number) => getApi(`/clusters/${clusterPhyId}/jobs/${jobId}/modify-detail`),
//编辑任务
putJobsTaskData: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/jobs`),
// Zookeeper 接口
getZookeeperState: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/zookeepers-state`),
getZookeeperList: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/zookeepers-overview`),
getZookeeperNodeChildren: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/znode-children`),
getZookeeperNodeData: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/znode-data`),
getZookeeperMetricsInfo: (clusterPhyId: number) => getApi(`/clusters/${clusterPhyId}/zookeeper-latest-metrics`),
getZookeeperMetrics: (clusterPhyId: string) => getApi(`/clusters/${clusterPhyId}/zookeeper-metrics`),
};
export default api;

View File

@@ -3,13 +3,12 @@ import '@babel/polyfill';
import React, { useState, useEffect, useLayoutEffect } from 'react';
import { BrowserRouter, Switch, Route, useLocation, useHistory } from 'react-router-dom';
import { get as lodashGet } from 'lodash';
import { DProLayout, AppContainer, Menu, Utils, Page500, Modal } from 'knowdesign';
import { DProLayout, AppContainer, Menu, Utils, Page403, Page404, Page500, Modal } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import dantdZhCN from 'knowdesign/es/locale/zh_CN';
import dantdEnUS from 'knowdesign/es/locale/en_US';
import { DotChartOutlined } from '@ant-design/icons';
import { licenseEventBus } from './constants/axiosConfig';
import { Page403, Page404, NoLicense } from './pages/ErrorPages';
import intlZhCN from './locales/zh';
import intlEnUS from './locales/en';
import registerApps from '../config/registerApps';
@@ -19,21 +18,13 @@ import { Login } from './pages/Login';
import { getLicenseInfo } from './constants/common';
import api from './api';
import ClusterContainer from './pages/index';
import NoLicense from './pages/NoLicense';
import ksLogo from './assets/ks-logo.png';
interface ILocaleMap {
[index: string]: any;
}
interface VersionInfo {
'git.branch': string;
'git.build.itme': string;
'git.build.version': string;
'git.commit.id': string;
'git.commit.id.abbrev': string;
'git.commit.time': string;
}
const localeMap: ILocaleMap = {
'zh-CN': {
dantd: dantdZhCN,
@@ -115,7 +106,6 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
const history = useHistory();
const userInfo = localStorage.getItem('userInfo');
const [curActiveAppName, setCurActiveAppName] = useState('');
const [versionInfo, setVersionInfo] = useState<VersionInfo>();
useEffect(() => {
if (pathname.startsWith('/config')) {
@@ -125,13 +115,6 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
}
}, [pathname]);
// 获取版本信息
useEffect(() => {
Utils.request(api.getVersionInfo()).then((res: VersionInfo) => {
setVersionInfo(res);
});
}, []);
return (
<DProLayout.Container
headerProps={{
@@ -158,12 +141,7 @@ const AppContent = (props: { setlanguage: (language: string) => void }) => {
],
isFixed: false,
userDropMenuItems: [
<Menu.Item key={0}>
<a href="https://github.com/didi/KnowStreaming/releases" rel="noreferrer" target="_blank">
: {versionInfo?.['git.build.version']}
</a>
</Menu.Item>,
<Menu.Item key={1} onClick={logout}>
<Menu.Item key={0} onClick={logout}>
</Menu.Item>,
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,12 +1,11 @@
/* eslint-disable react/display-name */
import React, { useState, useEffect } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import CardBar from '@src/components/CardBar';
import { healthDataProps } from '.';
import { Utils } from 'knowdesign';
import { Tag, Utils } from 'knowdesign';
import Api from '@src/api';
import { hashDataParse } from '@src/constants/common';
import { HealthStateEnum } from '../HealthState';
export default (props: { record: any }) => {
const { record } = props;
@@ -15,20 +14,22 @@ export default (props: { record: any }) => {
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [healthData, setHealthData] = useState<healthDataProps>({
state: HealthStateEnum.UNKNOWN,
score: 0,
passed: 0,
total: 0,
alive: 0,
});
const healthItems = ['HealthScore_Topics', 'HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'live'];
useEffect(() => {
setLoading(true);
Utils.post(Api.getBrokerDetailMetricPoints(hashDataParse(urlLocation.hash)?.brokerId, urlParams?.clusterId), [
'Partitions',
'Leaders',
'PartitionURP',
'HealthScore',
'HealthCheckPassed',
'HealthCheckTotal',
'HealthState',
'Alive',
]).then((data: any) => {
setLoading(false);
const rightData = JSON.parse(JSON.stringify(data.metrics));
@@ -46,12 +47,14 @@ export default (props: { record: any }) => {
value: rightData['PartitionURP'] || '-',
},
];
const healthResData: any = {};
healthResData.score = data?.metrics?.['HealthScore'] || 0;
healthResData.passed = data?.metrics?.['HealthCheckPassed'] || 0;
healthResData.total = data?.metrics?.['HealthCheckTotal'] || 0;
healthResData.alive = data?.metrics?.['Alive'] || 0;
setCardData(cordRightMap);
setHealthData({
state: data?.metrics?.['HealthState'],
passed: data?.metrics?.['HealthCheckPassed'] || 0,
total: data?.metrics?.['HealthCheckTotal'] || 0,
});
setHealthData(healthResData);
// setCardData(data.metrics)
});
}, []);
return (

View File

@@ -6,7 +6,6 @@ import { healthDataProps } from '.';
import { Tag, Tooltip, Utils } from 'knowdesign';
import api from '@src/api';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { HealthStateEnum } from '../HealthState';
export default () => {
const routeParams = useParams<{
@@ -15,21 +14,26 @@ export default () => {
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [healthData, setHealthData] = useState<healthDataProps>({
state: HealthStateEnum.UNKNOWN,
score: 0,
passed: 0,
total: 0,
alive: 0,
});
const healthItems = ['HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'HealthState'];
const cardItems = ['Partitions', 'PartitionsSkew', 'Leaders', 'LeadersSkew', 'LogSize'];
const healthItems = ['HealthScore_Brokers', 'HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'Alive'];
useEffect(() => {
setLoading(true);
// 获取左侧健康度
const brokerMetric = Utils.post(api.getBrokerMetricPoints(Number(routeParams.clusterId)), healthItems).then((data: any) => {
setHealthData({
state: data?.metrics?.['HealthState'],
passed: data?.metrics?.['HealthCheckPassed_Brokers'] || 0,
total: data?.metrics?.['HealthCheckTotal_Brokers'] || 0,
});
const healthResData: any = {};
// healthResData.score = data?.find((item:any) => item.metricName === 'HealthScore_Brokers')?.value || 0;
// healthResData.passed = data?.find((item:any) => item.metricName === 'HealthCheckPassed_Brokers')?.value || 0;
// healthResData.total = data?.find((item:any) => item.metricName === 'HealthCheckTotal_Brokers')?.value || 0;
healthResData.score = data?.metrics?.['HealthScore_Brokers'] || 0;
healthResData.passed = data?.metrics?.['HealthCheckPassed_Brokers'] || 0;
healthResData.total = data?.metrics?.['HealthCheckTotal_Brokers'] || 0;
healthResData.alive = data?.metrics?.['Alive'] || 0;
setHealthData(healthResData);
});
// 获取右侧状态
const brokersState = Utils.request(api.getBrokersState(routeParams?.clusterId)).then((data) => {
@@ -111,6 +115,6 @@ export default () => {
setLoading(false);
});
}, [routeParams.clusterId]);
// console.log('cardData', cardData, healthData);
return <CardBar scene="broker" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
};

View File

@@ -4,7 +4,6 @@ import CardBar from '@src/components/CardBar';
import { healthDataProps } from '.';
import { Utils } from 'knowdesign';
import api from '@src/api';
import { HealthStateEnum } from '../HealthState';
export default () => {
const routeParams = useParams<{
@@ -13,17 +12,22 @@ export default () => {
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [healthData, setHealthData] = useState<healthDataProps>({
state: HealthStateEnum.UNKNOWN,
score: 0,
passed: 0,
total: 0,
alive: 0,
});
const [healthDetail, setHealthDetail] = useState([]);
const cardItems = ['Groups', 'GroupActives', 'GroupEmptys', 'GroupRebalances', 'GroupDeads'];
const healthItems = ['HealthCheckPassed_Groups', 'HealthCheckTotal_Groups', 'HealthState'];
const healthItems = ['HealthScore_Groups', 'HealthCheckPassed_Groups', 'HealthCheckTotal_Groups', 'Alive'];
useEffect(() => {
setLoading(true);
Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => {
setLoading(false);
// setCardData(data
// .filter((item: any) => cardItems.indexOf(item.metricName) >= 0)
// .map((item: any) => ({ title: item.metricName, value: item.value }))
// )
setCardData(
cardItems.map((item) => {
if (item === 'GroupDeads') {
@@ -32,11 +36,12 @@ export default () => {
return { title: item, value: data.metrics[item] };
})
);
setHealthData({
state: data?.metrics?.['HealthState'],
passed: data?.metrics?.['HealthCheckPassed_Groups'] || 0,
total: data?.metrics?.['HealthCheckTotal_Groups'] || 0,
});
const healthResData: any = {};
healthResData.score = data.metrics['HealthScore_Groups'] || 0;
healthResData.passed = data.metrics['HealthCheckPassed_Groups'] || 0;
healthResData.total = data.metrics['HealthCheckTotal_Groups'] || 0;
healthResData.alive = data.metrics['Alive'] || 0;
setHealthData(healthResData);
});
}, []);
return <CardBar scene="group" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;

View File

@@ -2,7 +2,8 @@
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import CardBar from '@src/components/CardBar';
import { Utils } from 'knowdesign';
import { healthDataProps } from '.';
import { Tag, Utils } from 'knowdesign';
import Api from '@src/api';
export default () => {
@@ -11,7 +12,14 @@ export default () => {
}>();
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [healthData, setHealthData] = useState<healthDataProps>({
score: 0,
passed: 0,
total: 0,
alive: 0,
});
const cardItems = ['Partitions', 'PartitionsSkew', 'Leaders', 'LeadersSkew', 'LogSize'];
const healthItems = ['HealthScore_Brokers', 'HealthCheckPassed_Brokers', 'HealthCheckTotal_Brokers', 'alive'];
const getCordRightMap = (data: any) => {
const cordRightMap = [
{
@@ -41,7 +49,6 @@ export default () => {
];
return cordRightMap;
};
useEffect(() => {
setLoading(true);
// 获取状态

View File

@@ -32,6 +32,7 @@ const LoadRebalanceCardBar = (props: any) => {
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [normsVisible, setNormsVisible] = useState(null);
const cardItems = ['AclEnable', 'Acls', 'AclUsers', 'AclTopics', 'AclGroups'];
const onClose = () => {
setNormsVisible(false);
};
@@ -44,9 +45,11 @@ const LoadRebalanceCardBar = (props: any) => {
// 获取右侧状态
getCartInfo()
.then((res: any) => {
// const { AclEnable, Acls, AclUsers, AclTopics, AclGroups } = res.metrics;
const { next, sub, status } = res;
const { cpu, disk, bytesIn, bytesOut } = sub;
const newNextDate: any = transUnitTimePro(moment(next).valueOf() - moment().valueOf());
// const newNextDate = parseInt(`${transUnitTimePro(moment(next).valueOf() - moment().valueOf())}`);
const cardMap = [
{
title() {
@@ -77,15 +80,20 @@ const LoadRebalanceCardBar = (props: any) => {
>
{!status ? '已均衡' : '未均衡'}
</Tag>
{/* <Tag style={{ padding: '2px 4px', backgroundColor: 'rgba(85,110,230,0.10)', color: '#556EE6' }}>已均衡</Tag> */}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
<IconFont className="cutomIcon" type={`${!status ? 'icon-zhengchang' : 'icon-warning'}`} />
</span>
{/* <span>
周期均衡 <IconFont className="cutomIcon" type="icon-zhengchang" />
</span> */}
<span>
{newNextDate?.value || 0}
{newNextDate?.unit || '分钟'}
</span>
{/* {<span>距下次均衡还剩{1}小时</span>} */}
</div>
</div>
);
@@ -98,6 +106,73 @@ const LoadRebalanceCardBar = (props: any) => {
padding: '12px 12px 8px 12px',
},
},
// {
// // title: 'CPU avg',
// title() {
// return (
// <div>
// <span style={{ display: 'inline-block', marginRight: '8px' }}>CPU AVG</span>
// {!cpu?.interval && cpu?.interval !== 0 && (
// <Tooltip overlayClassName="rebalance-tooltip" title="未设置均衡策略">
// <QuestionCircleOutlined />
// </Tooltip>
// )}
// {/* <IconFont className="cutomIcon" onClick={() => setNormsVisible(true)} type="icon-shezhi"></IconFont> */}
// </div>
// );
// },
// value(visibleType: boolean) {
// return (
// <div id="CPU" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
// <div style={{ display: 'inline-block' }}>
// <div style={{ margin: '5px 0', fontFamily: 'DIDIFD-Medium' }}>
// <span style={{ fontSize: '24px' }}>{cpu?.avg || 0}</span>
// <span style={{ fontSize: '14px', display: 'inline-block', marginLeft: '4px' }}>%</span>
// </div>
// <div style={{ marginTop: '-4px', display: 'flex', justifyContent: 'space-between' }}>
// <span>均衡区间: ±{cpu?.interval || 0}%</span>
// </div>
// </div>
// <Popover
// // visible={visibleType} // 修改为hover柱状图
// overlayClassName="custom-popover"
// content={
// <div style={{ color: '#495057' }}>
// <div>
// <IconFont className="cutomIcon cutomIcon-red" type="icon-chaoguo" />
// 超过均衡区间的有: {cpu?.bigNu || 0}
// </div>
// <div style={{ margin: '6px 0' }}>
// <IconFont className="cutomIcon cutomIcon-green" type="icon-qujian" />
// 在均衡区间内的有: {cpu?.betweenNu || 0}
// </div>
// <div>
// <IconFont className="cutomIcon cutomIcon-red" type="icon-diyu" />
// 低于均衡区间的有: {cpu?.smallNu || 0}
// </div>
// </div>
// }
// getPopupContainer={(triggerNode: any) => {
// return triggerNode;
// }}
// color="#ffffff"
// >
// <div style={{ width: '44px', height: '30px' }}>
// <StateChart
// data={[
// { name: 'bigNu', value: cpu?.bigNu || 0 },
// { name: 'betweenNu', value: cpu?.betweenNu || 0 },
// { name: 'smallNu', value: cpu?.smallNu || 0 },
// ]}
// />
// </div>
// </Popover>
// </div>
// );
// },
// className: 'custom-card-bar',
// valueClassName: 'custom-card-bar-value',
// },
{
title() {
return (

View File

@@ -5,10 +5,8 @@ import { healthDataProps } from '.';
import { Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import api from '@src/api';
import { healthScoreCondition } from './const';
import { hashDataParse } from '@src/constants/common';
import { HealthStateEnum } from '../HealthState';
const healthItems = ['HealthCheckPassed', 'HealthCheckTotal', 'HealthState'];
const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => {
return typeof v === 'function' ? v(visibleType) : v;
@@ -21,12 +19,14 @@ export default (props: { record: any }) => {
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [healthData, setHealthData] = useState<healthDataProps>({
state: HealthStateEnum.UNKNOWN,
score: 0,
passed: 0,
total: 0,
alive: 0,
});
const [healthDetail, setHealthDetail] = useState([]);
const [clusterAlive, setClusterAlive] = useState(0);
const healthItems = ['HealthScore', 'HealthCheckPassed', 'HealthCheckTotal', 'alive'];
const getNumAndSubTitles = (cardColumnsItemData: any) => {
return (
<div style={{ width: '100%', display: 'flex', alignItems: 'end' }}>
@@ -40,21 +40,21 @@ export default (props: { record: any }) => {
</div>
);
};
useEffect(() => {
setLoading(true);
const topicName = hashDataParse(location.hash)['topicName'];
const detailHealthPromise = Utils.post(api.getTopicMetricPointsLatest(Number(routeParams.clusterId), topicName), healthItems).then(
let detailHealthPromise = Utils.post(api.getTopicMetricPointsLatest(Number(routeParams.clusterId), topicName), healthItems).then(
(data: any) => {
setHealthData({
state: data.metrics['HealthState'],
passed: data.metrics['HealthCheckPassed'] || 0,
total: data.metrics['HealthCheckTotal'] || 0,
});
let healthResData: any = {};
healthResData.score = data.metrics['HealthScore'] || 0;
healthResData.passed = data.metrics['HealthCheckPassed'] || 0;
healthResData.total = data.metrics['HealthCheckTotal'] || 0;
// healthResData.alive = data.metrics['alive'] || 0
setHealthData(healthResData);
}
);
const detailStatePromise = Utils.request(api.getTopicState(Number(routeParams.clusterId), topicName)).then((topicHealthState: any) => {
let detailStatePromise = Utils.request(api.getTopicState(Number(routeParams.clusterId), topicName)).then((topicHealthState: any) => {
setCardData([
{
title: 'Partitions',
@@ -87,12 +87,13 @@ export default (props: { record: any }) => {
]);
});
// 获取集群维度的指标信息
const clusterStatePromise = Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), ['Alive']).then(
let clusterStatePromise = Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), ['Alive']).then(
(clusterHealthState: any) => {
setClusterAlive(clusterHealthState?.metrics?.Alive || 0);
let clusterAlive = clusterHealthState?.metrics?.Alive || 0;
setClusterAlive(clusterAlive);
}
);
Promise.all([detailHealthPromise, detailStatePromise, clusterStatePromise]).then(() => {
Promise.all([detailHealthPromise, detailStatePromise, clusterStatePromise]).then((res) => {
setLoading(false);
});
}, []);
@@ -100,7 +101,7 @@ export default (props: { record: any }) => {
<CardBar
record={record}
scene="topic"
healthData={{ ...healthData, state: clusterAlive ? healthData.state : HealthStateEnum.DOWN }}
healthData={{ ...healthData, alive: clusterAlive }}
cardColumns={cardData}
showCardBg={false}
loading={loading}

View File

@@ -4,7 +4,6 @@ import CardBar from '@src/components/CardBar';
import { healthDataProps } from '.';
import { Utils } from 'knowdesign';
import api from '@src/api';
import { HealthStateEnum } from '../HealthState';
export default () => {
const routeParams = useParams<{
@@ -13,12 +12,14 @@ export default () => {
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [healthData, setHealthData] = useState<healthDataProps>({
state: HealthStateEnum.UNKNOWN,
score: 0,
passed: 0,
total: 0,
alive: 0,
});
const [healthDetail, setHealthDetail] = useState([]);
const cardItems = ['Topics', 'Partitions', 'PartitionNoLeader', 'PartitionMinISR_S', 'PartitionMinISR_E', 'PartitionURP'];
const healthItems = ['HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'HealthState'];
const healthItems = ['HealthScore_Topics', 'HealthCheckPassed_Topics', 'HealthCheckTotal_Topics', 'Alive'];
useEffect(() => {
setLoading(true);
Utils.post(api.getMetricPointsLatest(Number(routeParams.clusterId)), cardItems.concat(healthItems)).then((data: any) => {
@@ -41,6 +42,12 @@ export default () => {
PartitionURP: 'URP',
PartitionNoLeader: 'No Leader',
};
// setCardData(data
// .filter(item => cardItems.indexOf(item.name) >= 0)
// .map(item => {
// return { title: metricElmMap[item.name] || item.name, value: item.value }
// })
// )
setCardData(
cardItems.map((item) => {
let title = item;
@@ -59,11 +66,12 @@ export default () => {
return { title, value: data.metrics[item] };
})
);
setHealthData({
state: data.metrics['HealthState'],
passed: data.metrics['HealthCheckPassed_Topics'] || 0,
total: data.metrics['HealthCheckTotal_Topics'] || 0,
});
const healthResData: any = {};
healthResData.score = data.metrics['HealthScore_Topics'] || 0;
healthResData.passed = data.metrics['HealthCheckPassed_Topics'] || 0;
healthResData.total = data.metrics['HealthCheckTotal_Topics'] || 0;
healthResData.alive = data.metrics['Alive'] || 0;
setHealthData(healthResData);
});
}, []);
return <CardBar scene="topic" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;

View File

@@ -1,120 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import CardBar, { healthDataProps } from './index';
import { Utils } from 'knowdesign';
import api from '@src/api';
import { HealthStateEnum } from '../HealthState';
interface ZookeeperState {
aliveFollowerCount: number;
aliveObserverCount: number;
aliveServerCount: number;
healthCheckPassed: number;
healthCheckTotal: number;
healthState: number;
leaderNode: string;
totalFollowerCount: number;
totalObserverCount: number;
totalServerCount: number;
watchCount: number;
}
const getVal = (val: string | number | undefined | null) => {
return val === undefined || val === null || val === '' ? '-' : val;
};
const ZookeeperCard = () => {
const { clusterId } = useParams<{
clusterId: string;
}>();
const [loading, setLoading] = useState(false);
const [cardData, setCardData] = useState([]);
const [healthData, setHealthData] = useState<healthDataProps>({
state: HealthStateEnum.UNKNOWN,
passed: 0,
total: 0,
});
const getHealthData = () => {
return Utils.post(api.getZookeeperMetricsInfo(Number(clusterId)), ['HealthCheckPassed', 'HealthCheckTotal', 'HealthState']).then(
(data: any) => {
setHealthData({
state: data?.metrics?.['HealthState'],
passed: data?.metrics?.['HealthCheckPassed'] || 0,
total: data?.metrics?.['HealthCheckTotal'] || 0,
});
}
);
};
const getCardInfo = () => {
return Utils.request(api.getZookeeperState(clusterId)).then((res: ZookeeperState) => {
const {
aliveFollowerCount,
aliveObserverCount,
aliveServerCount,
totalFollowerCount,
totalObserverCount,
totalServerCount,
watchCount,
leaderNode,
} = res || {};
const cardMap = [
{
title: 'Node Count',
value() {
return (
<span>
{aliveServerCount || '-'}/{totalServerCount || '-'}
</span>
);
},
customStyle: {
// 自定义cardbar样式
marginLeft: 0,
},
},
{
title: 'Watch Count',
value: getVal(watchCount),
},
{
title: 'Leader',
value() {
return <span style={{ fontSize: 24 }}>{leaderNode || '-'}</span>;
},
},
{
title: 'Follower',
value() {
return (
<span>
{getVal(aliveFollowerCount)}/{getVal(totalFollowerCount)}
</span>
);
},
},
{
title: 'Observer',
value() {
return (
<span>
{getVal(aliveObserverCount)}/{getVal(totalObserverCount)}
</span>
);
},
},
];
setCardData(cardMap);
});
};
useEffect(() => {
setLoading(true);
Promise.all([getHealthData(), getCardInfo()]).finally(() => {
setLoading(false);
});
}, [clusterId]);
return <CardBar scene="zookeeper" healthData={healthData} cardColumns={cardData} loading={loading}></CardBar>;
};
export default ZookeeperCard;

View File

@@ -10,15 +10,48 @@
height: 88px;
width: 100%;
display: flex;
// justify-content: space-between;
align-items: center;
.card-bar-health {
width: 240px;
height: 70px;
display: flex;
align-items: center;
// justify-content: space-between;
.card-bar-health-process {
padding-top: 30px;
margin-right: 20px;
height: 100%;
margin-right: 24px;
.dcloud-progress-inner {
border-radius: 50%;
}
.dcloud-progress-status-normal {
.dcloud-progress-inner {
background: rgba(85, 110, 230, 0.03);
}
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
stroke: rgb(85, 110, 230);
}
}
.dcloud-progress-status-success {
.dcloud-progress-inner {
background: rgba(0, 192, 162, 0.03);
}
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
stroke: rgb(0, 192, 162);
}
}
.dcloud-progress-status-exception {
.dcloud-progress-inner {
background: rgba(255, 112, 102, 0.03);
}
.dcloud-progress-inner:not(.dcloud-progress-circle-gradient) .dcloud-progress-circle-path {
stroke: rgb(255, 112, 102);
}
}
.dcloud-progress-inner {
font-family: DIDIFD-Regular;
font-size: 40px !important;
}
}
.state {
font-size: 13px;
@@ -28,6 +61,20 @@
line-height: 20px;
display: flex;
align-items: center;
.health-status-image {
width: 15px;
height: 15px;
background-size: cover;
}
.health-status-image-success {
background-image: url('../../assets/health-status-success.png');
}
.health-status-image-exception {
background-image: url('../../assets/health-status-exception.png');
}
.health-status-image-normal {
background-image: url('../../assets/health-status-normal.png');
}
}
.value-bar {
display: flex;

View File

@@ -1,24 +1,25 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Drawer, Spin, Table, Utils } from 'knowdesign';
import { Drawer, Select, Spin, Table } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import { Utils, Progress } from 'knowdesign';
import './index.less';
import api from '@src/api';
import moment from 'moment';
import TagsWithHide from '../TagsWithHide/index';
import HealthState, { getHealthStateDesc, getHealthStateEmoji, HealthStateEnum } from '../HealthState';
import { getConfigItemDetailDesc } from '@src/pages/SingleClusterDetail/config';
import { getHealthProcessColor } from '@src/pages/SingleClusterDetail/config';
export interface healthDataProps {
state: HealthStateEnum;
score: number;
passed: number;
total: number;
alive: number;
}
export interface CardBarProps {
cardColumns?: any[];
healthData?: healthDataProps;
showCardBg?: boolean;
scene: 'topic' | 'broker' | 'group' | 'zookeeper';
scene: 'topic' | 'broker' | 'group';
record?: any;
loading?: boolean;
needProgress?: boolean;
@@ -26,27 +27,36 @@ export interface CardBarProps {
const renderValue = (v: string | number | ((visibleType?: boolean) => JSX.Element), visibleType?: boolean) => {
return typeof v === 'function' ? v(visibleType) : v;
};
const sceneCodeMap = {
broker: {
code: 1,
fieldName: 'brokerId',
alias: 'Brokers',
const statusTxtEmojiMap = {
success: {
emoji: '👍',
txt: '优异',
},
normal: {
emoji: '😊',
txt: '正常',
},
exception: {
emoji: '👻',
txt: '异常',
},
};
const sceneCodeMap = {
topic: {
code: 2,
fieldName: 'topicName',
alias: 'Topics',
},
broker: {
code: 1,
fieldName: 'brokerId',
alias: 'Brokers',
},
group: {
code: 3,
fieldName: 'groupName',
alias: 'Consumers',
},
zookeeper: {
code: 4,
fieldName: 'zookeeperId',
alias: 'Zookeeper',
},
};
const CardColumnsItem: any = (cardItem: any) => {
const { cardColumnsItemData, showCardBg } = cardItem;
@@ -82,7 +92,16 @@ const CardBar = (props: CardBarProps) => {
}>();
const { healthData, cardColumns, showCardBg = true, scene, record, loading, needProgress = true } = props;
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
const [progressStatus, setProgressStatus] = useState<'success' | 'exception' | 'normal'>('success');
const [healthCheckDetailList, setHealthCheckDetailList] = useState([]);
const [isAlive, setIsAlive] = useState(true);
useEffect(() => {
if (healthData) {
setProgressStatus(!isAlive ? 'exception' : healthData.score >= 90 ? 'success' : 'normal');
setIsAlive(healthData.alive === 1);
}
}, [healthData, isAlive]);
useEffect(() => {
const sceneObj = sceneCodeMap[scene];
@@ -101,24 +120,23 @@ const CardBar = (props: CardBarProps) => {
const columns = [
{
title: '检查项',
dataIndex: 'checkConfig',
render(config: any, record: any) {
let valueGroup = {};
try {
valueGroup = JSON.parse(config.value);
} catch (e) {
//
}
return getConfigItemDetailDesc(record.configItem, valueGroup) || record.configDesc || '-';
},
dataIndex: 'configDesc',
key: 'configDesc',
},
{
title: '权重',
dataIndex: 'weightPercent',
key: 'weightPercent',
},
{
title: '得分',
dataIndex: 'score',
key: 'score',
},
// {
// title: '得分',
// dataIndex: 'score',
// },
{
title: '检查时间',
dataIndex: 'updateTime',
key: 'updateTime',
render: (value: number) => {
return moment(value).format('YYYY-MM-DD HH:mm:ss');
},
@@ -126,6 +144,8 @@ const CardBar = (props: CardBarProps) => {
{
title: '检查结果',
dataIndex: 'passed',
key: 'passed',
width: 280,
render(value: boolean, record: any) {
const icon = value ? <IconFont type="icon-zhengchang"></IconFont> : <IconFont type="icon-yichang"></IconFont>;
const txt = value ? '已通过' : '未通过';
@@ -148,13 +168,40 @@ const CardBar = (props: CardBarProps) => {
{!loading && healthData && needProgress && (
<div className="card-bar-health">
<div className="card-bar-health-process">
<HealthState state={healthData?.state} width={74} height={74} />
<Progress
width={70}
type="circle"
percent={!isAlive ? 100 : healthData.score}
status={progressStatus}
format={(percent, successPercent) => {
return !isAlive ? (
<div
style={{
fontFamily: 'HelveticaNeue-Medium',
fontSize: 22,
color: getHealthProcessColor(healthData.score, healthData.alive),
}}
>
Down
</div>
) : (
<div
style={{
textIndent: Math.round(percent) >= 100 ? '-4px' : '',
color: getHealthProcessColor(healthData.score, healthData.alive),
}}
>
{Math.round(percent)}
</div>
);
}}
strokeWidth={3}
/>
</div>
<div>
<div className="state">
{getHealthStateEmoji(healthData?.state)}
&nbsp;{sceneCodeMap[scene].alias}
{getHealthStateDesc(healthData?.state)}
<div className={`health-status-image health-status-image-${progressStatus}`}></div>
&nbsp;{sceneCodeMap[scene].alias}{statusTxtEmojiMap[progressStatus].txt}
</div>
<div className="value-bar">
<div className="value">{`${healthData?.passed}/${healthData?.total}`}</div>

View File

@@ -26,6 +26,7 @@ const OptionsDefault = [
const NodeScope = ({ nodeScopeModule, change }: propsType) => {
const {
hasCustomScope,
customScopeList: customList,
scopeName = '',
scopeLabel = '自定义范围',
@@ -128,75 +129,79 @@ const NodeScope = ({ nodeScopeModule, change }: propsType) => {
</Space>
</Radio.Group>
</div>
<div className="flx_r">
<h6 className="time_title">{scopeLabel}</h6>
<div className="custom-scope">
<div className="check-row">
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
</Checkbox>
<Input
className="search-input"
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
size="small"
placeholder={searchPlaceholder}
onChange={(e) => setScopeSearchValue(e.target.value)}
/>
</div>
<div className="fixed-height">
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
<Row gutter={[10, 12]}>
{customList
.filter((item) => item.label.includes(scopeSearchValue))
.map((item) => (
<Col span={12} key={item.value}>
<Checkbox value={item.value}>{item.label}</Checkbox>
</Col>
))}
</Row>
</Checkbox.Group>
</div>
{hasCustomScope && (
<div className="flx_r">
<h6 className="time_title">{scopeLabel}</h6>
<div className="custom-scope">
<div className="check-row">
<Checkbox className="check-all" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
</Checkbox>
<Input
className="search-input"
suffix={<IconFont type="icon-fangdajing" style={{ fontSize: '16px' }} />}
size="small"
placeholder={searchPlaceholder}
onChange={(e) => setScopeSearchValue(e.target.value)}
/>
</div>
<div className="fixed-height">
<Checkbox.Group style={{ width: '100%' }} onChange={checkChange} value={checkedListTemp}>
<Row gutter={[10, 12]}>
{customList
.filter((item) => item.label.includes(scopeSearchValue))
.map((item) => (
<Col span={12} key={item.value}>
<Checkbox value={item.value}>{item.label}</Checkbox>
</Col>
))}
</Row>
</Checkbox.Group>
</div>
<div className="btn-con">
<Button
type="primary"
size="small"
className="btn-sure"
onClick={customSure}
disabled={checkedListTemp?.length > 0 ? false : true}
>
</Button>
<Button size="small" onClick={customCancel}>
</Button>
<div className="btn-con">
<Button
type="primary"
size="small"
className="btn-sure"
onClick={customSure}
disabled={checkedListTemp?.length > 0 ? false : true}
>
</Button>
<Button size="small" onClick={customCancel}>
</Button>
</div>
</div>
</div>
</div>
)}
</div>
</div>
);
return (
<div id="d-node-scope">
<div className="scope-title">{scopeName}</div>
<Popover
trigger={['click']}
visible={popVisible}
content={clickContent}
placement="bottomRight"
overlayClassName="d-node-scope-popover large-size"
onVisibleChange={visibleChange}
>
<span className="input-span">
<Input
className={isTop ? 'relativeTime d-node-scope-input' : 'absoluteTime d-node-scope-input'}
value={inputValue}
readOnly={true}
suffix={<IconFont type="icon-jiantou1" rotate={90} style={{ color: '#74788D' }}></IconFont>}
/>
</span>
</Popover>
</div>
<>
<div id="d-node-scope">
<div className="scope-title">{scopeName}</div>
<Popover
trigger={['click']}
visible={popVisible}
content={clickContent}
placement="bottomRight"
overlayClassName={`d-node-scope-popover ${hasCustomScope ? 'large-size' : ''}`}
onVisibleChange={visibleChange}
>
<span className="input-span">
<Input
className={isTop ? 'relativeTime d-node-scope-input' : 'absoluteTime d-node-scope-input'}
value={inputValue}
readOnly={true}
suffix={<IconFont type="icon-jiantou1" rotate={90} style={{ color: '#74788D' }}></IconFont>}
/>
</span>
</Popover>
</div>
</>
);
};

View File

@@ -47,6 +47,7 @@ export interface IcustomScope {
}
export interface InodeScopeModule {
hasCustomScope: boolean;
customScopeList: IcustomScope[];
scopeName?: string;
scopeLabel?: string;
@@ -86,6 +87,7 @@ const GRID_SIZE_OPTIONS = [
const MetricOperateBar = ({
metricSelect,
nodeScopeModule = {
hasCustomScope: false,
customScopeList: [],
},
hideNodeScope = false,

View File

@@ -4,7 +4,7 @@ import { getBasicChartConfig, CHART_COLOR_LIST } from '@src/constants/chartConfi
const METRIC_DASHBOARD_REQ_MAP = {
[MetricType.Broker]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Broker),
[MetricType.Topic]: (clusterId: string) => api.getDashboardMetricChartData(clusterId, MetricType.Topic),
[MetricType.Zookeeper]: (clusterId: string) => api.getZookeeperMetrics(clusterId),
[MetricType.Zookeeper]: (clusterId: string) => '',
};
export const getMetricDashboardReq = (clusterId: string, type: MetricType.Broker | MetricType.Topic | MetricType.Zookeeper) =>

View File

@@ -108,10 +108,10 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
startTime,
endTime,
metricsNames: selectedMetricNames,
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
},
dashboardType === MetricType.Broker || dashboardType === MetricType.Topic
? {
topNu: curHeaderOptions?.scopeData?.isTop ? curHeaderOptions.scopeData.data : null,
[dashboardType === MetricType.Broker ? 'brokerIds' : 'topics']: curHeaderOptions?.scopeData?.isTop
? null
: curHeaderOptions.scopeData.data,
@@ -233,8 +233,8 @@ const DraggableCharts = (props: PropsType): JSX.Element => {
<div id="dashboard-drag-chart" className="topic-dashboard">
<ChartOperateBar
onChange={ksHeaderChange}
hideNodeScope={dashboardType === MetricType.Zookeeper}
nodeScopeModule={{
hasCustomScope: !(dashboardType === MetricType.Zookeeper),
customScopeList: scopeList,
scopeName: dashboardType === MetricType.Broker ? 'Broker' : dashboardType === MetricType.Topic ? 'Topic' : 'Zookeeper',
scopeLabel: `自定义 ${

View File

@@ -1,6 +0,0 @@
.health-state {
img {
width: 100%;
height: 100%;
}
}

View File

@@ -1,76 +0,0 @@
import React from 'react';
import GoodState from '@src/assets/health-good.png';
import MediumState from '@src/assets/health-medium.png';
import PoorState from '@src/assets/health-poor.png';
import DownState from '@src/assets/health-down.png';
import UnknownState from '@src/assets/health-unknown.png';
import GoodStateEmoji from '@src/assets/health-good-emoji.png';
import MediumStateEmoji from '@src/assets/health-medium-emoji.png';
import PoorStateEmoji from '@src/assets/health-poor-emoji.png';
import DownStateEmoji from '@src/assets/health-down-emoji.png';
import './index.less';
export enum HealthStateEnum {
UNKNOWN = -1,
GOOD,
MEDIUM,
POOR,
DOWN,
}
interface HealthStateProps {
state: HealthStateEnum;
width: string | number;
height: string | number;
}
const HEALTH_STATE_MAP = {
[HealthStateEnum.GOOD]: GoodState,
[HealthStateEnum.MEDIUM]: MediumState,
[HealthStateEnum.POOR]: PoorState,
[HealthStateEnum.DOWN]: DownState,
[HealthStateEnum.UNKNOWN]: UnknownState,
};
const HEALTH_STATE_EMOJI_MAP = {
[HealthStateEnum.GOOD]: GoodStateEmoji,
[HealthStateEnum.MEDIUM]: MediumStateEmoji,
[HealthStateEnum.POOR]: PoorStateEmoji,
[HealthStateEnum.DOWN]: DownStateEmoji,
[HealthStateEnum.UNKNOWN]: DownStateEmoji,
};
const HEALTH_STATE_DESC_MAP = {
[HealthStateEnum.GOOD]: '状态优异',
[HealthStateEnum.MEDIUM]: '状态良好',
[HealthStateEnum.POOR]: '状态较差',
[HealthStateEnum.DOWN]: '状态异常',
[HealthStateEnum.UNKNOWN]: '状态异常',
};
export const getHealthStateEmoji = (state: HealthStateEnum, width = 16, height = 16) => {
return (
<img
width={width}
height={height}
style={{ marginTop: -3 }}
src={HEALTH_STATE_EMOJI_MAP[state] || HEALTH_STATE_EMOJI_MAP[HealthStateEnum.UNKNOWN]}
/>
);
};
export const getHealthStateDesc = (state: HealthStateEnum) => {
return HEALTH_STATE_DESC_MAP[state] || HEALTH_STATE_DESC_MAP[HealthStateEnum.UNKNOWN];
};
const HealthState = (props: HealthStateProps) => {
const { state, width, height } = props;
return (
<div className="health-state" style={{ width, height }}>
<img src={HEALTH_STATE_MAP[state] || UnknownState} />
</div>
);
};
export default HealthState;

View File

@@ -1,10 +1,9 @@
import React from 'react';
import { ClustersPermissionMap } from '@src/pages/CommonConfig';
import { ClusterRunState } from '@src/pages/MutliClusterPage/List';
const pkgJson = require('../../package');
export const systemKey = pkgJson.ident;
export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
export const leftMenus = (clusterId?: string) => ({
name: `${systemKey}`,
icon: 'icon-jiqun',
path: `cluster/${clusterId}`,
@@ -12,12 +11,12 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
{
name: 'cluster',
path: 'cluster',
icon: 'icon-Cluster1',
icon: 'icon-Cluster',
},
{
name: 'broker',
path: 'broker',
icon: 'icon-Brokers1',
icon: 'icon-Brokers',
children: [
{
name: 'dashbord',
@@ -39,7 +38,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
{
name: 'topic',
path: 'topic',
icon: 'icon-Topics1',
icon: 'icon-Topics',
children: [
{
name: 'dashbord',
@@ -53,36 +52,10 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
},
],
},
clusterRunState && clusterRunState !== ClusterRunState.Raft
? {
name: (intl: any) => {
return (
<div className="menu-item-with-beta-tag">
<span>{intl.formatMessage({ id: 'menu.cluster.zookeeper' })}</span>
<div className="beta-tag"></div>
</div>
);
},
path: 'zookeeper',
icon: 'icon-Zookeeper',
children: [
{
name: (intl: any) => <span>{intl.formatMessage({ id: 'menu.cluster.zookeeper.dashboard' })}</span>,
path: '',
icon: '#icon-luoji',
},
{
name: (intl: any) => <span>{intl.formatMessage({ id: 'menu.cluster.zookeeper.servers' })}</span>,
path: 'servers',
icon: 'icon-Jobs',
},
],
}
: undefined,
{
name: 'consumer-group',
path: 'consumers',
icon: 'icon-Consumer',
icon: 'icon-ConsumerGroups',
// children: [
// {
// name: 'operating-state',
@@ -99,7 +72,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
{
name: 'operation',
path: 'operation',
icon: 'icon-Operation',
icon: 'icon-Jobs',
children: [
process.env.BUSINESS_VERSION
? {
@@ -119,7 +92,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
? {
name: 'produce-consume',
path: 'testing',
icon: 'icon-Message',
icon: 'icon-a-ProduceConsume',
permissionPoint: [ClustersPermissionMap.TEST_CONSUMER, ClustersPermissionMap.TEST_PRODUCER],
children: [
{
@@ -140,7 +113,7 @@ export const leftMenus = (clusterId?: string, clusterRunState?: number) => ({
{
name: 'security',
path: 'security',
icon: 'icon-Security',
icon: 'icon-ACLs',
children: [
{
name: 'acls',

View File

@@ -259,21 +259,6 @@ li {
}
}
.menu-item-with-beta-tag {
display: flex;
.beta-tag {
width: 26px;
margin-left: 4px;
background: no-repeat center/26px 15px url('./assets/beta-tag.png');
}
}
.dcloud-menu-item-selected .menu-item-with-beta-tag .beta-tag {
width: 0;
}
.empty-panel {
display: flex;
flex-direction: column;

View File

@@ -51,10 +51,6 @@ export default {
[`menu.${systemKey}.jobs`]: 'Job',
[`menu.${systemKey}.zookeeper`]: 'Zookeeper',
[`menu.${systemKey}.zookeeper.dashboard`]: 'Overview',
[`menu.${systemKey}.zookeeper.servers`]: 'Servers',
'access.cluster': '接入集群',
'access.cluster.low.version.tip': '监测到当前Version较低建议维护Zookeeper信息以便得到更好的产品体验',
'edit.cluster': '编辑集群',

View File

@@ -1,11 +0,0 @@
import React from 'react';
import pageNoLicense from '@src/assets/page-no-license.png';
export default () => {
return (
<div className="error-page">
<img width={230} height={150} src={pageNoLicense} />
<div className="title">license </div>
</div>
);
};

View File

@@ -1,14 +0,0 @@
import React from 'react';
import page403 from '@src/assets/page403.png';
export default () => {
return (
<div className="error-page">
<img width={230} height={150} src={page403} />
<div className="title">访</div>
<a className="link" href="/">
</a>
</div>
);
};

View File

@@ -1,15 +0,0 @@
import React from 'react';
import page404 from '@src/assets/page404.png';
export default () => {
return (
<div className="error-page">
<img width={230} height={150} src={page404} />
<div className="title"></div>
<div className="desc"></div>
<a className="link" href="/">
</a>
</div>
);
};

View File

@@ -1,25 +0,0 @@
.error-page {
display: flex;
flex-direction: column;
align-items: center;
height: calc(100% - 48px);
> img {
margin-top: 165px;
}
.title {
margin: 16px 0;
font-family: @font-family-bold;
font-size: 18px;
color: #495057;
line-height: 25px;
}
.desc {
margin-bottom: 12px;
font-size: 13px;
color: #74788d;
font-weight: 400;
}
.link {
font-size: 15px;
}
}

View File

@@ -1,4 +0,0 @@
import './index.less';
export { default as Page403 } from './Page403';
export { default as Page404 } from './Page404';
export { default as NoLicense } from './NoLicense';

View File

@@ -1,14 +1,14 @@
import React, { useEffect, useRef, useState } from 'react';
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer, Tooltip } from 'knowdesign';
import { Slider, Input, Select, Checkbox, Button, Utils, Spin, AppContainer } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import API from '@src/api';
import TourGuide, { MultiPageSteps } from '@src/components/TourGuide';
import { healthSorceList, sliderValueMap, sortFieldList, sortTypes, statusFilters } from './config';
import './index.less';
import { healthSorceList, sortFieldList, sortTypes, statusFilters } from './config';
import ClusterList from './List';
import AccessClusters from './AccessCluster';
import CustomCheckGroup from './CustomCheckGroup';
import { ClustersPermissionMap } from '../CommonConfig';
import './index.less';
const CheckboxGroup = Checkbox.Group;
const { Option } = Select;
@@ -19,17 +19,8 @@ interface ClustersState {
total: number;
}
interface ClustersHealthState {
deadCount: number;
goodCount: number;
mediumCount: number;
poorCount: number;
total: number;
unknownCount: number;
}
export interface SearchParams {
healthState?: number[];
healthScoreRange?: [number, number];
checkedKafkaVersions?: string[];
sortInfo?: {
sortField: string;
@@ -83,24 +74,16 @@ const MultiClusterPage = () => {
liveCount: 0,
total: 0,
});
const [clustersHealthState, setClustersHealthState] = React.useState<ClustersHealthState>();
const [sliderInfo, setSliderInfo] = React.useState<{
value: [number, number];
desc: string;
}>({
value: [0, 5],
desc: '',
});
// TODO: 首次进入因 searchParams 状态变化导致获取两次列表数据的问题
const [searchParams, setSearchParams] = React.useState<SearchParams>({
keywords: '',
checkedKafkaVersions: [],
healthScoreRange: [0, 100],
sortInfo: {
sortField: 'HealthState',
sortField: 'HealthScore',
sortType: 'asc',
},
clusterStatus: [0, 1],
healthState: [-1, 0, 1, 2, 3],
// 是否拉取当前所有数据
isReloadAll: false,
});
@@ -108,28 +91,10 @@ const MultiClusterPage = () => {
const searchKeyword = useRef('');
const isReload = useRef(false);
const getPhyClusterHealthState = () => {
Utils.request(API.phyClusterHealthState).then((res: ClustersHealthState) => {
setClustersHealthState(res || undefined);
const result: string[] = [];
for (let i = 0; i < sliderInfo.value[1] - sliderInfo.value[0]; i++) {
const val = sliderValueMap[(sliderInfo.value[1] - i) as keyof typeof sliderValueMap];
result.push(`${val.name}: ${res?.[val.key as keyof ClustersHealthState]}`);
}
setSliderInfo((cur) => ({
...cur,
desc: result.reverse().join(', '),
}));
});
};
// 获取集群状态
const getPhyClusterState = () => {
getPhyClusterHealthState();
Utils.request(API.phyClusterState)
.then((res: ClustersState) => {
.then((res: any) => {
setStateInfo(res);
})
.finally(() => {
@@ -146,14 +111,13 @@ const MultiClusterPage = () => {
const updateSearchParams = (params: SearchParams) => {
setSearchParams((curParams) => ({ ...curParams, isReloadAll: false, ...params }));
getPhyClusterHealthState();
};
const searchParamsChangeFunc = {
// 健康分改变
onSilderChange: (value: number[]) =>
onSilderChange: (value: [number, number]) =>
updateSearchParams({
healthState: value,
healthScoreRange: value,
}),
// 排序信息改变
onSortInfoChange: (type: string, value: string) =>
@@ -287,41 +251,16 @@ const MultiClusterPage = () => {
</div>
</div>
<div className="header-filter-bottom-item header-filter-bottom-item-slider">
<h3 className="header-filter-bottom-item-title title-right"></h3>
<Tooltip title={sliderInfo.desc} overlayClassName="cluster-health-state-tooltip">
<div className="header-filter-bottom-item-content" id="clusters-slider">
<Slider
dots
range={{ draggableTrack: true }}
step={1}
max={5}
marks={healthSorceList}
value={sliderInfo.value}
tooltipVisible={false}
onChange={(value: [number, number]) => {
if (value[0] !== value[1]) {
const result = [];
for (let i = 0; i < value[1] - value[0]; i++) {
const val = sliderValueMap[(value[1] - i) as keyof typeof sliderValueMap];
result.push(`${val.name}: ${clustersHealthState?.[val.key as keyof ClustersHealthState]}`);
}
setSliderInfo({
value,
desc: result.reverse().join(', '),
});
}
}}
onAfterChange={(value: [number, number]) => {
const result = [];
for (let i = 0; i < value[1] - value[0]; i++) {
const val = sliderValueMap[(value[1] - i) as keyof typeof sliderValueMap];
result.push(val.code);
}
searchParamsChangeFunc.onSilderChange(result);
}}
/>
</div>
</Tooltip>
<h3 className="header-filter-bottom-item-title title-right"></h3>
<div className="header-filter-bottom-item-content">
<Slider
range
step={20}
defaultValue={[0, 100]}
marks={healthSorceList}
onAfterChange={searchParamsChangeFunc.onSilderChange}
/>
</div>
</div>
</div>
</div>
@@ -330,7 +269,7 @@ const MultiClusterPage = () => {
<div className="multi-cluster-filter-select">
<Select
onChange={(value) => searchParamsChangeFunc.onSortInfoChange('sortField', value)}
defaultValue="HealthState"
defaultValue="HealthScore"
style={{ width: 170, marginRight: 12 }}
>
{sortFieldList.map((item) => (

View File

@@ -10,15 +10,15 @@ import { timeFormat, oneDayMillims } from '@src/constants/common';
import { IMetricPoint, linesMetric, pointsMetric } from './config';
import { useIntl } from 'react-intl';
import api, { MetricType } from '@src/api';
import { getHealthClassName, getHealthProcessColor, getHealthText } from '../SingleClusterDetail/config';
import { ClustersPermissionMap } from '../CommonConfig';
import { getDataUnit } from '@src/constants/chartConfig';
import SmallChart from '@src/components/SmallChart';
import HealthState, { HealthStateEnum } from '@src/components/HealthState';
import { SearchParams } from './HomePage';
const DEFAULT_PAGE_SIZE = 10;
export enum ClusterRunState {
enum ClusterRunState {
Raft = 2,
}
@@ -165,9 +165,12 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
fieldName: 'kafkaVersion',
fieldValueList: searchParams.checkedKafkaVersions as (string | number)[],
},
],
rangeFilterDTOList: [
{
fieldName: 'HealthState',
fieldValueList: searchParams.healthState,
fieldMaxValue: searchParams.healthScoreRange[1],
fieldMinValue: searchParams.healthScoreRange[0],
fieldName: 'HealthScore',
},
],
searchKeywords: searchParams.keywords,
@@ -254,7 +257,7 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
Zookeepers: zks,
HealthCheckPassed: healthCheckPassed,
HealthCheckTotal: healthCheckTotal,
HealthState: healthState,
HealthScore: healthScore,
ZookeepersAvailable: zookeepersAvailable,
LoadReBalanceCpu: loadReBalanceCpu,
LoadReBalanceDisk: loadReBalanceDisk,
@@ -269,16 +272,28 @@ const ClusterList = (props: { searchParams: SearchParams; showAccessCluster: any
history.push(`/cluster/${itemData.id}/cluster`);
}}
>
<div className="multi-cluster-list-item">
<div className={'multi-cluster-list-item'}>
<div className="multi-cluster-list-item-healthy">
<div className="healthy-box">
<HealthState state={healthState} width={70} height={70} />
<div className="healthy-degree">
<span className="healthy-degree-status"></span>
<span className="healthy-degree-proportion">
{healthCheckPassed}/{healthCheckTotal}
</span>
</div>
<Progress
type="circle"
status={!itemData.alive ? 'exception' : healthScore >= 90 ? 'success' : 'normal'}
strokeWidth={4}
// className={healthScore > 90 ? 'green-circle' : ''}
className={+itemData.alive <= 0 ? 'red-circle' : +healthScore < 90 ? 'blue-circle' : 'green-circle'}
strokeColor={getHealthProcessColor(healthScore, itemData.alive)}
percent={itemData.alive ? healthScore : 100}
format={() => (
<div className={`healthy-percent ${getHealthClassName(healthScore, itemData?.alive)}`}>
{getHealthText(healthScore, itemData?.alive)}
</div>
)}
width={70}
/>
<div className="healthy-degree">
<span className="healthy-degree-status"></span>
<span className="healthy-degree-proportion">
{healthCheckPassed}/{healthCheckTotal}
</span>
</div>
</div>
<div className="multi-cluster-list-item-right">

View File

@@ -1,4 +1,3 @@
import { HealthStateEnum } from '@src/components/HealthState';
import { FormItemType, IFormItem } from 'knowdesign/es/extend/x-form';
export const bootstrapServersErrCodes = [10, 11, 12];
@@ -22,8 +21,8 @@ export const sortFieldList = [
value: 'createTime',
},
{
label: '健康状态',
value: 'HealthState',
label: '健康',
value: 'HealthScore',
},
{
label: 'Messages',
@@ -72,41 +71,18 @@ export const metricNameMap = {
[key: string]: string;
};
export const sliderValueMap = {
1: {
code: HealthStateEnum.GOOD,
key: 'goodCount',
name: '好',
},
2: {
code: HealthStateEnum.MEDIUM,
key: 'mediumCount',
name: '中',
},
3: {
code: HealthStateEnum.POOR,
key: 'poorCount',
name: '差',
},
4: {
code: HealthStateEnum.DOWN,
key: 'deadCount',
name: 'Down',
},
5: {
code: HealthStateEnum.UNKNOWN,
key: 'unknownCount',
name: 'Unknown',
},
};
export const healthSorceList = {
0: '',
1: '',
2: '中',
3: '',
4: 'Down',
5: 'Unknown',
0: 0,
10: '',
20: 20,
30: '',
40: 40,
50: '',
60: 60,
70: '',
80: 80,
90: '',
100: 100,
};
export interface IMetricPoint {

View File

@@ -1,17 +1,5 @@
@error-color: #f46a6a;
.cluster-health-state-tooltip {
.dcloud-tooltip-arrow,
.dcloud-tooltip-inner {
margin-bottom: -10px;
}
.dcloud-tooltip-inner {
padding: 2px 4px;
min-height: 25px;
border-radius: 4px;
}
}
.multi-cluster-page {
position: absolute;
left: 0;
@@ -241,9 +229,6 @@
&-slider {
width: 298px;
padding-right: 20px;
.dcloud-slider-mark {
left: -27px;
}
}
&-title {
@@ -419,29 +404,59 @@
transition: 0.5s all;
&-healthy {
display: flex;
align-items: center;
margin-right: 24px;
.healthy-box {
position: relative;
height: 70px;
margin-top: 2px;
.dcloud-progress-inner {
border-radius: 50%;
}
.green-circle {
.dcloud-progress-inner {
background: #f5fdfc;
}
}
.red-circle {
.dcloud-progress-inner {
background: #fffafa;
}
}
.healthy-percent {
font-family: DIDIFD-Regular;
font-size: 40px;
text-align: center;
line-height: 36px;
color: #00c0a2;
&.less-90 {
color: @primary-color;
}
&.no-info {
color: #e9e7e7;
}
&.down {
font-family: @font-family-bold;
font-size: 22px;
color: #ff7066;
text-align: center;
line-height: 30px;
}
}
.healthy-degree {
position: absolute;
bottom: 0;
width: 100%;
font-family: @font-family-bold;
font-size: 12px;
color: #495057;
line-height: 1;
line-height: 15px;
margin-top: 6px;
text-align: center;
&-status {
margin-right: 6px;
color: #74788d;
}
&-proportion {
}
}
}

View File

@@ -0,0 +1,4 @@
import { Result } from 'knowdesign';
import React from 'react';
export default () => <Result status="403" title="No License" subTitle="很抱歉,您的 Licence 无法使用" />;

View File

@@ -6,27 +6,16 @@ import { useIntl } from 'react-intl';
import { getHealthySettingColumn } from './config';
import API from '../../api';
import { useParams } from 'react-router-dom';
import notification from '@src/components/Notification';
interface HealthConfig {
dimensionCode: number;
dimensionName: string;
configGroup: string;
configItem: string;
configName: string;
configDesc: string;
value: string;
}
const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
const intl = useIntl();
const [form] = Form.useForm();
const { clusterId } = useParams<{ clusterId: string }>();
const [visible, setVisible] = useState(false);
const [initialValues, setInitialValues] = useState({} as any);
const [data, setData] = React.useState<HealthConfig[]>([]);
const [data, setData] = React.useState([]);
const { clusterId } = useParams<{ clusterId: string }>();
React.useImperativeHandle(ref, () => ({
setVisible,
@@ -34,25 +23,29 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
}));
const getHealthconfig = () => {
return Utils.request(API.getClusterHealthyConfigs(+clusterId)).then((res: HealthConfig[]) => {
return Utils.request(API.getClusterHealthyConfigs(+clusterId)).then((res: any) => {
const values = {} as any;
res.sort((a, b) => a.dimensionCode - b.dimensionCode);
try {
res.forEach((item) => {
res = res.map((item: any) => {
const itemValue = JSON.parse(item.value);
const { value, latestMinutes, detectedTimes, amount, ratio } = itemValue;
item.weight = itemValue?.weight;
value && (values[`value_${item.configItem}`] = value);
latestMinutes && (values[`latestMinutes_${item.configItem}`] = latestMinutes);
detectedTimes && (values[`detectedTimes_${item.configItem}`] = detectedTimes);
amount && (values[`amount_${item.configItem}`] = amount);
ratio && (values[`ratio_${item.configItem}`] = ratio);
item.configItemName =
item.configItem.indexOf('Group Re-Balance') > -1
? 'ReBalance'
: item.configItem.includes('副本未同步')
? 'UNDER_REPLICA'
: item.configItem;
values[`weight_${item.configItemName}`] = itemValue?.weight;
values[`value_${item.configItemName}`] = itemValue?.value;
values[`latestMinutes_${item.configItemName}`] = itemValue?.latestMinutes;
values[`detectedTimes_${item.configItemName}`] = itemValue?.detectedTimes;
return item;
});
} catch (err) {
notification.error({
message: '健康项检查规则解析失败',
});
//
}
const formItemsValue = {
...initialValues,
@@ -77,11 +70,10 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
clusterId: +clusterId,
value: JSON.stringify({
clusterPhyId: +clusterId,
detectedTimes: res[`detectedTimes_${item.configItem}`],
latestMinutes: res[`latestMinutes_${item.configItem}`],
amount: res[`amount_${item.configItem}`],
ratio: res[`ratio_${item.configItem}`],
value: item.configItem === 'Controller' ? 1 : res[`value_${item.configItem}`],
detectedTimes: res[`detectedTimes_${item.configItemName}`],
latestMinutes: res[`latestMinutes_${item.configItemName}`],
weight: res[`weight_${item.configItemName}`],
value: item.configItemName === 'Controller' ? 1 : res[`value_${item.configItemName}`],
}),
valueGroup: item.configGroup,
valueName: item.configName,
@@ -128,7 +120,7 @@ const HealthySetting = React.forwardRef((props: any, ref): JSX.Element => {
<Form form={form} layout="vertical" onValuesChange={onHandleValuesChange}>
<ProTable
tableProps={{
rowKey: 'configItem',
rowKey: 'dimensionCode',
showHeader: false,
dataSource: data,
columns: getHealthySettingColumn(form, data, clusterId),

View File

@@ -1,4 +1,4 @@
import { AppContainer, Divider, Tooltip, Utils } from 'knowdesign';
import { AppContainer, Divider, Progress, Tooltip, Utils } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import React, { useEffect, useState } from 'react';
import AccessClusters from '../MutliClusterPage/AccessCluster';
@@ -7,9 +7,8 @@ import API from '../../api';
import HealthySetting from './HealthySetting';
import CheckDetail from './CheckDetail';
import { Link, useHistory, useParams } from 'react-router-dom';
import { renderToolTipValue } from './config';
import { getHealthClassName, getHealthProcessColor, getHealthState, getHealthText, renderToolTipValue } from './config';
import { ClustersPermissionMap } from '../CommonConfig';
import HealthState, { getHealthStateDesc, getHealthStateEmoji } from '@src/components/HealthState';
const LeftSider = () => {
const [global] = AppContainer.useGlobalValue();
@@ -41,7 +40,6 @@ const LeftSider = () => {
return Utils.post(
API.getPhyClusterMetrics(+clusterId),
[
'HealthState',
'HealthScore',
'HealthCheckPassed',
'HealthCheckTotal',
@@ -104,12 +102,23 @@ const LeftSider = () => {
<>
<div className="left-sider">
<div className="state-card">
<HealthState state={clusterMetrics?.HealthState} width={74} height={74} />
<Progress
type="circle"
status="active"
strokeWidth={4}
strokeColor={getHealthProcessColor(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}
percent={clusterMetrics?.HealthScore ?? '-'}
className={+clusterMetrics.Alive <= 0 ? 'red-circle' : +clusterMetrics?.HealthScore < 90 ? 'blue-circle' : 'green-circle'}
format={() => (
<div className={`healthy-percent ${getHealthClassName(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}`}>
{getHealthText(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}
</div>
)}
width={75}
/>
<div className="healthy-state">
<div className="healthy-state-status">
<span>
{getHealthStateEmoji(clusterMetrics?.HealthState)} {getHealthStateDesc(clusterMetrics?.HealthState)}
</span>
<span>{getHealthState(clusterMetrics?.HealthScore, clusterMetrics?.Alive)}</span>
{/* 健康分设置 */}
{global.hasPermission && global.hasPermission(ClustersPermissionMap.CLUSTER_CHANGE_HEALTHY) ? (
<span

View File

@@ -7,15 +7,30 @@ import { IconFont } from '@knowdesign/icons';
import { Link } from 'react-router-dom';
import { systemKey } from '../../constants/menu';
const statusTxtEmojiMap = {
success: {
emoji: '👍',
txt: '优异',
},
normal: {
emoji: '😊',
txt: '正常',
},
exception: {
emoji: '👻',
txt: '异常',
},
};
export const dimensionMap = {
// '-1': {
// label: 'Unknown',
// href: ``,
// },
// 0: {
// label: 'Cluster',
// href: ``,
// },
'-1': {
label: 'Unknown',
href: ``,
},
0: {
label: 'Cluster',
href: ``,
},
1: {
label: 'Broker',
href: `/broker`,
@@ -28,100 +43,30 @@ export const dimensionMap = {
label: 'ConsumerGroup',
href: `/consumers`,
},
4: {
label: 'Zookeeper',
href: '/zookeeper',
},
} as any;
const toLowerCase = (name = '') => {
const [first, ...rest] = name.split('');
return first.toUpperCase() + rest.join('').toLowerCase();
};
const CONFIG_ITEM_DETAIL_DESC = {
Controller: () => {
return '集群 Controller 数等于 1';
},
RequestQueueSize: (valueGroup: any) => {
return `Broker-RequestQueueSize 小于 ${valueGroup?.value}`;
},
NoLeader: (valueGroup: any) => {
return `Topic 无 Leader 数小于 ${valueGroup?.value}`;
},
NetworkProcessorAvgIdlePercent: (valueGroup: any) => {
return `Broker-NetworkProcessorAvgIdlePercent 的 idle 大于 ${valueGroup?.value * 100}%`;
},
UnderReplicaTooLong: (valueGroup: any) => {
return `Topic 小于 ${parseFloat(((valueGroup?.detectedTimes / valueGroup?.latestMinutes) * 100).toFixed(2))}% 周期处于未同步状态`;
},
'Group Re-Balance': (valueGroup: any) => {
return `Consumer Group 小于 ${parseFloat(
((valueGroup?.detectedTimes / valueGroup?.latestMinutes) * 100).toFixed(2)
)}% 周期处于 Re-balance 状态`;
},
BrainSplit: () => {
return `Zookeeper 未脑裂`;
},
OutstandingRequests: (valueGroup: any) => {
return `Zookeeper 请求堆积数小于 ${valueGroup?.ratio * 100}% 总容量`;
},
WatchCount: (valueGroup: any) => {
return `Zookeeper 订阅数小于 ${valueGroup?.ratio * 100}% 总容量`;
},
AliveConnections: (valueGroup: any) => {
return `Zookeeper 连接数小于 ${valueGroup?.ratio * 100}% 总容量`;
},
ApproximateDataSize: (valueGroup: any) => {
return `Zookeeper 数据大小小于 ${valueGroup?.ratio * 100}% 总容量`;
},
SentRate: (valueGroup: any) => {
return `Zookeeper 首发包数小于 ${valueGroup?.ratio * 100}% 总容量`;
},
};
export const getConfigItemDetailDesc = (item: keyof typeof CONFIG_ITEM_DETAIL_DESC, valueGroup: any) => {
return CONFIG_ITEM_DETAIL_DESC[item]?.(valueGroup);
};
const getFormItem = (params: { configItem: string; type?: string; percent?: boolean; attrs?: any; validator?: any }) => {
const { validator, configItem, percent, type = 'value', attrs = { min: 0 } } = params;
export const getHealthState = (value: number, down: number) => {
if (value === undefined) return '-';
const progressStatus = +down <= 0 ? 'exception' : value >= 90 ? 'success' : 'normal';
return (
<Form.Item
name={`${type}_${configItem}`}
label=""
rules={
validator
? [
{
required: true,
validator: validator,
},
]
: [
{
required: true,
message: '请输入',
},
]
}
>
{percent ? (
<InputNumber
size="small"
min={0}
max={1}
style={{ width: 86 }}
formatter={(value) => `${value * 100}%`}
parser={(value: any) => parseFloat(value.replace('%', '')) / 100}
/>
) : (
<InputNumber style={{ width: 86 }} size="small" {...attrs} />
)}
</Form.Item>
<span>
{statusTxtEmojiMap[progressStatus].emoji}&nbsp;{statusTxtEmojiMap[progressStatus].txt}
</span>
);
};
export const getHealthText = (value: number, down: number) => {
return +down <= 0 ? 'Down' : value ? value.toFixed(0) : '-';
};
export const getHealthProcessColor = (value: number, down: number) => {
return +down <= 0 ? '#FF7066' : +value < 90 ? '#556EE6' : '#00C0A2';
};
export const getHealthClassName = (value: number, down: number) => {
return +down <= 0 ? 'down' : value === undefined ? 'no-info' : +value < 90 ? 'less-90' : '';
};
export const renderToolTipValue = (value: string, num: number) => {
return (
<>
@@ -143,40 +88,48 @@ export const getDetailColumn = (clusterId: number) => [
title: '检查模块',
dataIndex: 'dimension',
// eslint-disable-next-line react/display-name
render: (text: number, record: any) => {
return dimensionMap[text] ? (
<Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{toLowerCase(record?.dimensionName)}</Link>
) : (
toLowerCase(record?.dimensionName)
);
render: (text: number) => {
if (text === 0 || text === -1) return dimensionMap[text]?.label;
return <Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{dimensionMap[text]?.label}</Link>;
},
},
{
title: '检查项',
dataIndex: 'checkConfig',
render(config: any, record: any) {
let valueGroup = {};
try {
valueGroup = JSON.parse(config.value);
} catch (e) {
//
const valueGroup = JSON.parse(config.value);
if (record.configItem === 'Controller') {
return '集群 Controller 数等于 1';
} else if (record.configItem === 'RequestQueueSize') {
return `Broker-RequestQueueSize 小于 ${valueGroup.value}`;
} else if (record.configItem === 'NoLeader') {
return `Topic 无 Leader 数小于 ${valueGroup.value}`;
} else if (record.configItem === 'NetworkProcessorAvgIdlePercent') {
return `Broker-NetworkProcessorAvgIdlePercent 的 idle 大于 ${valueGroup.value}%`;
} else if (record.configItem === 'UnderReplicaTooLong') {
return `Topic 小于 ${parseFloat(((valueGroup.detectedTimes / valueGroup.latestMinutes) * 100).toFixed(2))}% 周期处于未同步状态`;
} else if (record.configItem === 'Group Re-Balance') {
return `Consumer Group 小于 ${parseFloat(
((valueGroup.detectedTimes / valueGroup.latestMinutes) * 100).toFixed(2)
)}% 周期处于 Re-balance 状态`;
}
return getConfigItemDetailDesc(record.configItem, valueGroup) || record.configDesc || '-';
return <></>;
},
},
// {
// title: '权重',
// dataIndex: 'weightPercent',
// width: 80,
// render(value: number) {
// return `${value}%`;
// },
// },
// {
// title: '得分',
// dataIndex: 'score',
// width: 60,
// },
{
title: '权重',
dataIndex: 'weightPercent',
width: 80,
render(value: number) {
return `${value}%`;
},
},
{
title: '得分',
dataIndex: 'score',
width: 60,
},
{
title: '检查时间',
width: 190,
@@ -215,160 +168,168 @@ export const getHealthySettingColumn = (form: any, data: any, clusterId: string)
{
title: '检查模块',
dataIndex: 'dimensionCode',
width: 140,
// eslint-disable-next-line react/display-name
render: (text: number, record: any) => {
return dimensionMap[text] ? (
<Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{toLowerCase(record?.dimensionName)}</Link>
) : (
toLowerCase(record?.dimensionName)
);
render: (text: number) => {
if (text === 0 || text === -1) return dimensionMap[text]?.label;
return <Link to={`/${systemKey}/${clusterId}${dimensionMap[text]?.href}`}>{dimensionMap[text]?.label}</Link>;
},
},
{
title: '检查项',
dataIndex: 'configItem',
width: 230,
width: 200,
needTooltip: true,
},
{
title: '检查项描述',
dataIndex: 'configDesc',
width: 310,
width: 240,
needToolTip: true,
},
// {
// title: '权重',
// dataIndex: 'weight',
// // width: 180,
// // eslint-disable-next-line react/display-name
// render: (text: number, record: any) => {
// return (
// <>
// <Form.Item
// name={`weight_${record.configItemName}`}
// label=""
// rules={[
// {
// required: true,
// validator: async (rule: any, value: string) => {
// const otherWeightCongigName: string[] = [];
// let totalPercent = 0;
// data.map((item: any) => {
// if (item.configItemName !== record.configItemName) {
// otherWeightCongigName.push(`weight_${item.configItemName}`);
// totalPercent += form.getFieldValue(`weight_${item.configItemName}`) ?? 0;
// }
// });
// if (!value) {
// return Promise.reject('请输入权重');
// }
// if (+value < 0) {
// return Promise.reject('最小为0');
// }
// if (+value + totalPercent !== 100) {
// return Promise.reject('总和应为100%');
// }
// form.setFields(otherWeightCongigName.map((i) => ({ name: i, errors: [] })));
// return Promise.resolve('');
// },
// },
// ]}
// >
// <InputNumber
// size="small"
// min={0}
// max={100}
// formatter={(value) => `${value}%`}
// parser={(value: any) => value.replace('%', '')}
// />
// </Form.Item>
// </>
// );
// },
// },
{
title: '权重',
dataIndex: 'weight',
// width: 180,
// eslint-disable-next-line react/display-name
render: (text: number, record: any) => {
return (
<>
<Form.Item
name={`weight_${record.configItemName}`}
label=""
rules={[
{
required: true,
validator: async (rule: any, value: string) => {
const otherWeightCongigName: string[] = [];
let totalPercent = 0;
data.map((item: any) => {
if (item.configItemName !== record.configItemName) {
otherWeightCongigName.push(`weight_${item.configItemName}`);
totalPercent += form.getFieldValue(`weight_${item.configItemName}`) ?? 0;
}
});
if (!value) {
return Promise.reject('请输入权重');
}
if (+value < 0) {
return Promise.reject('最小为0');
}
if (+value + totalPercent !== 100) {
return Promise.reject('总和应为100%');
}
form.setFields(otherWeightCongigName.map((i) => ({ name: i, errors: [] })));
return Promise.resolve('');
},
},
]}
>
<InputNumber
size="small"
min={0}
max={100}
formatter={(value) => `${value}%`}
parser={(value: any) => value.replace('%', '')}
/>
</Form.Item>
</>
);
},
},
{
title: '检查规则',
// width: 350,
dataIndex: 'passed',
// eslint-disable-next-line react/display-name
render: (text: any, record: any) => {
const configItem = record.configItem;
const getFormItem = (params: { type?: string; percent?: boolean; attrs?: any; validator?: any }) => {
const { validator, percent, type = 'value', attrs = { min: 0 } } = params;
return (
<Form.Item
name={`${type}_${record.configItemName}`}
label=""
rules={
validator
? [
{
required: true,
validator: validator,
},
]
: [
{
required: true,
message: '请输入',
},
]
}
>
{percent ? (
<InputNumber
size="small"
min={0}
max={1}
style={{ width: 86 }}
formatter={(value) => `${value * 100}%`}
parser={(value: any) => parseFloat(value.replace('%', '')) / 100}
/>
) : (
<InputNumber style={{ width: 86 }} size="small" {...attrs} />
)}
</Form.Item>
);
};
switch (configItem) {
case 'Controller': {
return <div className="table-form-item"> 1 </div>;
}
case 'BrainSplit': {
return <div className="table-form-item"></div>;
}
case 'RequestQueueSize':
case 'NoLeader': {
return (
<div className="table-form-item">
<span className="left-text"></span>
{getFormItem({ configItem, attrs: { min: 0, max: 99998 } })}
<span className="right-text"></span>
</div>
);
}
case 'SentRate':
case 'WatchCount':
case 'AliveConnections':
case 'ApproximateDataSize':
case 'OutstandingRequests': {
return (
<div className="table-form-item">
<span className="left-text"></span>
{getFormItem({ configItem, type: 'amount' })}
<span className="left-text">, </span>
{getFormItem({ configItem, type: 'ratio', percent: true })}
<span className="right-text"></span>
</div>
);
}
case 'NetworkProcessorAvgIdlePercent': {
return (
<div className="table-form-item">
<span className="left-text"></span>
{getFormItem({ configItem, percent: true })}
<span className="right-text"></span>
</div>
);
}
case 'UnderReplicaTooLong':
case 'Group Re-Balance': {
return (
<div className="table-form-item">
{getFormItem({ type: 'latestMinutes', configItem, attrs: { min: 1, max: 10080 } })}
<span className="right-text left-text"></span>
{getFormItem({
type: 'detectedTimes',
configItem,
attrs: { min: 1, max: 10080 },
validator: async (rule: any, value: string) => {
const latestMinutesValue = form.getFieldValue(`latestMinutes_${configItem}`);
if (!value) {
return Promise.reject('请输入');
}
if (+value < 1) {
return Promise.reject('最小为1');
}
if (+value > +latestMinutesValue) {
return Promise.reject('值不能大于周期');
}
return Promise.resolve('');
},
})}
<span className="right-text"></span>
</div>
);
}
default: {
return <></>;
}
if (record.configItemName === 'Controller') {
return <div className="table-form-item"> 1 </div>;
}
if (record.configItemName === 'RequestQueueSize' || record.configItemName === 'NoLeader') {
return (
<div className="table-form-item">
<span className="left-text"></span>
{getFormItem({ attrs: { min: 0, max: 99998 } })}
<span className="right-text"></span>
</div>
);
}
if (record.configItemName === 'NetworkProcessorAvgIdlePercent') {
return (
<div className="table-form-item">
<span className="left-text"></span>
{getFormItem({ percent: true })}
<span className="right-text"></span>
</div>
);
}
if (record.configItemName === 'UnderReplicaTooLong' || record.configItemName === 'ReBalance') {
return (
<div className="table-form-item">
{getFormItem({ type: 'latestMinutes', attrs: { min: 1, max: 10080 } })}
<span className="right-text left-text"></span>
{getFormItem({
type: 'detectedTimes',
attrs: { min: 1, max: 10080 },
validator: async (rule: any, value: string) => {
const latestMinutesValue = form.getFieldValue(`latestMinutes_${record.configItemName}`);
if (!value) {
return Promise.reject('请输入');
}
if (+value < 1) {
return Promise.reject('最小为1');
}
if (+value > +latestMinutesValue) {
return Promise.reject('值不能大于周期');
}
return Promise.resolve('');
},
})}
<span className="right-text"></span>
</div>
);
}
return <></>;
},
},
] as any;

View File

@@ -195,14 +195,15 @@
}
.healthy-state {
margin-left: 10px;
margin-left: 14px;
margin-top: 8px;
&-status {
font-size: 13px;
color: #495057;
letter-spacing: 0;
line-height: 20px;
margin-bottom: 10px;
margin-bottom: 13px;
.icon {
margin-left: 4px;
@@ -224,7 +225,7 @@
}
.dcloud-divider-horizontal {
margin: 0 16px 14px 0;
margin: 16px 4px;
padding: 0px 20px;
}
}

View File

@@ -1,60 +0,0 @@
import React, { useState } from 'react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import SwitchTab from '@src/components/SwitchTab';
const isJSON = (str: string) => {
if (typeof str == 'string') {
try {
JSON.parse(str);
return true;
} catch (e) {
return false;
}
} else {
return false;
}
};
const ZKData = ({ nodeData }: { nodeData: string }) => {
const [showMode, setShowMode] = useState('default');
return (
<>
<div className="zk-detail-layout-right-content-format">
<SwitchTab defaultKey={showMode} onChange={(key) => setShowMode(key)}>
<SwitchTab.TabItem key="default">
<div style={{ padding: '0 10px' }}></div>
</SwitchTab.TabItem>
<SwitchTab.TabItem key="JSON">
<div style={{ padding: '0 10px' }}>JSON格式</div>
</SwitchTab.TabItem>
</SwitchTab>
</div>
{showMode === 'default' && (
<div className={'zk-detail-layout-right-content-data'}>
{isJSON(nodeData) ? JSON.stringify(JSON.parse(nodeData), null, 2) : nodeData}
</div>
)}
{showMode === 'JSON' && (
<div className={'zk-detail-layout-right-content-code'}>
<CodeMirror
className={'zk-detail-layout-right-content-code-data'}
value={isJSON(nodeData) ? JSON.stringify(JSON.parse(nodeData), null, 2) : nodeData}
options={{
mode: 'application/json',
lineNumbers: true,
lineWrapper: true,
autoCloseBrackets: true,
smartIndent: true,
tabSize: 2,
}}
onBeforeChange={() => {
return;
}}
/>
</div>
)}
</>
);
};
export default ZKData;

View File

@@ -1,188 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Drawer, Utils, AppContainer, Spin, Empty } from 'knowdesign';
import ZKDetailMenu from './Sider';
import Api from '@src/api';
import ZKInfo from './Info';
import ZKData from './Data';
const { request } = Utils;
import './index.less';
import { DataNode } from './config';
const ZookeeperDetail = ({ visible, setVisible }: { visible: boolean; setVisible: (visible: boolean) => void }) => {
const [global] = AppContainer.useGlobalValue();
// const { visible, setVisible } = props;
const [detailLoading, setDetailLoading] = useState(true);
const [isDetail, setIsDetail] = useState(true);
const [detailInfoLoading, setDetailInfoLoading] = useState(false);
const [pathList, setPathList] = useState([]);
const [node, setNode] = useState<any>({});
const [idenKey, setIdenKey] = useState([]);
const [siderWidth, setSiderWidth] = useState(200);
const [startPageX, setStartPageX] = useState(0);
const [dragging, setDragging] = useState(false);
const [detailTreeData, setDetailTreeData] = useState([]);
const onClose = () => {
setVisible(false);
setPathList([]);
setNode({});
setIdenKey([]);
};
const siderMouseDown = (e: { pageX: number }) => {
setStartPageX(e.pageX);
setDragging(true);
};
const siderMouseMove = (e: { pageX: number }) => {
const currentSiderWidth = siderWidth + e.pageX - startPageX;
if (currentSiderWidth < 200) {
setSiderWidth(200);
} else if (currentSiderWidth > 320) {
setSiderWidth(320);
} else {
setSiderWidth(currentSiderWidth);
}
setStartPageX(e.pageX);
};
const siderMouseUp = () => {
setDragging(false);
};
const rootClick = () => {
setPathList([]);
setIdenKey([]);
};
useEffect(() => {
// 第一次加载不触发详情信息的loading
!detailLoading && setDetailInfoLoading(true);
visible &&
request(Api.getZookeeperNodeData(+global?.clusterInfo?.id), {
params: { path: '/' + pathList.map((item: DataNode) => item.title).join('/') },
})
.then((res) => {
setNode(res || {});
})
.catch(() => {
setNode({});
})
.finally(() => {
setDetailInfoLoading(false);
});
}, [pathList]);
useEffect(() => {
setDetailLoading(true);
visible &&
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: { path: '/', keyword: '' } })
// zkDetailInfo()
.then((res: string[]) => {
const newData =
res && res.length > 0
? res.map((item: string, index: number) => {
return {
title: item,
key: `${index}`,
};
})
: [];
if (newData.length > 0) {
setIsDetail(false);
setDetailTreeData(newData);
}
})
.finally(() => {
setDetailLoading(false);
});
}, [visible]);
return (
<Drawer
push={false}
title={'Zookeeper 详情'}
width={1080}
placement="right"
onClose={onClose}
visible={visible}
className="zookeeper-detail-drawer"
destroyOnClose
maskClosable={false}
>
<Spin spinning={detailLoading}>
{isDetail ? (
<div className="zk-detail-empty">
<Empty image={Empty.PRESENTED_IMAGE_CUSTOM} description="暂无数据" />
</div>
) : (
<div className="zk-detail-layout">
<div className="zk-detail-layout-left" style={{ width: siderWidth + 'px' }}>
<div className="zk-detail-layout-left-title"></div>
<div className="zk-detail-layout-left-content">
{visible && (
<ZKDetailMenu
setDetailInfoLoading={setDetailInfoLoading}
detailTreeData={detailTreeData}
setPathList={setPathList}
setIdenKey={setIdenKey}
idenKey={idenKey}
/>
)}
</div>
</div>
<div className="zk-detail-layout-resizer" style={{ left: siderWidth + 'px' }} onMouseDown={siderMouseDown}>
{dragging && <div className="resize-mask" onMouseMove={siderMouseMove} onMouseUp={siderMouseUp} />}
</div>
<div className="zk-detail-layout-right">
<div className="zk-detail-layout-right-title"></div>
<div className="zk-detail-layout-right-content">
{visible && (
<Spin spinning={detailInfoLoading}>
<div className="zk-detail-layout-right-content-countheight">
<div className="zk-detail-layout-right-content-path">
<span onClick={rootClick}>{node.namespace}</span>
{pathList.length > 0 &&
pathList.map((item, index) => {
if (item.key === idenKey[0]) {
return (
<span key={index}>
{' '}
/
<a key={index} onClick={() => setIdenKey([item.key])}>
{item.title}
</a>
</span>
);
}
return (
<span key={index}>
{' '}
/
<span key={index} onClick={() => setIdenKey([item.key])}>
{item.title}
</span>
</span>
);
})}
</div>
{/* <div>{'/' + pathList.map((item: any) => item.title).join(' / ') || '/'}</div> */}
<div className="zk-detail-layout-right-content-info">
<ZKInfo siderWidth={siderWidth} nodeInfo={node?.stat || {}} />
</div>
<ZKData nodeData={node?.data || ''} />
</div>
</Spin>
)}
</div>
</div>
</div>
)}
</Spin>
</Drawer>
);
};
export default ZookeeperDetail;

View File

@@ -1,61 +0,0 @@
import React from 'react';
import { Descriptions } from 'knowdesign';
import moment from 'moment';
const ZKInfo = ({ nodeInfo, siderWidth }: any) => {
const smallStyle = {
width: 82,
};
return (
<Descriptions
style={{ fontSize: '13px' }}
column={siderWidth >= 270 ? 2 : 3}
labelStyle={{
display: 'flex',
textAlign: 'right',
justifyContent: 'end',
color: '#74788D',
fontSize: '13px',
width: siderWidth >= 270 ? 144 : 100,
}}
contentStyle={{ fontSize: '13px' }}
>
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="aversion">
{nodeInfo.aversion || nodeInfo.aversion === 0 ? nodeInfo.aversion : '-'}
</Descriptions.Item>
<Descriptions.Item label="ctime">{nodeInfo.ctime || nodeInfo.ctime === 0 ? nodeInfo.ctime : '-'}</Descriptions.Item>
<Descriptions.Item label="ctime-pretty">
{nodeInfo.ctime ? moment(nodeInfo.ctime).format('YYYY-MM-DD HH:mm:ss') : '-'}
</Descriptions.Item>
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="cversion">
{nodeInfo.cversion || nodeInfo.cversion === 0 ? nodeInfo.cversion : '-'}
</Descriptions.Item>
<Descriptions.Item label="czxid">{nodeInfo.czxid || nodeInfo.czxid === 0 ? nodeInfo.czxid : '-'}</Descriptions.Item>
<Descriptions.Item label="dataLength">
{nodeInfo.dataLength || nodeInfo.dataLength === 0 ? nodeInfo.dataLength : '-'}
</Descriptions.Item>
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="mtime">
{nodeInfo.mtime || nodeInfo.mtime === 0 ? nodeInfo.mtime : '-'}
</Descriptions.Item>
<Descriptions.Item label="mtime-pretty">
{nodeInfo.mtime ? moment(nodeInfo.mtime).format('YYYY-MM-DD HH:mm:ss') : '-'}
</Descriptions.Item>
<Descriptions.Item label="numChildren">
{nodeInfo.numChildren || nodeInfo.numChildren === 0 ? nodeInfo.numChildren : '-'}
</Descriptions.Item>
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} label="mzxid">
{nodeInfo.mzxid || nodeInfo.mzxid === 0 ? nodeInfo.mzxid : '-'}
</Descriptions.Item>
<Descriptions.Item label="ephemeralOwner">
{nodeInfo.ephemeralOwner || nodeInfo.ephemeralOwner === 0 ? nodeInfo.ephemeralOwner : '-'}
</Descriptions.Item>
<Descriptions.Item style={{ paddingBottom: '12px' }} label="pzxid">
{nodeInfo.pzxid || nodeInfo.pzxid === 0 ? nodeInfo.pzxid : '-'}
</Descriptions.Item>
<Descriptions.Item labelStyle={siderWidth < 270 && smallStyle} style={{ paddingBottom: '12px' }} label="version">
{nodeInfo.version || nodeInfo.version === 0 ? nodeInfo.version : '-'}
</Descriptions.Item>
</Descriptions>
);
};
export default ZKInfo;

View File

@@ -1,179 +0,0 @@
import React, { useEffect, useState, useRef } from 'react';
import { Tree, SearchInput, AppContainer, Utils } from 'knowdesign';
import { DataNode, DetailMenuType, getPathByKey, updateTreeData } from './config';
import Api from '@src/api';
const { request } = Utils;
const ZKDetailMenu = (props: DetailMenuType) => {
const { detailTreeData, setDetailInfoLoading, setPathList, setIdenKey, idenKey } = props;
const [global] = AppContainer.useGlobalValue();
const [treeData, setTreeData] = useState<DataNode[]>(detailTreeData);
const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const [loadedKeys, setLoadedKeys] = useState<string[]>([]);
const [childrenClose, setChildrenClose] = useState<boolean>(false);
// const [searchValue, setSearchValue] = useState<string>('');
// const treeRef = useRef();
// 处理参数
const getParams = (key: string, searchValue?: string) => {
const path =
'/' +
getPathByKey(key, treeData)
.map((item: any) => item.title)
.join('/');
return {
path,
keyword: searchValue ? searchValue : '',
};
};
const onSelect = (selectedKeys: string[]) => {
// 控制右侧详情内容的Loading
setDetailInfoLoading(true);
setIdenKey(selectedKeys);
};
const onLoadData = ({ key, children = null }: any) => {
return new Promise<void>((resolve, reject) => {
// 节点关闭,在展开要重新发送请求,以保证节点的准确性
if (children && !childrenClose) {
resolve();
return;
}
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: getParams(key) })
.then((res: string[]) => {
const newData =
res && res.length > 0
? res.map((item: string, index: number) => {
return {
title: item,
key: `${key}-${index}`,
};
})
: [
{
title: '暂无子节点',
key: `${key}-${1}`,
disabled: true,
selectable: false,
isLeaf: true,
},
];
setAutoExpandParent(true);
setTreeData((origin: DataNode[]) => updateTreeData(origin, key, newData));
})
.finally(() => {
return resolve();
});
});
};
const searchChange = (e: string) => {
if (idenKey[0] && idenKey[0].length > 0) {
request(Api.getZookeeperNodeChildren(+global?.clusterInfo?.id), { params: getParams(idenKey[0], e) }).then((res: string[]) => {
const newData =
res && res.length > 0
? res.map((item: string, index: number) => {
return {
title: item,
key: `${idenKey[0]}-${index}`,
};
})
: [
// 如果查询不到节点或者所查询的父节点下没有子节点人为插入一个节点
{
title: e ? '未搜索到相关节点' : '暂无子节点',
key: `${idenKey[0]}-${0}`,
disabled: true,
selectable: false,
isLeaf: true,
},
];
setTreeData((origin: DataNode[]) => {
return updateTreeData(origin, idenKey[0], newData);
});
// 筛选打开的节点中非选中节点的其他节点(排除选中节点以及选中节点的叶子节点)
const filterExpandedKeys = expandedKeys.filter((item) => item.slice(0, idenKey[0].length) !== idenKey[0]);
// 将当前选中的节点再次合并
const newExpandedKeys = [...filterExpandedKeys, ...idenKey];
setExpandedKeys(newExpandedKeys);
setLoadedKeys(newExpandedKeys);
setAutoExpandParent(true);
});
}
};
// 展开收起
const onExpand = (keys: any, arg: any) => {
const { node, expanded } = arg;
let filterExpandedKeys = keys;
let newLoadKeys = loadedKeys;
setChildrenClose(false);
if (!expanded) {
idenKey[0] !== node.key && (idenKey[0] as string)?.slice(0, node.key.length) === node.key && setIdenKey([]);
filterExpandedKeys = keys.filter((item: string) => {
return item !== node.key && item.slice(0, node.key.length) !== node.key;
});
newLoadKeys = loadedKeys.filter((i: string) => i.slice(0, node.key.length) !== node.key);
setChildrenClose(true);
}
setAutoExpandParent(false);
setExpandedKeys(filterExpandedKeys);
setLoadedKeys(newLoadKeys);
};
const onLoad = (loadedKeys: string[]) => {
setLoadedKeys(loadedKeys);
};
useEffect(() => {
// treeRef?.current?.scrollTo({ key: idenKey[0] });
const pathKey = getPathByKey(idenKey[0], treeData);
setPathList(pathKey);
}, [idenKey]);
useEffect(() => {
if (detailTreeData.length > 0) {
setTreeData(detailTreeData);
}
}, [detailTreeData]);
return (
<>
<SearchInput
onSearch={searchChange}
attrs={{
placeholder: '在当前节点进行搜索',
// onChange: searchChange,
size: 'small',
style: {
marginBottom: '15px',
},
// value: searchValue,
// onChange: (value: string) => setSearchValue(value),
}}
/>
<div className="zk-detail-layout-left-content-text">
<Tree
// ref={treeRef}
// height={300}
onLoad={onLoad}
loadData={onLoadData}
loadedKeys={loadedKeys}
expandedKeys={expandedKeys}
selectedKeys={idenKey}
onExpand={onExpand}
autoExpandParent={autoExpandParent}
onSelect={onSelect}
treeData={treeData}
/>
</div>
</>
);
};
export default ZKDetailMenu;

View File

@@ -1,133 +0,0 @@
import React from 'react';
import { Tag } from 'knowdesign';
export interface DetailMenuType {
detailTreeData: any;
setDetailInfoLoading: (loading: boolean) => void;
setPathList: (pathList: DataNode[]) => void;
setIdenKey: (idenKey: string[]) => void;
idenKey: string[];
}
export interface DataNode {
title: string;
key: string;
isLeaf?: boolean;
children?: DataNode[];
}
// 角色
const roleType: any = {
leader: 'Leader',
follower: 'Follower',
ovsever: 'Obsever',
};
export const updateTreeData = (list: DataNode[], key: React.Key, children: DataNode[]): DataNode[] => {
return list.map((node) => {
if (node.key === key) {
return {
...node,
children,
};
}
if (node.children) {
return {
...node,
children: updateTreeData(node.children, key, children),
};
}
return node;
});
};
export const getZookeeperColumns = (arg?: any) => {
const columns = [
{
title: 'Host',
dataIndex: 'host',
key: 'host',
width: 200,
render: (t: string, r: any) => {
return (
<span>
{t}
{r?.status ? <Tag className="tag-success">Live</Tag> : <Tag className="tag-error">Down</Tag>}
</span>
);
},
},
{
title: 'Port',
dataIndex: 'port',
key: 'port',
width: 200,
},
{
title: 'Version',
dataIndex: 'version',
key: 'version',
width: 200,
},
{
title: 'Role',
dataIndex: 'role',
key: 'role',
width: 200,
render(t: string, r: any) {
return (
<Tag
style={{
background: t === 'leader' ? 'rgba(85,110,230,0.10)' : t === 'follower' ? 'rgba(0,192,162,0.10)' : '#fff3e4',
color: t === 'leader' ? '#556EE6' : t === 'follower' ? '#00C0A2' : '#F58342',
padding: '3px 6px',
}}
>
{roleType[t]}
</Tag>
);
},
},
];
return columns;
};
export const defaultPagination = {
current: 1,
pageSize: 10,
position: 'bottomRight',
showSizeChanger: true,
pageSizeOptions: ['10', '20', '50', '100', '200', '500'],
};
export const getPathByKey = (curKey: string, data: DataNode[]) => {
/** 存放搜索到的树节点到顶部节点的路径节点 */
let result: any[] = [];
const traverse = (curKey: string, path: any[], data: DataNode[]) => {
// 树为空时,不执行函数
if (data.length === 0) {
return;
}
// 遍历存放树的数组
for (const item of data) {
// 遍历的数组元素存入path参数数组中
path.push(item);
// 如果目的节点的id值等于当前遍历元素的节点id值
if (item.key === curKey) {
// 把获取到的节点路径数组path赋值到result数组
result = JSON.parse(JSON.stringify(path));
return;
}
// 当前元素的children是数组
const children = Array.isArray(item.children) ? item.children : [];
// 递归遍历子数组内容
traverse(curKey, path, children);
// 利用回溯思想当没有在当前叶树找到目的节点依次删除存入到的path数组路径
path.pop();
}
};
traverse(curKey, [], data);
// 返回找到的树节点路径
return result;
};

View File

@@ -1,131 +0,0 @@
.zookeeper-detail-drawer {
.zk-detail-empty {
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
border: 2px solid #eff2f7;
height: calc(100vh - 90px);
}
.zk-detail-layout {
display: flex;
border-radius: 8px;
border: 2px solid #eff2f7;
height: 100%;
position: relative;
&-left,
&-right {
&-title {
background-color: rgba(116, 120, 141, 0.04);
font-size: 14px;
font-weight: 500;
font-family: @font-family-bold;
padding: 12px 20px;
border-bottom: 2px solid #eff2f7;
}
&-content {
height: calc(100vh - 128px);
}
}
&-left {
min-width: 200px;
max-width: 320px;
&-content {
padding: 10px;
&-text {
height: calc(100vh - 190px);
overflow: auto;
}
}
.dcloud-tree-title {
white-space: nowrap !important;
}
.dcloud-tree-treenode {
font-size: 13px;
padding: 6px 0;
}
.dcloud-tree-node-content-wrapper {
&:hover:not(.dcloud-tree-node-content-wrapper-normal) {
background: #f1f3ff !important;
color: #556ee6;
}
}
.dcloud-tree .dcloud-tree-node-content-wrapper.dcloud-tree-node-selected {
background-color: transparent;
color: #556ee6;
}
}
&-resizer {
.resize-mask {
background: rgba(0, 0, 0, 0);
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 1;
cursor: col-resize;
}
border: 1px solid #eff2f7;
position: absolute;
left: 200px;
top: 0;
bottom: 0;
cursor: col-resize;
&:hover {
border: 1px solid #556ee6;
}
}
&-right {
flex: 1;
&-content {
padding: 12px 20px 16px;
overflow: auto;
&-info {
background: rgba(116, 120, 141, 0.04);
border-radius: 8px;
padding: 12px 0 0;
}
&-countheight {
display: flex;
flex-direction: column;
height: calc(100vh - 156px);
}
&-path {
margin-bottom: 16px;
& > *:hover {
color: #556ee6;
cursor: pointer;
}
}
&-format {
margin: 12px 0 6px;
display: flex;
justify-content: right;
}
&-data {
flex: 1;
overflow: auto;
background: rgba(116, 120, 141, 0.04);
border-radius: 8px;
padding: 12px 20px 0;
}
&-code {
flex: 1;
overflow: auto;
border-radius: 8px;
&-data {
height: 100% !important;
}
.cm-s-default {
height: 100% !important;
// border-radius: 8px !important;
overflow: hidden;
background: rgba(116, 120, 141, 0.04);
}
}
}
}
}
}

View File

@@ -1,128 +0,0 @@
import React, { useState, useEffect, memo } from 'react';
import { useParams, useHistory, useLocation } from 'react-router-dom';
import { ProTable, Button, Utils, AppContainer, SearchInput } from 'knowdesign';
import { IconFont } from '@knowdesign/icons';
import API from '../../api';
import { getZookeeperColumns, defaultPagination } from './config';
import { tableHeaderPrefix } from '@src/constants/common';
import ZookeeperDetail from './Detail';
import ZookeeperCard from '@src/components/CardBar/ZookeeperCard';
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
import './index.less';
const { request } = Utils;
const ZookeeperList: React.FC = () => {
const [global] = AppContainer.useGlobalValue();
const [loading, setLoading] = useState(false);
const [detailVisible, setDetailVisible] = useState(false);
const [data, setData] = useState([]);
const [searchKeywords, setSearchKeywords] = useState('');
const [pagination, setPagination] = useState<any>(defaultPagination);
// 请求接口获取数据
const genData = async ({ pageNo, pageSize, filters, sorter }: any) => {
if (global?.clusterInfo?.id === undefined) return;
setLoading(true);
const params = {
searchKeywords: searchKeywords.slice(0, 128),
pageNo,
pageSize,
};
request(API.getZookeeperList(global?.clusterInfo?.id), { method: 'POST', data: params })
.then((res: any) => {
setPagination({
current: res.pagination?.pageNo,
pageSize: res.pagination?.pageSize,
total: res.pagination?.total,
});
const newData =
res?.bizData.map((item: any) => {
return {
...item,
...item?.latestMetrics?.metrics,
};
}) || [];
setData(newData);
setLoading(false);
})
.catch((err) => {
setLoading(false);
});
};
const onTableChange = (pagination: any, filters: any, sorter: any) => {
genData({ pageNo: pagination.current, pageSize: pagination.pageSize, filters, sorter });
};
useEffect(() => {
genData({
pageNo: 1,
pageSize: pagination.pageSize,
});
}, [searchKeywords]);
return (
<div key="brokerList" className="brokerList">
<div className="breadcrumb" style={{ marginBottom: '10px' }}>
<DBreadcrumb
breadcrumbs={[
{ label: '多集群管理', aHref: '/' },
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
{ label: 'Zookeeper', aHref: `/cluster/${global?.clusterInfo?.id}/zookepper` },
{ label: 'Servers', aHref: `` },
]}
/>
</div>
<div style={{ margin: '12px 0' }}>
<ZookeeperCard />
</div>
<div className="clustom-table-content">
<div className={tableHeaderPrefix}>
<div className={`${tableHeaderPrefix}-left`}>
<div
className={`${tableHeaderPrefix}-left-refresh`}
onClick={() => genData({ pageNo: pagination.current, pageSize: pagination.pageSize })}
>
<IconFont className={`${tableHeaderPrefix}-left-refresh-icon`} type="icon-shuaxin1" />
</div>
</div>
<div className={`${tableHeaderPrefix}-right`}>
<SearchInput
onSearch={setSearchKeywords}
attrs={{
placeholder: '请输入Host',
style: { width: '248px', borderRiadus: '8px' },
maxLength: 128,
}}
/>
<Button type="primary" onClick={() => setDetailVisible(true)}>
Zookeeper详情
</Button>
</div>
</div>
<ProTable
key="zookeeper-table"
showQueryForm={false}
tableProps={{
showHeader: false,
rowKey: 'zookeeper_list',
loading: loading,
columns: getZookeeperColumns(),
dataSource: data,
paginationProps: { ...pagination },
attrs: {
onChange: onTableChange,
scroll: { y: 'calc(100vh - 400px)' },
bordered: false,
},
}}
/>
</div>
{<ZookeeperDetail visible={detailVisible} setVisible={setDetailVisible} />}
</div>
);
};
export default ZookeeperList;

View File

@@ -1,27 +0,0 @@
import React from 'react';
import { MetricType } from '@src/api';
import DraggableCharts from '@src/components/DraggableCharts';
import DBreadcrumb from 'knowdesign/es/extend/d-breadcrumb';
import { AppContainer } from 'knowdesign';
import ZookeeperCard from '@src/components/CardBar/ZookeeperCard';
const ZookeeperDashboard = (): JSX.Element => {
const [global] = AppContainer.useGlobalValue();
return (
<>
<div className="breadcrumb" style={{ marginBottom: '10px' }}>
<DBreadcrumb
breadcrumbs={[
{ label: '多集群管理', aHref: '/' },
{ label: global?.clusterInfo?.name, aHref: `/cluster/${global?.clusterInfo?.id}` },
{ label: 'Zookeeper', aHref: `` },
]}
/>
</div>
<ZookeeperCard />
<DraggableCharts type={MetricType.Zookeeper} />
</>
);
};
export default ZookeeperDashboard;

View File

@@ -7,7 +7,6 @@ import { leftMenus, systemKey } from '@src/constants/menu';
import { ClustersPermissionMap } from './CommonConfig';
import { getLicenseInfo } from '@src/constants/common';
import { licenseEventBus } from '@src/constants/axiosConfig';
import { ClusterRunState } from './MutliClusterPage/List';
export const NoMatch = <Redirect to="/404" />;
@@ -36,13 +35,6 @@ const LayoutContainer = () => {
const [showSider, setShowSider] = useState<boolean>(!(notCurrentSystemKey || hasNoSiderPage));
const [handledLeftMenus, setHandledLeftMenus] = useState(leftMenus());
const forbidenPaths = (path: string) => {
// Raft 模式运行的集群没有 ZK 页面
if (path.includes('zookeeper') && global.clusterInfo?.runState === ClusterRunState.Raft) {
history.replace('/404');
}
};
const isShowMenu = useCallback(
(nodes: ClustersPermissionMap | ClustersPermissionMap[]) => {
let isAllow = false;
@@ -75,7 +67,6 @@ const LayoutContainer = () => {
if (permissionNode) {
// 判断用户是否有当前页面的权限
if (global.hasPermission(permissionNode)) {
forbidenPaths(path);
return Promise.resolve(true);
} else {
// 用户没有当前页面权限,跳转到多集群首页
@@ -86,7 +77,6 @@ const LayoutContainer = () => {
return Promise.reject(false);
}
}
forbidenPaths(path);
return Promise.resolve(true);
},
[global.clusterInfo, global.hasPermission, global.getMetricDefine]
@@ -96,14 +86,12 @@ const LayoutContainer = () => {
const notCurrentSystemKey = window.location.pathname.split('/')?.[1] !== systemKey;
const hasNoSiderPage = noSiderPages.findIndex((item) => item.path === getCurrentPathname(pathname)) > -1;
setShowSider(notCurrentSystemKey || hasNoSiderPage ? false : true);
if (pathname.startsWith('/cluster') || pathname === '/') {
const items = pathname.split('/');
const clusterId = items[2];
clusterId !== global.clusterInfo?.id &&
setHandledLeftMenus(leftMenus(clusterId, pathname === '/' ? undefined : global.clusterInfo?.runState));
setHandledLeftMenus(leftMenus(clusterId));
}
}, [pathname, global.clusterInfo]);
}, [pathname]);
return (
<div id="sub-system" style={{ display: 'flex' }}>

View File

@@ -22,9 +22,6 @@ import SecurityACLs from './SecurityACLs';
import SecurityUsers from './SecurityUsers';
import LoadRebalance from './LoadRebalance';
import Zookeeper from './Zookeeper';
import ZookeeperDashboard from './ZookeeperDashboard';
const pageRoutes = [
{
path: '/',
@@ -118,18 +115,6 @@ const pageRoutes = [
component: Jobs,
noSider: false,
},
{
path: 'zookeeper',
exact: true,
component: ZookeeperDashboard,
noSider: false,
},
{
path: 'zookeeper/servers',
exact: true,
component: Zookeeper,
noSider: false,
},
{
path: 'security/acls',
exact: true,

View File

@@ -28,7 +28,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils;
import com.xiaojukeji.know.streaming.km.core.cache.CollectedMetricsLocalCache;
import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerMetricService;
import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService;
import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService;
import com.xiaojukeji.know.streaming.km.core.service.health.score.HealthScoreService;
import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService;
import com.xiaojukeji.know.streaming.km.core.service.replica.ReplicaMetricService;
import com.xiaojukeji.know.streaming.km.core.service.topic.TopicService;
@@ -82,7 +82,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
private ReplicaMetricService replicaMetricService;
@Autowired
private HealthStateService healthStateService;
private HealthScoreService healthScoreService;
@Autowired
private KafkaJMXClient kafkaJMXClient;
@@ -108,6 +108,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
registerVCHandler( BROKER_METHOD_GET_HEALTH_SCORE, this::getMetricHealthScore);
registerVCHandler( BROKER_METHOD_GET_PARTITIONS_SKEW, this::getPartitionsSkew);
registerVCHandler( BROKER_METHOD_GET_LEADERS_SKEW, this::getLeadersSkew);
// registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, this::getLogSize);
registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, V_0_10_0_0, V_1_0_0, "getLogSizeFromJmx", this::getLogSizeFromJmx);
registerVCHandler( BROKER_METHOD_GET_LOG_SIZE, V_1_0_0, V_MAX, "getLogSizeFromClient", this::getLogSizeFromClient);
@@ -317,7 +318,7 @@ public class BrokerMetricServiceImpl extends BaseMetricService implements Broker
Long clusterId = param.getClusterId();
Integer brokerId = param.getBrokerId();
BrokerMetrics brokerMetrics = healthStateService.calBrokerHealthMetrics(clusterId, brokerId);
BrokerMetrics brokerMetrics = healthScoreService.calBrokerHealthScore(clusterId, brokerId);
return Result.buildSuc(brokerMetrics);
}

View File

@@ -134,7 +134,7 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok
newBrokerPO.setId(inDBBrokerPO.getId());
newBrokerPO.setStatus(Constant.ALIVE);
newBrokerPO.setCreateTime(inDBBrokerPO.getCreateTime());
newBrokerPO.setUpdateTime(new Date());
newBrokerPO.setUpdateTime(inDBBrokerPO.getUpdateTime());
if (newBrokerPO.getStartTimestamp() == null) {
// 如果当前broker获取不到启动时间
// 如果DB中的broker状态为down则使用当前时间否则使用db中已有broker的时间
@@ -363,11 +363,11 @@ public class BrokerServiceImpl extends BaseVersionControlService implements Brok
try {
Long startTime = jmxDAO.getServerStartTime(clusterPhyId, newNode.host(), null, jmxConfig);
return Broker.buildFrom(clusterPhyId, newNode, startTime, jmxConfig);
return Broker.buildFrom(clusterPhyId, newNode, startTime);
} catch (Exception e) {
log.error("class=BrokerServiceImpl||method=getStartTimeAndBuildBroker||clusterPhyId={}||brokerNode={}||jmxConfig={}||errMsg=exception!", clusterPhyId, newNode, jmxConfig, e);
}
return Broker.buildFrom(clusterPhyId, newNode, null, jmxConfig);
return Broker.buildFrom(clusterPhyId, newNode, null);
}
}

View File

@@ -39,7 +39,7 @@ import com.xiaojukeji.know.streaming.km.core.service.broker.BrokerService;
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterMetricService;
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService;
import com.xiaojukeji.know.streaming.km.core.service.group.GroupService;
import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService;
import com.xiaojukeji.know.streaming.km.core.service.health.score.HealthScoreService;
import com.xiaojukeji.know.streaming.km.core.service.job.JobService;
import com.xiaojukeji.know.streaming.km.core.service.kafkacontroller.KafkaControllerService;
import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService;
@@ -85,7 +85,7 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust
public static final String CLUSTER_METHOD_GET_TOTAL_LOG_SIZE = "getTotalLogSize";
public static final String CLUSTER_METHOD_GET_PARTITION_SIZE = "getPartitionSize";
public static final String CLUSTER_METHOD_GET_PARTITION_NO_LEADER_SIZE = "getPartitionNoLeaderSize";
public static final String CLUSTER_METHOD_GET_HEALTH_METRICS = "getClusterHealthMetrics";
public static final String CLUSTER_METHOD_GET_HEALTH_SCORE = "getClusterHealthScore";
public static final String CLUSTER_METHOD_GET_METRIC_FROM_KAFKA_BY_TOTAL_BROKERS_JMX = "getMetricFromKafkaByTotalBrokersJMX";
public static final String CLUSTER_METHOD_GET_METRIC_FROM_KAFKA_BY_CONTROLLER_JMX = "getMetricFromKafkaByControllerJMX";
public static final String CLUSTER_METHOD_GET_ZK_COUNT = "getZKCount";
@@ -114,7 +114,7 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust
public static final String CLUSTER_METHOD_GET_JOBS_FAILED = "getJobsFailed";
@Autowired
private HealthStateService healthStateService;
private HealthScoreService healthScoreService;
@Autowired
private BrokerService brokerService;
@@ -188,7 +188,7 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust
registerVCHandler( CLUSTER_METHOD_GET_PARTITION_SIZE, this::getPartitionSize);
registerVCHandler( CLUSTER_METHOD_GET_PARTITION_NO_LEADER_SIZE, this::getPartitionNoLeaderSize);
registerVCHandler( CLUSTER_METHOD_GET_HEALTH_METRICS, this::getClusterHealthMetrics);
registerVCHandler( CLUSTER_METHOD_GET_HEALTH_SCORE, this::getClusterHealthScore);
registerVCHandler( CLUSTER_METHOD_GET_METRIC_FROM_KAFKA_BY_TOTAL_BROKERS_JMX, this::getMetricFromKafkaByTotalBrokersJMX);
registerVCHandler( CLUSTER_METHOD_GET_METRIC_FROM_KAFKA_BY_CONTROLLER_JMX, this::getMetricFromKafkaByControllerJMX);
@@ -361,13 +361,15 @@ public class ClusterMetricServiceImpl extends BaseMetricService implements Clust
}
private Result<ClusterMetrics> getClusterHealthMetrics(VersionItemParam metricParam){
/**
* 获取集群的健康分
*/
private Result<ClusterMetrics> getClusterHealthScore(VersionItemParam metricParam){
ClusterMetricParam param = (ClusterMetricParam)metricParam;
ClusterMetrics clusterMetrics = healthStateService.calClusterHealthMetrics(param.getClusterId());
ClusterMetrics clusterMetrics = healthScoreService.calClusterHealthScore(param.getClusterId());
return Result.buildSuc(clusterMetrics);
}
/**
* 获取集群的 totalLogSize
* @param metricParam

View File

@@ -20,7 +20,7 @@ import com.xiaojukeji.know.streaming.km.common.utils.BeanUtil;
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
import com.xiaojukeji.know.streaming.km.core.service.group.GroupMetricService;
import com.xiaojukeji.know.streaming.km.core.service.group.GroupService;
import com.xiaojukeji.know.streaming.km.core.service.health.state.HealthStateService;
import com.xiaojukeji.know.streaming.km.core.service.health.score.HealthScoreService;
import com.xiaojukeji.know.streaming.km.core.service.partition.PartitionService;
import com.xiaojukeji.know.streaming.km.core.service.version.BaseMetricService;
import com.xiaojukeji.know.streaming.km.persistence.es.dao.GroupMetricESDAO;
@@ -64,7 +64,7 @@ public class GroupMetricServiceImpl extends BaseMetricService implements GroupMe
private GroupService groupService;
@Autowired
private HealthStateService healthStateService;
private HealthScoreService healthScoreService;
@Autowired
private PartitionService partitionService;
@@ -265,8 +265,8 @@ public class GroupMetricServiceImpl extends BaseMetricService implements GroupMe
private Result<List<GroupMetrics>> getMetricHealthScore(VersionItemParam param) {
GroupMetricParam groupMetricParam = (GroupMetricParam)param;
return Result.buildSuc(Arrays.asList(
healthStateService.calGroupHealthMetrics(groupMetricParam.getClusterPhyId(), groupMetricParam.getGroupName()))
return Result.buildSuc(Arrays.asList(healthScoreService.calGroupHealthScore(
groupMetricParam.getClusterPhyId(), groupMetricParam.getGroupName()))
);
}
}

View File

@@ -1,281 +0,0 @@
package com.xiaojukeji.know.streaming.km.core.service.health.checker.zookeeper;
import com.didiglobal.logi.log.ILog;
import com.didiglobal.logi.log.LogFactory;
import com.xiaojukeji.know.streaming.km.common.bean.entity.cluster.ClusterPhy;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.ZKConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthAmountRatioConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.HealthCompareValueConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthCheckResult;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ZookeeperMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.cluster.ClusterPhyParam;
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.metric.ZookeeperMetricParam;
import com.xiaojukeji.know.streaming.km.common.bean.entity.param.zookeeper.ZookeeperParam;
import com.xiaojukeji.know.streaming.km.common.bean.entity.result.Result;
import com.xiaojukeji.know.streaming.km.common.bean.entity.zookeeper.ZookeeperInfo;
import com.xiaojukeji.know.streaming.km.common.constant.Constant;
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum;
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum;
import com.xiaojukeji.know.streaming.km.common.enums.zookeeper.ZKRoleEnum;
import com.xiaojukeji.know.streaming.km.common.utils.ConvertUtil;
import com.xiaojukeji.know.streaming.km.common.utils.Tuple;
import com.xiaojukeji.know.streaming.km.common.utils.zookeeper.ZookeeperUtils;
import com.xiaojukeji.know.streaming.km.core.service.cluster.ClusterPhyService;
import com.xiaojukeji.know.streaming.km.core.service.health.checker.AbstractHealthCheckService;
import com.xiaojukeji.know.streaming.km.core.service.version.metrics.ZookeeperMetricVersionItems;
import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperMetricService;
import com.xiaojukeji.know.streaming.km.core.service.zookeeper.ZookeeperService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class HealthCheckZookeeperService extends AbstractHealthCheckService {
private static final ILog log = LogFactory.getLog(HealthCheckZookeeperService.class);
@Autowired
private ClusterPhyService clusterPhyService;
@Autowired
private ZookeeperService zookeeperService;
@Autowired
private ZookeeperMetricService zookeeperMetricService;
@PostConstruct
private void init() {
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_BRAIN_SPLIT.getConfigName(), this::checkBrainSplit);
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_OUTSTANDING_REQUESTS.getConfigName(), this::checkOutstandingRequests);
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_WATCH_COUNT.getConfigName(), this::checkWatchCount);
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_ALIVE_CONNECTIONS.getConfigName(), this::checkAliveConnections);
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_APPROXIMATE_DATA_SIZE.getConfigName(), this::checkApproximateDataSize);
functionMap.putIfAbsent(HealthCheckNameEnum.ZK_SENT_RATE.getConfigName(), this::checkSentRate);
}
@Override
public List<ClusterPhyParam> getResList(Long clusterPhyId) {
ClusterPhy clusterPhy = clusterPhyService.getClusterByCluster(clusterPhyId);
if (clusterPhy == null) {
return new ArrayList<>();
}
try {
return Arrays.asList(new ZookeeperParam(
clusterPhyId,
ZookeeperUtils.connectStringParser(clusterPhy.getZookeeper()),
ConvertUtil.str2ObjByJson(clusterPhy.getZkProperties(), ZKConfig.class)
));
} catch (Exception e) {
log.error("class=HealthCheckZookeeperService||method=getResList||clusterPhyId={}||errMsg=exception!", clusterPhyId, e);
}
return new ArrayList<>();
}
@Override
public HealthCheckDimensionEnum getHealthCheckDimensionEnum() {
return HealthCheckDimensionEnum.ZOOKEEPER;
}
private HealthCheckResult checkBrainSplit(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
HealthCompareValueConfig valueConfig = (HealthCompareValueConfig) singleConfigSimpleTuple.getV2();
List<ZookeeperInfo> infoList = zookeeperService.listFromDBByCluster(param.getClusterPhyId());
HealthCheckResult checkResult = new HealthCheckResult(
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
HealthCheckNameEnum.ZK_BRAIN_SPLIT.getConfigName(),
param.getClusterPhyId(),
""
);
long value = infoList.stream().filter(elem -> ZKRoleEnum.LEADER.getRole().equals(elem.getRole())).count();
checkResult.setPassed(value == valueConfig.getValue().longValue() ? Constant.YES : Constant.NO);
return checkResult;
}
private HealthCheckResult checkOutstandingRequests(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
new ZookeeperMetricParam(
param.getClusterPhyId(),
param.getZkAddressList(),
param.getZkConfig(),
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_OUTSTANDING_REQUESTS
)
);
if (metricsResult.failed() || !metricsResult.hasData()) {
log.error(
"class=HealthCheckZookeeperService||method=checkOutstandingRequests||param={}||config={}||result={}||errMsg=get metrics failed",
param, valueConfig, metricsResult
);
return null;
}
HealthCheckResult checkResult = new HealthCheckResult(
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
HealthCheckNameEnum.ZK_OUTSTANDING_REQUESTS.getConfigName(),
param.getClusterPhyId(),
""
);
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_OUTSTANDING_REQUESTS);
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
return checkResult;
}
private HealthCheckResult checkWatchCount(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
new ZookeeperMetricParam(
param.getClusterPhyId(),
param.getZkAddressList(),
param.getZkConfig(),
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT
)
);
if (metricsResult.failed() || !metricsResult.hasData()) {
log.error(
"class=HealthCheckZookeeperService||method=checkWatchCount||param={}||config={}||result={}||errMsg=get metrics failed",
param, valueConfig, metricsResult
);
return null;
}
HealthCheckResult checkResult = new HealthCheckResult(
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
HealthCheckNameEnum.ZK_WATCH_COUNT.getConfigName(),
param.getClusterPhyId(),
""
);
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_WATCH_COUNT);
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
return checkResult;
}
private HealthCheckResult checkAliveConnections(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
new ZookeeperMetricParam(
param.getClusterPhyId(),
param.getZkAddressList(),
param.getZkConfig(),
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_NUM_ALIVE_CONNECTIONS
)
);
if (metricsResult.failed() || !metricsResult.hasData()) {
log.error(
"class=HealthCheckZookeeperService||method=checkAliveConnections||param={}||config={}||result={}||errMsg=get metrics failed",
param, valueConfig, metricsResult
);
return null;
}
HealthCheckResult checkResult = new HealthCheckResult(
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
HealthCheckNameEnum.ZK_ALIVE_CONNECTIONS.getConfigName(),
param.getClusterPhyId(),
""
);
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_NUM_ALIVE_CONNECTIONS);
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
return checkResult;
}
private HealthCheckResult checkApproximateDataSize(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
new ZookeeperMetricParam(
param.getClusterPhyId(),
param.getZkAddressList(),
param.getZkConfig(),
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_APPROXIMATE_DATA_SIZE
)
);
if (metricsResult.failed() || !metricsResult.hasData()) {
log.error(
"class=HealthCheckZookeeperService||method=checkApproximateDataSize||param={}||config={}||result={}||errMsg=get metrics failed",
param, valueConfig, metricsResult
);
return null;
}
HealthCheckResult checkResult = new HealthCheckResult(
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
HealthCheckNameEnum.ZK_APPROXIMATE_DATA_SIZE.getConfigName(),
param.getClusterPhyId(),
""
);
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_APPROXIMATE_DATA_SIZE);
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
return checkResult;
}
private HealthCheckResult checkSentRate(Tuple<ClusterPhyParam, BaseClusterHealthConfig> singleConfigSimpleTuple) {
ZookeeperParam param = (ZookeeperParam) singleConfigSimpleTuple.getV1();
HealthAmountRatioConfig valueConfig = (HealthAmountRatioConfig) singleConfigSimpleTuple.getV2();
Result<ZookeeperMetrics> metricsResult = zookeeperMetricService.collectMetricsFromZookeeper(
new ZookeeperMetricParam(
param.getClusterPhyId(),
param.getZkAddressList(),
param.getZkConfig(),
ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_PACKETS_SENT
)
);
if (metricsResult.failed() || !metricsResult.hasData()) {
log.error(
"class=HealthCheckZookeeperService||method=checkSentRate||param={}||config={}||result={}||errMsg=get metrics failed",
param, valueConfig, metricsResult
);
return null;
}
HealthCheckResult checkResult = new HealthCheckResult(
HealthCheckDimensionEnum.ZOOKEEPER.getDimension(),
HealthCheckNameEnum.ZK_SENT_RATE.getConfigName(),
param.getClusterPhyId(),
""
);
Float value = metricsResult.getData().getMetric(ZookeeperMetricVersionItems.ZOOKEEPER_METRIC_PACKETS_SENT);
checkResult.setPassed(value.intValue() <= valueConfig.getAmount().doubleValue() * valueConfig.getRatio().doubleValue() ? Constant.YES : Constant.NO);
return checkResult;
}
}

View File

@@ -0,0 +1,71 @@
package com.xiaojukeji.know.streaming.km.core.service.health.score;
import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthScoreResult;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics;
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum;
import java.util.List;
public interface HealthScoreService {
/**
* 获取集群健康分指标
* @param clusterPhyId 集群ID
* @return
*/
@Deprecated
ClusterMetrics calClusterHealthScore(Long clusterPhyId);
/**
* 获取集群健康分指标
* @param clusterPhyId 集群ID
* @param topicName Topic名称
* @return
*/
@Deprecated
TopicMetrics calTopicHealthScore(Long clusterPhyId, String topicName);
/**
* 获取集群健康分指标
* @param clusterPhyId 集群ID
* @param brokerId brokerId
* @return
*/
@Deprecated
BrokerMetrics calBrokerHealthScore(Long clusterPhyId, Integer brokerId);
/**
* 获取集群健康分指标
* @param clusterPhyId 集群ID
* @param groupName group名称
* @return
*/
@Deprecated
GroupMetrics calGroupHealthScore(Long clusterPhyId, String groupName);
/**
* 获取集群健康分结果
* @param clusterPhyId 集群ID
* @return
*/
List<HealthScoreResult> getClusterHealthScoreResult(Long clusterPhyId);
/**
* 获取集群某个维度健康分结果
* @param clusterPhyId 集群ID
* @param dimensionEnum 维度
* @return
*/
List<HealthScoreResult> getDimensionHealthScoreResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum);
/**
* 获取集群某个资源的健康分结果
* @param clusterPhyId 集群ID
* @param dimension 维度
* @param resNme 资源名称
* @return
*/
List<HealthScoreResult> getResHealthScoreResult(Long clusterPhyId, Integer dimension, String resNme);
}

View File

@@ -0,0 +1,331 @@
package com.xiaojukeji.know.streaming.km.core.service.health.score.impl;
import com.xiaojukeji.know.streaming.km.common.bean.entity.config.healthcheck.BaseClusterHealthConfig;
import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthScoreResult;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.BrokerMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.ClusterMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.GroupMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.TopicMetrics;
import com.xiaojukeji.know.streaming.km.common.bean.po.health.HealthCheckResultPO;
import com.xiaojukeji.know.streaming.km.common.constant.Constant;
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckNameEnum;
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum;
import com.xiaojukeji.know.streaming.km.common.utils.ValidateUtils;
import com.xiaojukeji.know.streaming.km.core.service.health.checkresult.HealthCheckResultService;
import com.xiaojukeji.know.streaming.km.core.service.health.score.HealthScoreService;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.BrokerMetricVersionItems.*;
import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.TopicMetricVersionItems.*;
import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.ClusterMetricVersionItems.*;
import static com.xiaojukeji.know.streaming.km.core.service.version.metrics.GroupMetricVersionItems.*;
@Service
public class HealthScoreServiceImpl implements HealthScoreService {
@Autowired
private HealthCheckResultService healthCheckResultService;
@Override
public ClusterMetrics calClusterHealthScore(Long clusterPhyId) {
List<HealthScoreResult> allHealthScoreResultList = this.getClusterHealthScoreResult(clusterPhyId);
Map<Integer, List<HealthScoreResult>> healthScoreResultMap = new HashMap<>();
for (HealthScoreResult healthScoreResult: allHealthScoreResultList) {
healthScoreResultMap.putIfAbsent(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension(), new ArrayList<>());
healthScoreResultMap.get(healthScoreResult.getCheckNameEnum().getDimensionEnum().getDimension()).add(healthScoreResult);
}
Float healthScore = 0f;
Float healthCheckPassed = 0f;
Float healthCheckTotal = 0f;
ClusterMetrics metrics = new ClusterMetrics(clusterPhyId);
// cluster维度
List<HealthScoreResult> healthScoreResultList = healthScoreResultMap.get(HealthCheckDimensionEnum.CLUSTER.getDimension());
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_CLUSTER, Constant.MIN_HEALTH_SCORE);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER, 0.0f);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER, 0.0f);
healthScore += 0;
healthCheckPassed += 0;
healthCheckTotal += 0;
} else {
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_CLUSTER, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER, this.getHealthCheckPassed(healthScoreResultList));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER, Float.valueOf(healthScoreResultList.size()));
healthScore += this.getAllHealthScore(healthScoreResultList);
healthCheckPassed += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_PASSED_CLUSTER);
healthCheckTotal += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_CLUSTER);
}
// broker维度
healthScoreResultList = healthScoreResultMap.get(HealthCheckDimensionEnum.BROKER.getDimension());
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_BROKERS, Constant.MIN_HEALTH_SCORE);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_BROKERS, 0.0f);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_BROKERS, 0.0f);
healthScore += 0;
healthCheckPassed += 0;
healthCheckTotal += 0;
} else {
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_BROKERS, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_BROKERS, this.getHealthCheckPassed(healthScoreResultList));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_BROKERS, Float.valueOf(healthScoreResultList.size()));
healthScore += this.getAllHealthScore(healthScoreResultList);
healthCheckPassed += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_PASSED_BROKERS);
healthCheckTotal += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_BROKERS);
}
// topic维度
healthScoreResultList = healthScoreResultMap.get(HealthCheckDimensionEnum.TOPIC.getDimension());
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_TOPICS, Constant.MIN_HEALTH_SCORE);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_TOPICS, 0.0f);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_TOPICS, 0.0f);
healthScore += 0;
healthCheckPassed += 0;
healthCheckTotal += 0;
} else {
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_TOPICS, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_TOPICS, this.getHealthCheckPassed(healthScoreResultList));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_TOPICS, Float.valueOf(healthScoreResultList.size()));
healthScore += this.getAllHealthScore(healthScoreResultList);
healthCheckPassed += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_PASSED_TOPICS);
healthCheckTotal += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_TOPICS);
}
// group维度
healthScoreResultList = healthScoreResultMap.get(HealthCheckDimensionEnum.GROUP.getDimension());
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_GROUPS, Constant.MIN_HEALTH_SCORE);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_GROUPS, 0.0f);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_GROUPS, 0.0f);
healthScore += 0;
healthCheckPassed += 0;
healthCheckTotal += 0;
} else {
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE_GROUPS, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED_GROUPS, this.getHealthCheckPassed(healthScoreResultList));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_GROUPS, Float.valueOf(healthScoreResultList.size()));
healthScore += this.getAllHealthScore(healthScoreResultList);
healthCheckPassed += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_PASSED_GROUPS);
healthCheckTotal += metrics.getMetrics().get(CLUSTER_METRIC_HEALTH_CHECK_TOTAL_GROUPS);
}
// 集群最终的
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_SCORE, Math.max(healthScore, Constant.MIN_HEALTH_SCORE));
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_PASSED, healthCheckPassed);
metrics.getMetrics().put(CLUSTER_METRIC_HEALTH_CHECK_TOTAL, healthCheckTotal);
return metrics;
}
@Override
public TopicMetrics calTopicHealthScore(Long clusterPhyId, String topicName) {
List<HealthScoreResult> healthScoreResultList = this.getResHealthScoreResult(clusterPhyId, HealthCheckDimensionEnum.TOPIC.getDimension(), topicName);
TopicMetrics metrics = new TopicMetrics(topicName, clusterPhyId,true);
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_SCORE, Constant.MIN_HEALTH_SCORE);
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_PASSED, 0.0f);
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_TOTAL, 0.0f);
} else {
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_SCORE, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_PASSED, getHealthCheckPassed(healthScoreResultList));
metrics.getMetrics().put(TOPIC_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size()));
}
return metrics;
}
@Override
public BrokerMetrics calBrokerHealthScore(Long clusterPhyId, Integer brokerId) {
List<HealthScoreResult> healthScoreResultList = this.getResHealthScoreResult(clusterPhyId, HealthCheckDimensionEnum.BROKER.getDimension(), String.valueOf(brokerId));
BrokerMetrics metrics = new BrokerMetrics(clusterPhyId, brokerId);
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
metrics.getMetrics().put(BROKER_METRIC_HEALTH_SCORE, Constant.MIN_HEALTH_SCORE);
metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_PASSED, 0.0f);
metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_TOTAL, 0.0f);
} else {
metrics.getMetrics().put(BROKER_METRIC_HEALTH_SCORE, Math.max(this.getDimensionHealthScore(healthScoreResultList), Constant.MIN_HEALTH_SCORE));
metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_PASSED, getHealthCheckPassed(healthScoreResultList));
metrics.getMetrics().put(BROKER_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size()));
}
return metrics;
}
@Override
public GroupMetrics calGroupHealthScore(Long clusterPhyId, String groupName) {
List<HealthScoreResult> healthScoreResultList = this.getResHealthScoreResult(clusterPhyId, HealthCheckDimensionEnum.GROUP.getDimension(), groupName);
GroupMetrics metrics = new GroupMetrics(clusterPhyId, groupName, true);
if (ValidateUtils.isEmptyList(healthScoreResultList)) {
metrics.getMetrics().put(GROUP_METRIC_HEALTH_SCORE, Constant.MIN_HEALTH_SCORE);
metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_PASSED, 0.0f);
metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_TOTAL, 0.0f);
} else {
metrics.getMetrics().put(GROUP_METRIC_HEALTH_SCORE, Math.max(this.getDimensionHealthScore(healthScoreResultList),Constant.MIN_HEALTH_SCORE));
metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_PASSED, getHealthCheckPassed(healthScoreResultList));
metrics.getMetrics().put(GROUP_METRIC_HEALTH_CHECK_TOTAL, Float.valueOf(healthScoreResultList.size()));
}
return metrics;
}
@Override
public List<HealthScoreResult> getClusterHealthScoreResult(Long clusterPhyId) {
List<HealthCheckResultPO> poList = healthCheckResultService.getClusterHealthCheckResult(clusterPhyId);
// <检查项,<检查结果>>
Map<String, List<HealthCheckResultPO>> checkResultMap = new HashMap<>();
for (HealthCheckResultPO po: poList) {
checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>());
checkResultMap.get(po.getConfigName()).add(po);
}
// 每个维度的权重和
Map<String, Float> dimensionTotalWeightMap = new HashMap<>();
Float allDimensionTotalWeight = 0f;
Map<String, BaseClusterHealthConfig> configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId);
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.values()) {
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
if (baseConfig == null) {
continue;
}
allDimensionTotalWeight += baseConfig.getWeight();
Float totalWeight = dimensionTotalWeightMap.getOrDefault(nameEnum.getDimensionEnum().name(), 0f);
dimensionTotalWeightMap.put(nameEnum.getDimensionEnum().name(), totalWeight + baseConfig.getWeight());
}
List<HealthScoreResult> healthScoreResultList = new ArrayList<>();
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.values()) {
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
if (baseConfig == null) {
continue;
}
healthScoreResultList.add(new HealthScoreResult(
nameEnum,
dimensionTotalWeightMap.getOrDefault(nameEnum.getDimensionEnum().name(), 0f),
allDimensionTotalWeight,
baseConfig,
checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>()))
);
}
return healthScoreResultList;
}
@Override
public List<HealthScoreResult> getDimensionHealthScoreResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum) {
List<HealthCheckResultPO> poList = healthCheckResultService.getClusterResourcesHealthCheckResult(clusterPhyId, dimensionEnum.getDimension());
// <检查项,<通过的数量,不通过的数量>>
Map<String, List<HealthCheckResultPO>> checkResultMap = new HashMap<>();
for (HealthCheckResultPO po: poList) {
checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>());
checkResultMap.get(po.getConfigName()).add(po);
}
// 每个维度的权重和
Float totalWeight = 0f;
Map<String, BaseClusterHealthConfig> configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId);
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimension(dimensionEnum)) {
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
if (baseConfig == null) {
continue;
}
totalWeight += baseConfig.getWeight();
}
Float allDimensionTotalWeight = configMap.values().stream().map(elem -> elem.getWeight()).reduce(Float::sum).get();
List<HealthScoreResult> healthScoreResultList = new ArrayList<>();
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimension(dimensionEnum)) {
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
if (baseConfig == null) {
continue;
}
healthScoreResultList.add(new HealthScoreResult(nameEnum, totalWeight, allDimensionTotalWeight, baseConfig, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>())));
}
return healthScoreResultList;
}
@Override
public List<HealthScoreResult> getResHealthScoreResult(Long clusterPhyId, Integer dimension, String resNme) {
List<HealthCheckResultPO> poList = healthCheckResultService.getClusterResourcesHealthCheckResult(clusterPhyId, dimension);
Map<String, List<HealthCheckResultPO>> checkResultMap = new HashMap<>();
for (HealthCheckResultPO po: poList) {
checkResultMap.putIfAbsent(po.getConfigName(), new ArrayList<>());
checkResultMap.get(po.getConfigName()).add(po);
}
// 每个维度的权重和
Float totalWeight = 0f;
Map<String, BaseClusterHealthConfig> configMap = healthCheckResultService.getClusterHealthConfig(clusterPhyId);
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimensionCode(dimension)) {
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
if (baseConfig == null) {
continue;
}
totalWeight += baseConfig.getWeight();
}
List<HealthScoreResult> healthScoreResultList = new ArrayList<>();
for (HealthCheckNameEnum nameEnum: HealthCheckNameEnum.getByDimensionCode(dimension)) {
BaseClusterHealthConfig baseConfig = configMap.get(nameEnum.getConfigName());
if (baseConfig == null) {
continue;
}
healthScoreResultList.add(new HealthScoreResult(nameEnum, totalWeight, null, baseConfig, checkResultMap.getOrDefault(nameEnum.getConfigName(), new ArrayList<>())));
}
return healthScoreResultList;
}
private float getAllHealthScore(List<HealthScoreResult> healthScoreResultList){
if(CollectionUtils.isEmpty(healthScoreResultList)){return 0f;}
return healthScoreResultList.stream().map(elem -> elem.calAllWeightHealthScore()).reduce(Float::sum).get();
}
private float getDimensionHealthScore(List<HealthScoreResult> healthScoreResultList){
if(CollectionUtils.isEmpty(healthScoreResultList)){return 0f;}
return healthScoreResultList.stream().map(elem -> elem.calDimensionWeightHealthScore()).reduce(Float::sum).get();
}
private float getHealthCheckPassed(List<HealthScoreResult> healthScoreResultList){
if(CollectionUtils.isEmpty(healthScoreResultList)){return 0f;}
return Float.valueOf(healthScoreResultList.stream().filter(elem -> elem.getPassed()).count());
}
}

View File

@@ -1,50 +0,0 @@
package com.xiaojukeji.know.streaming.km.core.service.health.state;
import com.xiaojukeji.know.streaming.km.common.bean.entity.health.HealthScoreResult;
import com.xiaojukeji.know.streaming.km.common.bean.entity.metrics.*;
import com.xiaojukeji.know.streaming.km.common.enums.health.HealthCheckDimensionEnum;
import java.util.List;
public interface HealthStateService {
/**
* 集群健康指标
*/
ClusterMetrics calClusterHealthMetrics(Long clusterPhyId);
/**
* 获取Broker健康指标
*/
BrokerMetrics calBrokerHealthMetrics(Long clusterPhyId, Integer brokerId);
/**
* 获取Topic健康指标
*/
TopicMetrics calTopicHealthMetrics(Long clusterPhyId, String topicName);
/**
* 获取Group健康指标
*/
GroupMetrics calGroupHealthMetrics(Long clusterPhyId, String groupName);
/**
* 获取Zookeeper健康指标
*/
ZookeeperMetrics calZookeeperHealthMetrics(Long clusterPhyId);
/**
* 获取集群健康检查结果
*/
List<HealthScoreResult> getClusterHealthResult(Long clusterPhyId);
/**
* 获取集群某个维度健康检查结果
*/
List<HealthScoreResult> getDimensionHealthResult(Long clusterPhyId, HealthCheckDimensionEnum dimensionEnum);
/**
* 获取集群某个资源的健康检查结果
*/
List<HealthScoreResult> getResHealthResult(Long clusterPhyId, Integer dimension, String resNme);
}

Some files were not shown because too many files have changed in this diff Show More