Copilot commented on code in PR #56:
URL: https://github.com/apache/dubbo-benchmark/pull/56#discussion_r2667748252


##########
duubo_dlt/dubbo-provider/src/main/java/com/dubbo/provider/TestServiceImpl.java:
##########
@@ -0,0 +1,38 @@
+package com.dubbo.provider;
+
+import com.dubbo.common.aop.DubboStatManager;
+import com.dubbo.common.api.TestService;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+
+
+@DubboService
+public class TestServiceImpl implements TestService {
+
+    @Autowired
+    private DubboStatManager statManager;
+    @Override
+    public String sayHello(String name) {
+        // 模拟业务处理时间
+        try {
+            int processTime = (int) (Math.random() * 50);
+            Thread.sleep(processTime);
+            return String.format("Hello %s from Provider-%s", name);

Review Comment:
   This format call refers to 2 argument(s) but only supplies 1 argument(s).



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/NettyClient.java:
##########
@@ -0,0 +1,189 @@
+package com.dubbo.common.netty;
+
+import com.dubbo.common.conf.ClientType;
+import com.dubbo.common.entry.ConsumerTestResult;
+import com.dubbo.common.entry.Message;
+import com.dubbo.common.entry.QoPData;
+import com.dubbo.common.entry.TestConfig;
+import com.dubbo.common.netty.decoder.MessageDecoder;
+import com.dubbo.common.netty.encoder.MessageEncoder;
+import com.dubbo.common.netty.protocol.*;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import io.netty.handler.codec.LengthFieldPrepender;
+import io.netty.handler.timeout.IdleStateHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * Netty客户端
+ */
+public class NettyClient {
+    private static final Logger logger = 
LoggerFactory.getLogger(NettyClient.class);
+
+    private final String host;
+    private final int port;
+    private final String clientId;
+
+    private EventLoopGroup group;
+    private Channel channel;
+    private List<ChannelHandler> channelHandler;
+
+    private final ScheduledExecutorService reconnectScheduler =
+            Executors.newSingleThreadScheduledExecutor();
+
+    public NettyClient(String host, int port, String clientId) {
+        this.host = host;
+        this.port = port;
+        this.clientId = clientId;
+    }
+    public void setChannelHandler(List<ChannelHandler> channelHandler) {
+        this.channelHandler = channelHandler;
+    }
+
+    /**
+     * 连接到服务器
+     */
+    public void connect() throws InterruptedException {
+        if (channel != null && channel.isActive()) {
+            return;
+        }
+
+        group = new NioEventLoopGroup(1);
+
+        Bootstrap bootstrap = new Bootstrap();
+        bootstrap.group(group)
+                .channel(NioSocketChannel.class)
+                .option(ChannelOption.TCP_NODELAY, true)
+                .option(ChannelOption.SO_KEEPALIVE, true)
+                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
+                .handler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    protected void initChannel(SocketChannel ch) {
+                        ChannelPipeline pipeline = ch.pipeline();
+
+                        pipeline.addLast(new LengthFieldBasedFrameDecoder(
+                                1024 * 1024, 0, 4, 0, 4));
+                        pipeline.addLast(new LengthFieldPrepender(4));
+
+                        pipeline.addLast(new IdleStateHandler(0, 15, 0));
+
+                        // 编解码器
+                        pipeline.addLast(new MessageEncoder());
+                        pipeline.addLast(new MessageDecoder());
+
+                        // 业务处理器
+                        channelHandler.forEach(channel -> 
{pipeline.addLast(channel);});
+                    }
+                });
+
+        ChannelFuture future = bootstrap.connect(host, port).sync();
+        this.channel = future.channel();
+        logger.info("是否活跃: {}",channel.isActive());
+        logger.info("Netty客户端连接成功: {}:{}", host, port);
+    }
+
+    /**
+     * 向服务器注册
+     */
+    private void registerToServer() {
+        RegisterMessage registerMsg = new RegisterMessage();
+        registerMsg.setClientId(clientId);
+        registerMsg.setClientType(ClientType.CONSUMER);
+        registerMsg.setTimestamp(System.currentTimeMillis());
+
+        sendMessage(registerMsg);
+    }
+
+    /**
+     * 发送消息
+     */
+    public void sendMessage(Message message) {
+        logger.info("是否活跃: {}",channel.isActive());

Review Comment:
   Variable [channel](1) may be null at this access as suggested by [this](2) 
null guard.



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/NettyServer.java:
##########
@@ -0,0 +1,265 @@
+package com.dubbo.common.netty;
+
+
+import com.dubbo.common.conf.ClientType;
+import com.dubbo.common.conf.ControlCommand;
+import com.dubbo.common.entry.ClientSession;
+import com.dubbo.common.entry.Message;
+import com.dubbo.common.entry.TestConfig;
+import com.dubbo.common.netty.decoder.MessageDecoder;
+import com.dubbo.common.netty.encoder.MessageEncoder;
+import com.dubbo.common.netty.protocol.ControlMessage;
+import com.dubbo.common.netty.protocol.ShutdownMessage;
+import com.alibaba.fastjson2.JSONObject;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
+import io.netty.handler.codec.LengthFieldPrepender;
+import io.netty.handler.timeout.IdleStateHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * Netty服务器(Agent端使用)
+ */
+
+public class NettyServer {
+    private static final Logger logger = 
LoggerFactory.getLogger(NettyServer.class);
+
+    private final int port;
+    private EventLoopGroup bossGroup;
+    private EventLoopGroup workerGroup;
+    private Channel channel;
+    
+    // 客户端连接管理
+    private final Map<String, ClientSession> clientSessions = new 
ConcurrentHashMap<>();
+    private final Map<String, CompletableFuture<Message>> pendingRequests = 
new ConcurrentHashMap<>();
+    private List<ChannelHandler> customHandlers;
+    public NettyServer(int port) {
+        this.port = port;
+    }
+    public void setCustomHandlers(List<ChannelHandler> customHandlers) {
+        this.customHandlers = customHandlers;
+    }
+    
+    /**
+     * 启动服务器
+     */
+    public void start() throws InterruptedException {
+        bossGroup = new NioEventLoopGroup(2);
+        workerGroup = new NioEventLoopGroup(2);
+        
+        try {
+            ServerBootstrap bootstrap = new ServerBootstrap();
+            bootstrap.group(bossGroup, workerGroup)
+                    .channel(NioServerSocketChannel.class)
+                    .childHandler(new ChannelInitializer<SocketChannel>() {
+                        @Override
+                        protected void initChannel(SocketChannel ch) {
+                            ChannelPipeline pipeline = ch.pipeline();
+                            
+                            // 解决粘包/拆包
+                            pipeline.addLast(new LengthFieldBasedFrameDecoder(
+                                    1024 * 1024, 0, 4, 0, 4));
+                            pipeline.addLast(new LengthFieldPrepender(4));
+                            
+                            // 空闲检测
+                            pipeline.addLast(new IdleStateHandler(30, 0, 0));
+                            
+                            // 编解码器
+                            pipeline.addLast(new MessageEncoder());
+                            pipeline.addLast(new MessageDecoder());
+                            
+                            // 业务处理器
+                            customHandlers.forEach(handler -> 
pipeline.addLast((handler)));
+                        }
+                    })
+                    .option(ChannelOption.SO_BACKLOG, 128)
+                    .childOption(ChannelOption.SO_KEEPALIVE, true)
+                    .childOption(ChannelOption.TCP_NODELAY, true);
+
+            ChannelFuture future = bootstrap.bind(port).sync();
+            channel = future.channel();
+
+            // 注册关闭钩子
+            Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+    
+    /**
+     * 注册客户端
+     */
+    public void registerClient(String clientId, Channel channel, ClientType 
type) {
+        ClientSession session = new ClientSession(clientId, channel, type);
+        clientSessions.put(clientId, session);
+        logger.info("客户端注册: {} ({})", clientId, type);
+    }
+    
+    /**
+     * 注销客户端
+     */
+    public void unregisterClient(String clientId) {
+        clientSessions.remove(clientId);
+        logger.info("客户端注销: {}", clientId);
+    }
+    
+    /**
+     * 发送消息给指定客户端
+     */
+    public void sendToClient(String clientId, Message message) {
+        ClientSession session = clientSessions.get(clientId);
+        if (session != null && session.getChannel().isActive()) {
+            session.getChannel().writeAndFlush(message);
+        } else {
+            logger.warn("客户端未连接或不存在: {}", clientId);
+        }
+    }
+    
+    /**
+     * 广播消息给所有客户端或者指定的客户端
+     */
+    public void broadcast(Message message, ClientType filterType) {
+        clientSessions.values().stream()
+                .filter(session -> filterType == null || session.getType() == 
filterType)
+                .filter(session -> session.getChannel().isActive())
+                .forEach(session -> {
+                    session.getChannel().writeAndFlush(message);
+                });
+    }
+    
+    /**
+     * 发送控制指令给Consumer
+     */
+    public void sendControlToConsumer(String consumerId, ControlCommand 
command, String data) {
+        ControlMessage msg = new ControlMessage();
+        msg.setCommand(command);
+        msg.setData(data);
+        msg.setClientId(consumerId);
+        msg.setTimestamp(System.currentTimeMillis());
+        sendToClient(consumerId, msg);
+    }
+    
+    /**
+     * 请求Consumer开始测试
+     */
+    public void startTest(String consumerId, TestConfig config) {
+        ControlMessage msg = new ControlMessage();
+        msg.setCommand(ControlCommand.START_TEST);
+        msg.setData(JSONObject.toJSONString(config));
+        msg.setTargetClientId(consumerId);
+        msg.setTimestamp(System.currentTimeMillis());
+        
+        sendToClient(consumerId, msg);
+    }
+    
+    /**
+     * 请求Consumer停止测试
+     */
+    public void stopTest(String consumerId) {
+        ControlMessage msg = new ControlMessage();
+        msg.setCommand(ControlCommand.STOP_TEST);
+        msg.setTargetClientId(consumerId);
+        msg.setTimestamp(System.currentTimeMillis());
+        
+        sendToClient(consumerId, msg);
+    }
+    
+    /**
+     * 请求Provider发送QOP数据
+     */
+    public void requestQoPData(String providerId) {
+        ControlMessage msg = new ControlMessage();
+        msg.setCommand(ControlCommand.REQUEST_QOP);
+        msg.setTargetClientId(providerId);
+        msg.setTimestamp(System.currentTimeMillis());
+        
+        sendToClient(providerId, msg);
+    }
+    
+    /**
+     * 发送下线指令
+     */
+    public void sendShutdown(String clientId) {
+        ShutdownMessage msg = new ShutdownMessage();
+        msg.setTargetClientId(clientId);
+        msg.setTimestamp(System.currentTimeMillis());
+        
+        sendToClient(clientId, msg);
+    }
+    
+    /**
+     * 关闭服务器
+     */
+    public void shutdown() {
+        // 通知所有客户端
+        broadcast(new ShutdownMessage(), null);
+        
+        if (channel != null) {
+            channel.close();
+        }
+        if (workerGroup != null) {
+            workerGroup.shutdownGracefully();
+        }
+        if (bossGroup != null) {
+            bossGroup.shutdownGracefully();
+        }
+        
+        logger.info("Netty服务器已关闭");
+    }
+    
+    /**
+     * 获取客户端会话
+     */
+    public ClientSession getClientSession(String clientId) {
+        return clientSessions.get(clientId);
+    }
+    
+    /**
+     * 获取所有客户端会话
+     */
+    public Map<String, ClientSession> getAllSessions() {

Review Comment:
   getAllSessions exposes the internal representation stored in field 
clientSessions. The value may be modified [after this call to 
getAllSessions](1).
   getAllSessions exposes the internal representation stored in field 
clientSessions. The value may be modified [after this call to 
getAllSessions](2).
   getAllSessions exposes the internal representation stored in field 
clientSessions. The value may be modified [after this call to 
getAllSessions](3).



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/protocol/ControlMessage.java:
##########
@@ -0,0 +1,18 @@
+package com.dubbo.common.netty.protocol;
+
+import com.dubbo.common.conf.ControlCommand;
+import com.dubbo.common.conf.MessageType;
+import com.dubbo.common.entry.Message;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)

Review Comment:
   This method overrides [Message.canEqual](1); it is advisable to add an 
Override annotation.



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/protocol/ShutdownMessage.java:
##########
@@ -0,0 +1,18 @@
+package com.dubbo.common.netty.protocol;
+
+import com.dubbo.common.conf.MessageType;
+import com.dubbo.common.entry.Message;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+// 下线消息
+@Data
+@EqualsAndHashCode(callSuper = true)

Review Comment:
   This method overrides [Message.canEqual](1); it is advisable to add an 
Override annotation.



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/protocol/ReadyMessage.java:
##########
@@ -0,0 +1,19 @@
+package com.dubbo.common.netty.protocol;
+
+import com.dubbo.common.conf.MessageType;
+import com.dubbo.common.entry.Message;
+import com.dubbo.common.entry.TestConfig;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)

Review Comment:
   This method overrides [Message.canEqual](1); it is advisable to add an 
Override annotation.



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/aop/DubboInvokeStatData.java:
##########
@@ -0,0 +1,170 @@
+package com.dubbo.common.aop;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Dubbo调用统计数据
+ */
+public class DubboInvokeStatData {
+    
+    // 基础统计
+    private final AtomicInteger totalCalls = new AtomicInteger(0);
+    private final AtomicInteger successfulCalls = new AtomicInteger(0);
+    private final AtomicLong totalResponseTime = new AtomicLong(0);
+    private final long startTime = System.currentTimeMillis();
+    
+    // 响应时间记录
+    private final List<Long> responseTimes;
+    private final int maxResponseTimeRecords;
+    
+    // 错误记录
+    private final Queue<String> recentErrors;
+    private final int maxErrorRecords = 100;
+    
+    // 按方法名维度的统计
+    private final Map<String, MethodStat> methodStats = new HashMap<>();
+    
+    public DubboInvokeStatData(int maxRecords) {
+        this.maxResponseTimeRecords = maxRecords;
+        this.responseTimes = Collections.synchronizedList(new ArrayList<>());
+        this.recentErrors = new ArrayDeque<>(maxErrorRecords);
+    }
+    
+    /**
+     * 记录一次调用
+     */
+    public void recordCall(String methodName, long responseTime, boolean 
success, String error) {
+        totalCalls.incrementAndGet();
+        
+        if (success) {
+            successfulCalls.incrementAndGet();
+            totalResponseTime.addAndGet(responseTime);
+            recordResponseTime(responseTime);
+            
+            // 方法维度统计
+            MethodStat methodStat = methodStats.computeIfAbsent(methodName, 
+                k -> new MethodStat(maxResponseTimeRecords));
+            methodStat.recordSuccess(responseTime);
+        } else {
+            if (error != null) {
+                recordError(methodName + " - " + error);
+            }
+            
+            // 方法维度统计
+            MethodStat methodStat = methodStats.computeIfAbsent(methodName, 
+                k -> new MethodStat(maxResponseTimeRecords));
+            methodStat.recordFailure();
+        }
+    }
+    
+    private void recordResponseTime(long responseTime) {
+        synchronized (responseTimes) {
+            if (responseTimes.size() >= maxResponseTimeRecords) {
+                responseTimes.remove(0);
+            }
+            responseTimes.add(responseTime);
+        }
+    }
+    
+    private void recordError(String error) {
+        synchronized (recentErrors) {
+            if (recentErrors.size() >= maxErrorRecords) {
+                recentErrors.poll();
+            }
+            recentErrors.offer(error + " at " + new Date());

Review Comment:
   Method recordError ignores exceptional return value of Queue<String>.offer.
   ```suggestion
               String errorWithTimestamp = error + " at " + new Date();
               boolean added = recentErrors.offer(errorWithTimestamp);
               if (!added) {
                   // In case the underlying Queue implementation is 
capacity-bounded
                   // and refuses new elements, remove the oldest and force-add.
                   recentErrors.poll();
                   recentErrors.add(errorWithTimestamp);
               }
   ```



##########
duubo_dlt/dubbo-agent/src/main/java/com/dubbo/dlt/NettyServeragentService.java:
##########
@@ -0,0 +1,125 @@
+package com.dubbo.dlt;
+
+import com.dubbo.common.conf.TestMode;
+import com.dubbo.common.entry.TestConfig;
+import com.dubbo.dlt.handler.AgentNettyHandler;
+import com.dubbo.common.netty.NettyServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.util.Collections;
+
+public class NettyServeragentService {
+    private static final Logger logger = 
LoggerFactory.getLogger(NettyServeragentService.class);
+
+    // 安全获取字符串环境变量
+    private String safeGetEnv(String key, String defaultValue) {
+        String value = System.getenv(key);
+        return (value != null && !value.trim().isEmpty()) ? value.trim() : 
defaultValue;
+    }
+
+    // 安全获取整数环境变量
+    private Integer safeGetEnvInt(String key, Integer defaultValue) {
+        String value = System.getenv(key);
+        if (value == null || value.trim().isEmpty()) {
+            return defaultValue;
+        }
+        try {
+            return Integer.valueOf(value.trim());
+        } catch (NumberFormatException e) {
+            logger.warn("环境变量 {} 不是有效的整数: {}, 使用默认值 {}", key, value, 
defaultValue);
+            return defaultValue;
+        }
+    }
+
+    // 安全获取长整型环境变量(替换 EnvDurationUtils.getDurationInSeconds)
+    private Long safeGetEnvLong(String key, Long defaultValue) {
+        String value = System.getenv(key);
+        if (value == null || value.trim().isEmpty()) {
+            return defaultValue;
+        }
+        try {
+            return Long.valueOf(value.trim());
+        } catch (NumberFormatException e) {
+            logger.warn("环境变量 {} 不是有效的长整数: {}, 使用默认值 {}", key, value, 
defaultValue);
+            return defaultValue;
+        }
+    }
+
+    // 安全获取秒数(专门处理 AGENT_DURATION_SECONDS)
+    private Long safeGetDurationSeconds(String key, Long defaultValue) {
+        return safeGetEnvLong(key, defaultValue);
+    }
+
+    String nettyPortTest = System.getProperty("agent.netty.port", "8888");
+    private int nettyPort = Integer.parseInt(nettyPortTest);

Review Comment:
   Potential uncaught 'java.lang.NumberFormatException'.
   ```suggestion
       // 安全解析 Netty 端口配置,避免 NumberFormatException
       private int safeParsePort(String portStr, int defaultPort) {
           if (portStr == null || portStr.trim().isEmpty()) {
               return defaultPort;
           }
           String trimmed = portStr.trim();
           try {
               return Integer.parseInt(trimmed);
           } catch (NumberFormatException e) {
               logger.warn("系统属性 agent.netty.port 不是有效的整数: {}, 使用默认值 {}", 
trimmed, defaultPort);
               return defaultPort;
           }
       }
   
       String nettyPortTest = System.getProperty("agent.netty.port", "8888");
       private int nettyPort = safeParsePort(nettyPortTest, 8888);
   ```



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/protocol/ResultMessage.java:
##########
@@ -0,0 +1,20 @@
+package com.dubbo.common.netty.protocol;
+
+import com.dubbo.common.conf.MessageType;
+import com.dubbo.common.entry.Message;
+import com.dubbo.common.entry.ConsumerTestResult;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+// 结果消息
+@Data
+@EqualsAndHashCode(callSuper = true)

Review Comment:
   This method overrides [Message.canEqual](1); it is advisable to add an 
Override annotation.



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/protocol/QoPMessage.java:
##########
@@ -0,0 +1,19 @@
+package com.dubbo.common.netty.protocol;
+
+import com.dubbo.common.conf.MessageType;
+import com.dubbo.common.entry.Message;
+import com.dubbo.common.entry.QoPData;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+// QOP消息
+@Data
+@EqualsAndHashCode(callSuper = true)

Review Comment:
   This method overrides [Message.canEqual](1); it is advisable to add an 
Override annotation.



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/protocol/AckMessage.java:
##########
@@ -0,0 +1,20 @@
+package com.dubbo.common.netty.protocol;
+
+import com.dubbo.common.conf.MessageType;
+import com.dubbo.common.entry.Message;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+// 确认消息
+@Data
+@EqualsAndHashCode(callSuper = true)

Review Comment:
   This method overrides [Message.canEqual](1); it is advisable to add an 
Override annotation.



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/entry/ProvideTestResult.java:
##########
@@ -0,0 +1,30 @@
+package com.dubbo.common.entry;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ProvideTestResult {
+    private List<ProvideResult> provideResultList;
+    private Integer count;
+
+    public List<ProvideResult> getProvideResultList() {

Review Comment:
   getProvideResultList exposes the internal representation stored in field 
provideResultList. The value may be modified [after this call to 
getProvideResultList](1).



##########
duubo_dlt/dubbo-common/src/main/java/com/dubbo/common/netty/protocol/RegisterMessage.java:
##########
@@ -0,0 +1,23 @@
+package com.dubbo.common.netty.protocol;
+
+import com.dubbo.common.conf.MessageType;
+import com.dubbo.common.entry.Message;
+import com.dubbo.common.entry.TestConfig;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Map;
+
+// 注册消息
+@Data
+@EqualsAndHashCode(callSuper = true)

Review Comment:
   This method overrides [Message.canEqual](1); it is advisable to add an 
Override annotation.



##########
duubo_dlt/dubbo-agent/src/main/java/com/dubbo/dlt/trendGenerator/ProviderCallTrendGenerator.java:
##########
@@ -0,0 +1,427 @@
+package com.dubbo.dlt.trendGenerator;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.dubbo.common.entry.CpuMemTrendData;
+import com.dubbo.dlt.trendGenerator.entry.CallRecord;
+import com.dubbo.dlt.trendGenerator.entry.TrendData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class ProviderCallTrendGenerator {
+    private static final Logger logger = 
LoggerFactory.getLogger(ProviderCallTrendGenerator.class);
+
+    private static final DateTimeFormatter SECOND_FORMAT = 
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    private static final DateTimeFormatter MINUTE_FORMAT = 
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+
+    /**
+     * 核心入口方法 - 最终修正版:严格过滤非选中方法折线
+     */
+    public static void generateCallTrendHtml(String rawEscapeJson, String 
htmlGeneratePath) throws IOException {
+        String cleanJson = cleanEscapeJson(rawEscapeJson);
+        logger.info("JSON转义清理完成,开始解析数据");
+
+        JSONObject rootJson = JSON.parseObject(cleanJson);
+        List<CallRecord> callRecords = parseCallRecords(rootJson);
+        List<CpuMemTrendData> cpuMemTrendDataList = parseCpuMemData(rootJson);
+
+        // 接口调用趋势数据(秒级+分钟级)
+        List<TrendData> secondTrendData = aggregateDynamicData(callRecords, 
"second");
+        String secondDataJson = JSON.toJSONString(secondTrendData);
+        List<TrendData> minuteTrendData = aggregateDynamicData(callRecords, 
"minute");
+        String minuteDataJson = JSON.toJSONString(minuteTrendData);
+
+        // CPU内存趋势数据(秒级原始+分钟级聚合)
+        String cpuMemSecondDataJson = JSON.toJSONString(cpuMemTrendDataList);
+        List<CpuMemTrendData> cpuMemMinuteTrendDataList = 
aggregateCpuMemData(cpuMemTrendDataList, "minute");
+        String cpuMemMinuteDataJson = 
JSON.toJSONString(cpuMemMinuteTrendDataList);
+
+        // 提取所有唯一方法名 传给前端做下拉选择
+        Set<String> uniqueMethods = getUniqueMethodNames(callRecords);
+        String methodNamesJson = JSON.toJSONString(uniqueMethods);
+
+        String htmlTemplate = getDynamicEchartsHtmlTemplate();
+        String finalHtml = htmlTemplate
+                .replace("{{SECOND_DATA}}", secondDataJson)
+                .replace("{{MINUTE_DATA}}", minuteDataJson)
+                .replace("{{CPU_MEM_SECOND_DATA}}", cpuMemSecondDataJson)
+                .replace("{{CPU_MEM_MINUTE_DATA}}", cpuMemMinuteDataJson)
+                .replace("{{METHOD_NAMES}}", methodNamesJson);
+
+        writeHtmlToFile(finalHtml, htmlGeneratePath);
+        logger.info("✅ 动态趋势图生成完成!路径:{},共解析到【{}】个不同的接口方法", htmlGeneratePath, 
uniqueMethods.size());
+    }
+
+    /**
+     * 原有方法:聚合接口调用的趋势数据(秒/分钟维度)
+     */
+    private static List<TrendData> aggregateDynamicData(List<CallRecord> 
callRecords, String timeUnit) {
+        Map<String, List<CallRecord>> timeGroupMap;
+        if ("second".equalsIgnoreCase(timeUnit)) {
+            timeGroupMap = 
callRecords.stream().collect(Collectors.groupingBy(r -> 
r.getStartTimeDt().format(SECOND_FORMAT)));
+        } else {
+            timeGroupMap = 
callRecords.stream().collect(Collectors.groupingBy(r -> 
r.getStartTimeDt().format(MINUTE_FORMAT)));
+        }
+        List<TrendData> trendDataList = new ArrayList<>();
+        for (Map.Entry<String, List<CallRecord>> entry : 
timeGroupMap.entrySet()) {
+            String time = entry.getKey();
+            List<CallRecord> records = entry.getValue();
+
+            TrendData trendData = new TrendData();
+            trendData.setTime(time);
+            trendData.setTotalCount(records.size());
+
+            long successNum = 
records.stream().filter(CallRecord::isSuccess).count();
+            double successRate = records.size() == 0 ? 0 : (successNum * 
100.0) / records.size();
+            trendData.setSuccessRate(successRate);
+
+            Map<String, Integer> methodCountMap = records.stream()
+                    .collect(Collectors.groupingBy(CallRecord::getMethodName, 
Collectors.summingInt(e -> 1)));
+            trendData.setMethodCountMap(methodCountMap);
+
+            trendDataList.add(trendData);
+        }
+        trendDataList.sort(Comparator.comparing(TrendData::getTime));
+        return trendDataList;
+    }
+
+    /**
+     * CPU/JVM内存数据按【分钟】聚合,取平均值
+     */
+    private static List<CpuMemTrendData> 
aggregateCpuMemData(List<CpuMemTrendData> cpuMemList, String timeUnit) {
+        if (!"minute".equalsIgnoreCase(timeUnit) || cpuMemList.isEmpty()) {
+            return cpuMemList;
+        }
+        Map<String, List<CpuMemTrendData>> minuteGroupMap = cpuMemList.stream()
+                .collect(Collectors.groupingBy(data -> 
LocalDateTime.parse(data.getTime(), SECOND_FORMAT).format(MINUTE_FORMAT)));
+
+        List<CpuMemTrendData> minuteDataList = new ArrayList<>();
+        for (Map.Entry<String, List<CpuMemTrendData>> entry : 
minuteGroupMap.entrySet()) {
+            String minuteTime = entry.getKey();
+            List<CpuMemTrendData> secDataList = entry.getValue();
+
+            CpuMemTrendData minuteData = new CpuMemTrendData();
+            minuteData.setTime(minuteTime);
+            int avgCpu = 
secDataList.stream().mapToInt(CpuMemTrendData::getCpuUsage).sum() / 
secDataList.size();
+            int avgMem = 
secDataList.stream().mapToInt(CpuMemTrendData::getMemoryUsage).sum() / 
secDataList.size();
+            minuteData.setCpuUsage(avgCpu);
+            minuteData.setMemoryUsage(avgMem);
+            minuteDataList.add(minuteData);
+        }
+        minuteDataList.sort(Comparator.comparing(CpuMemTrendData::getTime));
+        return minuteDataList;
+    }
+
+    /**
+     * 获取所有唯一的接口方法名
+     */
+    private static Set<String> getUniqueMethodNames(List<CallRecord> 
callRecords) {
+        return 
callRecords.stream().map(CallRecord::getMethodName).collect(Collectors.toSet());
+    }
+
+    /**
+     * 清理JSON转义字符
+     */
+    public static String cleanEscapeJson(String escapeJson) {
+        if (escapeJson == null || escapeJson.isEmpty()) {
+            return "";
+        }
+        String cleanJson = escapeJson.replaceAll("\\\\", 
"").replaceAll("^\"|\"$", "");
+        Object jsonObj = JSON.parse(cleanJson);
+        return JSONObject.toJSONString(jsonObj);
+    }
+
+    /**
+     * 解析接口调用数据
+     */
+    public static List<CallRecord> parseCallRecords(JSONObject root) {
+        List<CallRecord> records = new ArrayList<>();
+        JSONObject allResults = root.getJSONObject("allResults");
+        JSONObject providerObj = 
allResults.getJSONObject("TestService-provider");
+        List<JSONObject> list = 
providerObj.getJSONArray("provideResultList").toJavaList(JSONObject.class);
+
+        for (JSONObject item : list) {
+            String methodName = item.getString("methodName");
+            String startTime = item.getString("startTime");
+            boolean success = item.getBooleanValue("success");
+            records.add(new CallRecord(methodName, startTime, success));
+        }
+        return records;
+    }
+
+    /**
+     * 解析CPU使用率+JVM内存使用率,生成每秒趋势数据
+     */
+    public static List<CpuMemTrendData> parseCpuMemData(JSONObject root) {
+        List<CpuMemTrendData> cpuMemList = new ArrayList<>();
+        String cpuStartTime = root.getString("cpuStartTime");
+        String cpuEndTime = root.getString("cpuEndTime");
+        JSONObject cpuUsageJson = root.getJSONObject("cpuUsage");
+        JSONObject memoryUsageJson = root.getJSONObject("memoryUsage");
+
+        if (cpuUsageJson == null || memoryUsageJson == null || cpuStartTime == 
null) {
+            return cpuMemList;
+        }
+        LocalDateTime startDt = LocalDateTime.parse(cpuStartTime.substring(0, 
19), SECOND_FORMAT);
+        Set<String> cpuKeySet = cpuUsageJson.keySet();
+
+        for (String secondKey : cpuKeySet) {
+            int secondStep = Integer.parseInt(secondKey);
+            LocalDateTime currDt = startDt.plusSeconds(secondStep - 1);
+            String currTime = currDt.format(SECOND_FORMAT);
+
+            CpuMemTrendData data = new CpuMemTrendData();
+            data.setTime(currTime);
+            data.setCpuUsage(cpuUsageJson.getInteger(secondKey));
+            data.setMemoryUsage(memoryUsageJson.getInteger(secondKey));
+            cpuMemList.add(data);

Review Comment:
   Potential uncaught 'java.lang.NumberFormatException'.
   ```suggestion
               try {
                   int secondStep = Integer.parseInt(secondKey);
                   LocalDateTime currDt = startDt.plusSeconds(secondStep - 1);
                   String currTime = currDt.format(SECOND_FORMAT);
   
                   CpuMemTrendData data = new CpuMemTrendData();
                   data.setTime(currTime);
                   data.setCpuUsage(cpuUsageJson.getInteger(secondKey));
                   data.setMemoryUsage(memoryUsageJson.getInteger(secondKey));
                   cpuMemList.add(data);
               } catch (NumberFormatException e) {
                   logger.warn("Invalid CPU/memory second key '{}', skipping 
entry", secondKey, e);
               }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to