fix: add intelligence implementation (#533)
* feat: add trace query and analyze filter
* feat: add log query
* feat: add metrics query
* feat: add controller layer code; improve the documentation
* feat: operator add intelligence yaml and nacos properties
* fix: transform en
diff --git a/ozhera-intelligence/README.md b/ozhera-intelligence/README.md
index fcbee8e..6df452c 100644
--- a/ozhera-intelligence/README.md
+++ b/ozhera-intelligence/README.md
@@ -30,4 +30,16 @@
## ozhera-intelligence-server
+ Service to initiate intelligent functionalities.
++ Access to the ozhera-intelligence-server requires the x-token to be included in the header for authorized access to ozhera's data.
+## ozhera-intelligence-service
+
++ LogService: Retrieves log data
++ MetricsService: Retrieves monitoring metric data
++ TraceService: Retrieves trace data
+
+# Important Dependencies
+It relies on a Xiaomi open-source [Agent builder platform--m78](https://github.com/XiaoMi/mone/tree/master/m78-all)
+
+# Intelligent Workflow (Sequence Diagram)
+
\ No newline at end of file
diff --git a/ozhera-intelligence/README_cn.md b/ozhera-intelligence/README_cn.md
index d1f3e85..d2a448c 100644
--- a/ozhera-intelligence/README_cn.md
+++ b/ozhera-intelligence/README_cn.md
@@ -29,3 +29,17 @@
## ozhera-intelligence-server
+ 智能化功能启动服务。
++ ozhera-intelligence-server访问需要header中带上x-token,实现有权限的访问ozhera的数据。
+
+## ozhera-intelligence-service
+
++ LogService,取日志数据
++ MetricsService,取监控指标数据
++ TraceService,取链路数据
+
+# 重要依赖
+会依赖小米开源的一款[Agent构建平台——m78](https://github.com/XiaoMi/mone/tree/master/m78-all)
+
+
+# 智能化工作流程(时序图)
+
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/pom.xml b/ozhera-intelligence/ozhera-intelligence-domain/pom.xml
index e1cf6ce..605a031 100644
--- a/ozhera-intelligence/ozhera-intelligence-domain/pom.xml
+++ b/ozhera-intelligence/ozhera-intelligence-domain/pom.xml
@@ -41,6 +41,10 @@
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.ozhera</groupId>
+ <artifactId>trace-etl-domain</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/HeraRootCaseAnalyseRes.java b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/HeraRootCaseAnalyseRes.java
new file mode 100644
index 0000000..bca831c
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/HeraRootCaseAnalyseRes.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.domain.rootanalysis;
+
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * @author dingtao
+ * @date 2025/1/20 11:26
+ */
+@Data
+@ToString
+public class HeraRootCaseAnalyseRes {
+ private Double maxCpuUsage;
+ private Double maxLoad;
+ private Double maxJvmHeapUsage;
+ private Double maxSTWCost;
+ private Double STWCountOf1m;
+ private Double cpuCount;
+}
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/Metric.java b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/Metric.java
new file mode 100644
index 0000000..d3cd46d
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/Metric.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.domain.rootanalysis;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class Metric implements Serializable {
+ private String application;
+ private String instance;
+ private String ip;
+ private String job;
+
+ private String replica;
+ private String serverIp;
+
+ //dubbo label
+ private String methodName;
+ private String serviceName;
+
+ //sql
+ private String sqlMethod;
+ private String sql;
+ private String dataSource;
+
+ //redis
+ private String host;
+ private String port;
+ private String dbindex;
+ private String method;
+
+ //env
+ private String serverEnv;
+
+ //container info
+ private String container_label_PROJECT_ID;
+ private String name;//container name eg:zzytest-20220323221926067
+ private String container;//container name for k8s eg:youpin-gateway-dingtao-test2-900329(gitGroup-gitName-envId)
+ private String pod;//pod name for k8s eg:91102-dingtao-test-644758f5b4-jk9t7
+ private String namespace;//namespace name for k8s eg:nrme
+
+ private String lastCreateTime;
+
+ private double value;
+
+ private String timestamp;
+
+ private String traceId;
+
+ private String service;
+
+ private String apiid;
+
+ private String serialid;
+
+ private String sceneid;
+
+ private String pod_ip;
+
+ private String calert;
+
+ private String alert_op;
+
+ private String alert_value;
+
+ private String detailRedirectUrl;
+
+ private String __priority__;
+
+ // downstream service information
+ private String clientProjectId;
+ private String clientProjectName;
+ private String clientEnv;
+ private String clientIp;
+
+}
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricDataRangeSetVector.java b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricDataRangeSetVector.java
new file mode 100644
index 0000000..eac2481
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricDataRangeSetVector.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.domain.rootanalysis;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@ToString
+public class MetricDataRangeSetVector implements Serializable {
+ private Metric metric;
+ private List<List<String>> values;
+}
+
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricDataRangeVector.java b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricDataRangeVector.java
new file mode 100644
index 0000000..8f2e1d2
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricDataRangeVector.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.domain.rootanalysis;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@ToString
+public class MetricDataRangeVector implements Serializable {
+ private String resultType;
+ private List<MetricDataRangeSetVector> result;
+}
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricRangeResponseVector.java b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricRangeResponseVector.java
new file mode 100644
index 0000000..70bc934
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricRangeResponseVector.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.domain.rootanalysis;
+
+import lombok.Data;
+import lombok.ToString;
+
+import java.io.Serializable;
+
+@Data
+@ToString
+public class MetricRangeResponseVector implements Serializable {
+ private String status;
+ private MetricDataRangeVector data;
+}
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricsQueryParam.java b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricsQueryParam.java
index 96541b9..98a6448 100644
--- a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricsQueryParam.java
+++ b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/MetricsQueryParam.java
@@ -20,9 +20,11 @@
import lombok.Builder;
import lombok.Data;
+import lombok.ToString;
@Data
@Builder
+@ToString
public class MetricsQueryParam {
private String env;
@@ -33,6 +35,8 @@
private String startTime;
+ private String endTime;
+
private String duration;
// The error margin between the start time and end time,
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TraceQueryParam.java b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TraceQueryParam.java
index 18fdcdf..b9de720 100644
--- a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TraceQueryParam.java
+++ b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TraceQueryParam.java
@@ -20,13 +20,15 @@
import lombok.Builder;
import lombok.Data;
+import lombok.ToString;
import java.util.List;
@Data
@Builder
+@ToString
public class TraceQueryParam {
private String traceId;
private String env;
- private List<String> timeStamp;
+ private long timeStamp;
}
diff --git a/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TraceTreeNode.java b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TraceTreeNode.java
new file mode 100644
index 0000000..14998c5
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-domain/src/main/java/org/apache/ozhera/intelligence/domain/rootanalysis/TraceTreeNode.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.domain.rootanalysis;
+
+import lombok.Data;
+import org.apache.ozhera.trace.etl.domain.tracequery.Span;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class TraceTreeNode {
+
+ private Span span;
+ private List<TraceTreeNode> children;
+
+ public TraceTreeNode(Span span){
+ this.span = span;
+ this.children = new ArrayList<>();
+ }
+
+ public TraceTreeNode(){
+
+ }
+
+ public void addChild(TraceTreeNode child) {
+ children.add(child);
+ }
+}
diff --git a/ozhera-intelligence/ozhera-intelligence-server/pom.xml b/ozhera-intelligence/ozhera-intelligence-server/pom.xml
index 02cdac4..5664c13 100644
--- a/ozhera-intelligence/ozhera-intelligence-server/pom.xml
+++ b/ozhera-intelligence/ozhera-intelligence-server/pom.xml
@@ -80,7 +80,7 @@
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.15</version>
<configuration>
- <mainClass>org.apache.ozhera.trace.etl.bootstrap.TraceEtlBootstrap</mainClass>
+ <mainClass>org.apache.ozhera.intelligence.bootstrap.IntelligenceBootStrap</mainClass>
</configuration>
<executions>
<execution>
diff --git a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/DubboConfiguration.java b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/DubboConfiguration.java
new file mode 100644
index 0000000..f672299
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/DubboConfiguration.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ozhera.intelligence.config;
+
+import com.alibaba.nacos.api.config.annotation.NacosValue;
+import com.google.common.collect.Maps;
+import org.apache.dubbo.config.ApplicationConfig;
+import org.apache.dubbo.config.ProtocolConfig;
+import org.apache.dubbo.config.RegistryConfig;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class DubboConfiguration {
+
+ @Value("${dubbo.protocol.port}")
+ private int port;
+
+ @Value("${server.port}")
+ private String httpGateWayPort;
+
+ @NacosValue("${dubbo.registry.address}")
+ private String regAddress;
+
+ @Bean
+ public ApplicationConfig applicationConfig() {
+ ApplicationConfig applicationConfig = new ApplicationConfig();
+ applicationConfig.setName("Trace-etl-manager");
+ applicationConfig.setParameters(Maps.newHashMap());
+ applicationConfig.getParameters().put("http_gateway_port", httpGateWayPort);
+ applicationConfig.getParameters().put("dubbo_version", "1.0");
+ applicationConfig.setQosEnable(false);
+ return applicationConfig;
+ }
+
+ @Bean
+ public RegistryConfig registryConfig() {
+ RegistryConfig registryConfig = new RegistryConfig();
+ registryConfig.setAddress(regAddress);
+ return registryConfig;
+ }
+
+ @Bean
+ public ProtocolConfig protocolConfig() {
+ ProtocolConfig protocolConfig = new ProtocolConfig();
+ protocolConfig.setPort(port);
+ protocolConfig.setTransporter("netty4");
+ protocolConfig.setThreadpool("fixed");
+ protocolConfig.setThreads(800);
+ return protocolConfig;
+ }
+
+}
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/NacosConfiguration.java b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/NacosConfiguration.java
new file mode 100644
index 0000000..cb7d29d
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/config/NacosConfiguration.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ozhera.intelligence.config;
+
+import com.alibaba.nacos.api.annotation.NacosProperties;
+import com.alibaba.nacos.spring.context.annotation.config.EnableNacosConfig;
+import com.alibaba.nacos.spring.context.annotation.config.NacosPropertySource;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = "${nacos.address}"))
+@NacosPropertySource(dataId = "hera_intelligence_config", autoRefreshed = true)
+public class NacosConfiguration {
+}
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/controller/RootAnalysisController.java b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/controller/RootAnalysisController.java
index 62ba03c..aad511c 100644
--- a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/controller/RootAnalysisController.java
+++ b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/controller/RootAnalysisController.java
@@ -19,13 +19,23 @@
package org.apache.ozhera.intelligence.controller;
import com.xiaomi.youpin.infra.rpc.Result;
+import com.xiaomi.youpin.infra.rpc.errors.GeneralCodes;
import lombok.extern.slf4j.Slf4j;
+import org.apache.ozhera.intelligence.domain.rootanalysis.HeraRootCaseAnalyseRes;
+import org.apache.ozhera.intelligence.domain.rootanalysis.LogParam;
+import org.apache.ozhera.intelligence.domain.rootanalysis.MetricsQueryParam;
+import org.apache.ozhera.intelligence.service.LogService;
+import org.apache.ozhera.intelligence.service.MetricsService;
+import org.apache.ozhera.intelligence.service.TraceService;
+import org.apache.ozhera.trace.etl.domain.tracequery.Span;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.apache.ozhera.intelligence.domain.rootanalysis.TraceQueryParam;
-import org.apache.ozhera.intelligence.service.RootAnalysisService;
+
+import java.util.List;
+import java.util.Map;
@RestController
@RequestMapping("/analysis")
@@ -33,10 +43,44 @@
public class RootAnalysisController {
@Autowired
- private RootAnalysisService rootAnalysisService;
+ private TraceService traceService;
- @GetMapping("/trace")
- public Result<String> traceRootAnalysis(TraceQueryParam param) {
- return rootAnalysisService.traceRootAnalysis(param);
+ @Autowired
+ private LogService logService;
+
+ @Autowired
+ private MetricsService metricsService;
+
+ @PostMapping("/trace/sectional/span")
+ public Result<List<Span>> traceRootAnalysis(TraceQueryParam param) {
+ try {
+ List<Span> spans = traceService.queryTraceRootAnalysis(param);
+ return Result.success(spans);
+ }catch (Exception e){
+ log.error("trace analyze error , ", e);
+ return Result.fail(GeneralCodes.InternalError, "trace analyze error");
+ }
+ }
+
+ @PostMapping("/log/condition")
+ public Result<List<Map<String, Object>>> logCondition(LogParam param) {
+ try {
+ List<Map<String, Object>> logs = logService.queryLogRootAnalysis(param);
+ return Result.success(logs);
+ }catch (Exception e){
+ log.error("log analyze error , ", e);
+ return Result.fail(GeneralCodes.InternalError, "log analyze error");
+ }
+ }
+
+ @PostMapping("/metrics")
+ public Result<HeraRootCaseAnalyseRes> metrics(MetricsQueryParam param) {
+ try {
+ HeraRootCaseAnalyseRes metrics = metricsService.queryMetricsRootAnalysis(param);
+ return Result.success(metrics);
+ }catch (Exception e){
+ log.error("metrics analyze error , ", e);
+ return Result.fail(GeneralCodes.InternalError, "metrics analyze error");
+ }
}
}
diff --git a/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/filter/TokenValidationFilter.java b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/filter/TokenValidationFilter.java
new file mode 100644
index 0000000..572da05
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-server/src/main/java/org/apache/ozhera/intelligence/filter/TokenValidationFilter.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.filter;
+
+import com.alibaba.nacos.api.config.annotation.NacosValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class TokenValidationFilter extends OncePerRequestFilter {
+
+ private static final Logger logger = LoggerFactory.getLogger(TokenValidationFilter.class);
+
+ @NacosValue("${analyze.token}")
+ private String validToken;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ String token = request.getHeader("x-token");
+
+ if (token != null && token.equals(validToken)) {
+ filterChain.doFilter(request, response);
+ } else {
+ logger.warn("Token incorrect, illegal request!");
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+ }
+}
diff --git a/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/application.properties b/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/application.properties
index a38e97e..5691905 100644
--- a/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/application.properties
+++ b/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/application.properties
@@ -18,4 +18,15 @@
server.type=${server.type}
server.port=${server.port}
-log.path=${log.path}
\ No newline at end of file
+log.path=${log.path}
+
+dubbo.group=${dubbo.group}
+dubbo.protocol.id=${dubbo.protocol.id}
+dubbo.protocol.name=${dubbo.protocol.name}
+dubbo.protocol.port=${dubbo.protocol.port}
+trace.query.group=${trace.query.group}
+trace.query.version=${trace.query.version}
+log.query.group=${log.query.group}
+log.query.version=${log.query.version}
+
+nacos.address=${nacos.address}
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/config/opensource-outer.properties b/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/config/opensource-outer.properties
index 5ad085d..08fdc82 100644
--- a/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/config/opensource-outer.properties
+++ b/ozhera-intelligence/ozhera-intelligence-server/src/main/resources/config/opensource-outer.properties
@@ -18,4 +18,15 @@
server.type=staging
server.port=8080
-log.path=/tmp/work/log
\ No newline at end of file
+log.path=/tmp/work/log
+
+dubbo.group=staging
+dubbo.protocol.id=dubbo
+dubbo.protocol.name=dubbo
+dubbo.protocol.port=-1
+trace.query.group=staging
+trace.query.version=1.0
+log.query.group=
+log.query.version=1.0
+
+nacos.address=nacos:80
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-service/pom.xml b/ozhera-intelligence/ozhera-intelligence-service/pom.xml
index e1afc94..fc97b77 100644
--- a/ozhera-intelligence/ozhera-intelligence-service/pom.xml
+++ b/ozhera-intelligence/ozhera-intelligence-service/pom.xml
@@ -72,11 +72,16 @@
<artifactId>spring-test</artifactId>
</dependency>
- <!-- trace domain -->
+ <!-- trace api -->
<dependency>
<groupId>org.apache.ozhera</groupId>
- <artifactId>trace-etl-domain</artifactId>
- <version>2.2.5-incubating</version>
+ <artifactId>trace-etl-api</artifactId>
+ </dependency>
+
+ <!-- log api -->
+ <dependency>
+ <groupId>org.apache.ozhera</groupId>
+ <artifactId>log-api</artifactId>
</dependency>
<dependency>
@@ -88,6 +93,75 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>run.mone</groupId>
+ <artifactId>dubbo</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.alibaba.spring</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- nacos -->
+ <dependency>
+ <groupId>run.mone</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>run.mone</groupId>
+ <artifactId>nacos-client</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>run.mone</groupId>
+ <artifactId>dubbo-registry-nacos</artifactId>
+ <exclusions>
+ <exclusion>
+ <artifactId>spring-context-support</artifactId>
+ <groupId>com.alibaba.spring</groupId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.alibaba.nacos</groupId>
+ <artifactId>nacos-spring-context</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.alibaba.spring</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- okhttp -->
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/LogService.java b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/LogService.java
index 4a1075d..a59402f 100644
--- a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/LogService.java
+++ b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/LogService.java
@@ -18,18 +18,54 @@
*/
package org.apache.ozhera.intelligence.service;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.apache.ozhera.log.api.model.dto.LogFilterOptions;
+import org.apache.ozhera.log.api.service.HeraLogApiService;
import org.springframework.stereotype.Service;
import org.apache.ozhera.intelligence.domain.rootanalysis.LogParam;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author dingtao
+ * @date 2025/1/20 11:26
+ */
@Service
public class LogService {
+ @DubboReference(interfaceClass = HeraLogApiService.class, group = "${log.query.group}", version = "${log.query.version}")
+ private HeraLogApiService heraLogApiService;
+
/**
+ * To cover the trace time in the log, we extend the trace time by 5 minutes before and after as a condition to query the log
+ */
+ private static final int LOG_QUERY_TIME_GAP = 1000 * 60 * 5;
+
+
+ /**
* Query logs based on the specified log query conditions.
+ *
* @param param
* @return
*/
- public String queryLogRootAnalysis(LogParam param){
- return null;
+ public List<Map<String, Object>> queryLogRootAnalysis(LogParam param) {
+ List<Map<String, Object>> maps = heraLogApiService.queryLogData(buildLogFilterOptions(param));
+ return maps;
}
-}
+
+
+ private LogFilterOptions buildLogFilterOptions(LogParam param) {
+ LogFilterOptions logFilterOptions = new LogFilterOptions();
+ logFilterOptions.setProjectId(Long.parseLong(param.getApplication()));
+ logFilterOptions.setEnvId(Long.parseLong(param.getEnvId()));
+ logFilterOptions.setTraceId(param.getTraceId());
+ Long startTime = Long.parseLong(param.getStartTime());
+ Long duration = Long.parseLong(param.getDuration());
+ Long endTime = startTime + duration;
+ logFilterOptions.setStartTime(String.valueOf(startTime / 1000 - LOG_QUERY_TIME_GAP));
+ logFilterOptions.setEndTime(String.valueOf(endTime / 1000 + LOG_QUERY_TIME_GAP));
+ logFilterOptions.setLevel(param.getLevel());
+ return logFilterOptions;
+ }
+}
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/MetricsService.java b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/MetricsService.java
index 9b17a89..0207be1 100644
--- a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/MetricsService.java
+++ b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/MetricsService.java
@@ -18,21 +18,231 @@
*/
package org.apache.ozhera.intelligence.service;
+import com.alibaba.nacos.api.config.annotation.NacosValue;
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.ozhera.intelligence.domain.rootanalysis.HeraRootCaseAnalyseRes;
+import org.apache.ozhera.intelligence.domain.rootanalysis.MetricDataRangeSetVector;
+import org.apache.ozhera.intelligence.domain.rootanalysis.MetricRangeResponseVector;
+import org.apache.ozhera.intelligence.util.HttpClient;
import org.springframework.stereotype.Service;
import org.apache.ozhera.intelligence.domain.rootanalysis.MetricsQueryParam;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import static org.apache.ozhera.intelligence.util.CommitPoolUtil.HERA_SOLUTION_METRICS_POOL;
+
/**
- *
+ * @author dingtao
+ * @date 2025/1/20 11:26
*/
@Service
+@Slf4j
public class MetricsService {
+ @NacosValue("${prometheus.api.url}")
+ private String prometheusUrl;
+
+ private static final String URI_QUERY_RANGE = "/api/v1/query_range";
+ private static final String P_QUERY = "query";
+ private static final String P_STEP = "step";
+ private static final String P_START = "start";
+ private static final String P_END = "end";
+
/**
* Query metrics based on the specified metric query conditions.
- * @param param
+ *
+ * @param req
* @return
*/
- public String queryMetricsRootAnalysis(MetricsQueryParam param){
- return null;
+ public HeraRootCaseAnalyseRes queryMetricsRootAnalysis(MetricsQueryParam req) {
+ HeraRootCaseAnalyseRes res = new HeraRootCaseAnalyseRes();
+ try {
+ updateParam(req);
+ log.info("updated param is : " + req);
+ String kubePodInfoPromQl = getKubePodInfoPromQl(req.getIp());
+ log.info("getPodNameByKubePodInfo.kubePodInfoPromQl:{}", kubePodInfoPromQl);
+ MetricRangeResponseVector podPodIpListVector = requestPrometheusRangeV2(kubePodInfoPromQl,
+ req.getStartTime(), req.getEndTime(), req.getEnv());
+ List<MetricDataRangeSetVector> kubePodInfo = podPodIpListVector.getData().getResult();
+ MetricDataRangeSetVector kubePodInfoMetrics = kubePodInfo.get(kubePodInfo.size() - 1);
+ String podName = kubePodInfoMetrics.getMetric().getPod();
+ String cpuPromQl = "max(rate(container_cpu_user_seconds_total{system=\"mione\",application=\"" + req.getApplication() + "\", pod=\"" + podName + "\", image!=\"\", container =~ \"^(?:\\\\d+-0-\\\\d+|faas-sidecar)$\"}[30s]) * 100)";
+ String loadPromQl = "max(container_cpu_load_average_10s{system=\"mione\",application=\"" + req.getApplication() + "\", pod=\"" + podName + "\", image!=\"\", container =~ \"^(?:\\\\d+-0-\\\\d+|faas-sidecar)$\"} / 1000)";
+ String gcMaxTimes = "max(delta(jvm_gc_pause_seconds_count{serverIp=\"" + req.getIp() + "\", application=\"" + req.getApplication() + "\",containerName=\"main\"}[1m]))";
+ String memoryUsedRate = "avg(sum(jvm_memory_used_bytes{application=\"" + req.getApplication() + "\",area=\"heap\",ip=\"" + req.getIp() + "\",containerName=\"main\"}) / sum(jvm_memory_max_bytes{application=\"" + req.getApplication() + "\",area=\"heap\",ip=\"" + req.getIp() + "\",containerName=\"main\"})) * 100";
+ String maxGCDuration = "max(max_over_time(jvm_gc_pause_seconds_max{serverIp=~\"" + req.getIp() + "\", application=\"" + req.getApplication() + "\",containerName=\"main\"}[1m]))";
+ String cpuCount = "system_cpu_count{serverIp=~\"" + req.getIp() + "\", application=\"" + req.getApplication() + "\",containerName=~\".*\"}";
+ log.info("getPodNameByKubePodInfo.cpuPromQl:{}", cpuPromQl);
+ log.info("getPodNameByKubePodInfo.loadPromQl:{}", loadPromQl);
+ log.info("getPodNameByKubePodInfo.gcMaxTimes:{}", gcMaxTimes);
+ log.info("getPodNameByKubePodInfo.memoryUsedRate:{}", memoryUsedRate);
+ log.info("getPodNameByKubePodInfo.maxGCDuration:{}", maxGCDuration);
+ log.info("getPodNameByKubePodInfo.cpuCount:{}", cpuCount);
+ CompletableFuture<MetricRangeResponseVector> cpuFuture = CompletableFuture.supplyAsync(() -> requestPrometheusRangeV2(cpuPromQl,
+ req.getStartTime(), req.getEndTime(), req.getEnv()), HERA_SOLUTION_METRICS_POOL);
+ CompletableFuture<MetricRangeResponseVector> loadFuture = CompletableFuture.supplyAsync(() -> requestPrometheusRangeV2(loadPromQl,
+ req.getStartTime(), req.getEndTime(), req.getEnv()), HERA_SOLUTION_METRICS_POOL);
+ CompletableFuture<MetricRangeResponseVector> gcMaxTimesFuture = CompletableFuture.supplyAsync(() -> requestPrometheusRangeV2(gcMaxTimes,
+ req.getStartTime(), req.getEndTime(), req.getEnv()), HERA_SOLUTION_METRICS_POOL);
+ CompletableFuture<MetricRangeResponseVector> memoryFuture = CompletableFuture.supplyAsync(() -> requestPrometheusRangeV2(memoryUsedRate,
+ req.getStartTime(), req.getEndTime(), req.getEnv()), HERA_SOLUTION_METRICS_POOL);
+ CompletableFuture<MetricRangeResponseVector> maxGCFuture = CompletableFuture.supplyAsync(() -> requestPrometheusRangeV2(maxGCDuration,
+ req.getStartTime(), req.getEndTime(), req.getEnv()), HERA_SOLUTION_METRICS_POOL);
+ CompletableFuture<MetricRangeResponseVector> cpuCountFuture = CompletableFuture.supplyAsync(() -> requestPrometheusRangeV2(cpuCount,
+ req.getStartTime(), req.getEndTime(), req.getEnv()), HERA_SOLUTION_METRICS_POOL);
+ CompletableFuture<Void> allComplete = CompletableFuture.allOf(cpuFuture, loadFuture, gcMaxTimesFuture, memoryFuture, maxGCFuture);
+ allComplete.get(8000, TimeUnit.MILLISECONDS);
+ MetricRangeResponseVector cpuMetrics = cpuFuture.get();
+ if (cpuMetrics != null) {
+ log.info("getPodNameByKubePodInfo cpuMetrics : " + cpuMetrics);
+ List<MetricDataRangeSetVector> cpuResult = cpuMetrics.getData().getResult();
+ if (cpuResult != null && cpuResult.size() > 0) {
+ List<List<String>> values = cpuResult.get(0).getValues();
+ res.setMaxCpuUsage(getMaxValue(values));
+ }
+ }
+ MetricRangeResponseVector loadMetrics = loadFuture.get();
+ if (loadMetrics != null) {
+ log.info("getPodNameByKubePodInfo loadMetrics : " + loadMetrics);
+ List<MetricDataRangeSetVector> loadResult = loadMetrics.getData().getResult();
+ if (loadResult != null && loadResult.size() > 0) {
+ List<List<String>> values = loadResult.get(0).getValues();
+ res.setMaxLoad(getMaxValue(values));
+ }
+ }
+ MetricRangeResponseVector gcMaxTimesMetrics = gcMaxTimesFuture.get();
+ if (gcMaxTimesMetrics != null) {
+ log.info("getPodNameByKubePodInfo gcMaxTimesMetrics : " + gcMaxTimesMetrics);
+ List<MetricDataRangeSetVector> gcMaxTimesResult = gcMaxTimesMetrics.getData().getResult();
+ if (gcMaxTimesResult != null && gcMaxTimesResult.size() > 0) {
+ List<List<String>> values = gcMaxTimesResult.get(0).getValues();
+ res.setMaxSTWCost(getMaxValue(values));
+ }
+ }
+ MetricRangeResponseVector memoryMetrics = memoryFuture.get();
+ if (memoryMetrics != null) {
+ log.info("getPodNameByKubePodInfo memoryMetrics : " + memoryMetrics);
+ List<MetricDataRangeSetVector> memoryResult = memoryMetrics.getData().getResult();
+ if (memoryResult != null && memoryResult.size() > 0) {
+ List<List<String>> values = memoryResult.get(0).getValues();
+ res.setMaxJvmHeapUsage(getMaxValue(values));
+ }
+ }
+ MetricRangeResponseVector maxGCMetrics = maxGCFuture.get();
+ if (maxGCMetrics != null) {
+ log.info("getPodNameByKubePodInfo maxGCMetrics : " + maxGCMetrics);
+ List<MetricDataRangeSetVector> maxGCResult = maxGCMetrics.getData().getResult();
+ if (maxGCResult != null && maxGCResult.size() > 0) {
+ List<List<String>> values = maxGCResult.get(0).getValues();
+ res.setSTWCountOf1m(getMaxValue(values));
+ }
+ }
+ MetricRangeResponseVector cpuCountMetrics = cpuCountFuture.get();
+ if (cpuCountMetrics != null) {
+ log.info("getPodNameByKubePodInfo maxGCMetrics : " + cpuCountMetrics);
+ List<MetricDataRangeSetVector> cpuCountResult = cpuCountMetrics.getData().getResult();
+ if (cpuCountResult != null && cpuCountResult.size() > 0) {
+ List<List<String>> values = cpuCountResult.get(0).getValues();
+ res.setCpuCount(getMaxValue(values));
+ }
+ }
+ } catch (Exception e) {
+ log.error("get root cause analysis error , ", e);
+ }
+ return res;
}
-}
+
+ private void updateParam(MetricsQueryParam req) {
+ Long startTime = Long.parseLong(req.getStartTime());
+ Long duration = Long.parseLong(req.getDuration());
+ Long endTime = startTime + duration;
+ Long gap = Long.parseLong(req.getGap());
+ // startTime is in microseconds, convert to milliseconds
+ Long startTimeMs = startTime / 1000;
+ Long endTimeMs = endTime / 1000;
+ req.setStartTime(addGap(startTimeMs, gap * -1));
+ req.setEndTime(addGap(endTimeMs, gap));
+ req.setApplication(convertApp(req.getApplication()));
+ }
+
+ private String getKubePodInfoPromQl(String hostIp) {
+ return "count(kube_pod_info{system=\"mione\",pod_ip=~\"" + hostIp + "\"}) by (pod_ip,pod)";
+ }
+
+ private MetricRangeResponseVector requestPrometheusRangeV2(String promQl, String startTime, String endTime, String env) {
+ try {
+ Map<String, String> map = new HashMap<>();
+ map.put(P_QUERY, promQl); // Metric parameter
+ // step: 1h = 15, 2h = 2 * 15
+ Long multi = (Long.parseLong(endTime) - Long.parseLong(startTime)) / 3600;
+ if (multi < 1) {
+ multi = 1L;
+
+ }
+ map.put(P_STEP, String.valueOf(multi * 15));
+ map.put(P_START, startTime);
+ map.put(P_END, endTime);
+ String url = completeQueryUrl(prometheusUrl, URI_QUERY_RANGE, promQl, map);
+ System.out.println(url);
+ Map<String, String> headers = new HashMap<>();
+ headers.put("Accept", "*/*");
+ log.info("queryPrometheusRangeResponse : {}", url.toString());
+ String data = HttpClient.get(url.toString(), headers);
+ MetricRangeResponseVector metricResult = new Gson().fromJson(data, MetricRangeResponseVector.class);
+ if (metricResult == null || !"success".equals(metricResult.getStatus())) {
+ return null;
+ } else {
+ return metricResult;
+ }
+ } catch (Exception e) {
+ log.error("requestPrometheusRange err", e);
+ return null;
+ }
+ }
+
+ private String completeQueryUrl(String domain, String path, String query, Map<String, String> map) {
+ URIBuilder builder = new URIBuilder();
+ builder.setScheme("http");
+ builder.setHost(domain);
+ builder.setPath(path);
+ map.forEach(builder::addParameter);
+ URL url = null;
+ try {
+ url = builder.build().toURL();
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ return url.toString();
+ }
+
+ private double getMaxValue(List<List<String>> values) {
+ List<Double> temValues = new ArrayList<>();
+ for (List<String> value : values) {
+ temValues.add(Double.valueOf(value.get(1)));
+ }
+ Collections.sort(temValues);
+ return temValues.get(temValues.size() - 1);
+ }
+
+ private String addGap(long time, long gap) {
+ // After calculating the time interval, convert ms to s
+ return String.valueOf((time + gap) / 1000);
+ }
+
+ private String convertApp(String application) {
+ return application.replaceAll("-", "_");
+ }
+}
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceService.java b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceService.java
index 0705d04..b2ec745 100644
--- a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceService.java
+++ b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/service/TraceService.java
@@ -18,18 +18,298 @@
*/
package org.apache.ozhera.intelligence.service;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.apache.ozhera.intelligence.domain.rootanalysis.TraceTreeNode;
+import org.apache.ozhera.trace.etl.api.service.TraceQueryService;
+import org.apache.ozhera.trace.etl.domain.jaegeres.JaegerAttribute;
+import org.apache.ozhera.trace.etl.domain.tracequery.Span;
+import org.apache.ozhera.trace.etl.domain.tracequery.Trace;
+import org.apache.ozhera.trace.etl.domain.tracequery.TraceIdQueryVo;
import org.springframework.stereotype.Service;
import org.apache.ozhera.intelligence.domain.rootanalysis.TraceQueryParam;
+import org.apache.ozhera.trace.etl.domain.jaegeres.JaegerProcess;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author dingtao
+ * @date 2025/1/20 11:26
+ */
@Service
public class TraceService {
/**
+ * call trace-etl-manager TraceQueryService, The group and version in Dubbo should be consistent with the group and version in TraceQueryServiceImpl in trace-etl-manager.
+ */
+ @DubboReference(interfaceClass = TraceQueryService.class, group = "${trace.query.group}", version = "${trace.query.version}")
+ private TraceQueryService traceQueryService;
+
+ /**
+ * The time range threshold for querying before and after the given timestamp.
+ * The actual query range is calculated by subtracting and adding QUERY_TIME_RANGE to the given timestamp.
+ */
+ private static final long QUERY_TIME_RANGE = 30 * 60 * 1000;
+
+ /**
+ * The maximum depth of the analyzed trace to be retrieved.
+ */
+ private static final int TRACE_DEEP = 5;
+
+
+ private static final String SERVICE_ENV_ID_KEY = "service.env.id";
+ private static final List<String> NOT_RETAIN_SPAN_NAMES = List.of("UDS");
+ private static final List<String> RETAIN_TAGS = Arrays.asList("net.sock.peer.addr", "span.kind", "net.sock.peer.port", "http.target", "net.host.name", "net.peer.name", "net.peer.port", "rpc.service", "rpc.method", "rpc.system", "db.statement", "error", "http.url");
+ private static final List<String> RETAIN_PROCESS_TAGS = Arrays.asList(SERVICE_ENV_ID_KEY, "ip", "service.env", "service.env.id", "service.container.name", "host.name");
+
+ /**
* Query trace based on the specified trace query conditions.
+ *
* @param param
* @return
*/
- public String queryTraceRootAnalysis(TraceQueryParam param){
- return null;
+ public List<Span> queryTraceRootAnalysis(TraceQueryParam param) {
+ Trace traceResult = traceQueryService.getByTraceId(buildTraceIdQueryVo(param));
+ List<Span> result = new ArrayList<>();
+ if (traceResult != null && traceResult.getSpans() != null && !traceResult.getSpans().isEmpty()) {
+ // analyze and cut span
+ List<Span> analyze = analyzeTrace(traceResult.getSpans(), TRACE_DEEP);
+ result = getSpansFilter(analyze);
+ }
+ return result;
}
-}
+
+ /**
+ * Analyze trace information and return a list of key Spans
+ * <p>
+ * This method first builds a TraceTreeNode tree, then analyzes the trace information based on whether errors exist.
+ * If there are errors, it will find and return the most recent error node and its child nodes.
+ * If there are no errors, it will search for time-consuming nodes.
+ *
+ * @param spans The list of Spans to be analyzed
+ * @param N The number of child nodes to return in case of errors
+ * @return A list containing key Spans, representing error nodes or time-consuming nodes
+ */
+ public List<Span> analyzeTrace(List<Span> spans, int N) {
+ // Build TraceTreeNode tree
+
+ // Build TraceTreeNode tree
+ Map<String, TraceTreeNode> nodeMap = new HashMap<>();
+ boolean hasError = false;
+ for (Span span : spans) {
+ if (hasException(span)) {
+ hasError = true;
+ }
+ TraceTreeNode node = new TraceTreeNode(span);
+
+ nodeMap.put(span.getSpanID(), node);
+ }
+ List<TraceTreeNode> roots = new ArrayList<>();
+ for (TraceTreeNode node : nodeMap.values()) {
+ if (node.getSpan().getReferences().isEmpty() ||
+ !nodeMap.containsKey(node.getSpan().getReferences().get(0).getSpanID())) {
+ roots.add(node);
+ } else {
+ TraceTreeNode parentNode = nodeMap.get(node.getSpan().getReferences().get(0).getSpanID());
+ parentNode.addChild(node);
+ }
+ }
+ // Sort sibling nodes by startTime
+ for (TraceTreeNode root : roots) {
+ sortChildren(root);
+ }
+ List<Span> result = new ArrayList<>();
+ if (hasError) {
+ // Find error nodes
+ List<TraceTreeNode> errorNodes = new ArrayList<>();
+ for (TraceTreeNode root : roots) {
+ findErrorTargetNode(root, errorNodes);
+ if (errorNodes != null && errorNodes.size() > 0) {
+ int currentChildCount = 0;
+ // Sort by Span.startTime, analyze the Span with the latest start time, which is likely the most relevant
+ errorNodes.sort(Comparator.comparing(s -> s.getSpan().getStartTime()));
+ TraceTreeNode lastErrorNode = errorNodes.get(errorNodes.size() - 1);
+ result.add(lastErrorNode.getSpan());
+ addNChild(result, lastErrorNode, N, currentChildCount);
+ }
+ }
+ } else {
+ // Finding time-consuming nodes is more complex. We start with the root node's duration as a baseline,
+ // and search level by level until there are no more child nodes or no child nodes with significantly high duration.
+ List<TraceTreeNode> slowNodes = new ArrayList<>();
+ for (TraceTreeNode root : roots) {
+ long rootDuration = root.getSpan().getDuration();
+ slowNodes.add(root);
+ findSlowNode(root, slowNodes, rootDuration);
+ for (TraceTreeNode node : slowNodes) {
+ result.add(node.getSpan());
+
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Filter and process the given list of Spans
+ *
+ * @param spans The list of Spans to be processed
+ * @return The filtered and processed list of Spans
+ * <p>
+ * This method mainly performs the following operations:
+ * 1. Filters out Spans that don't need to be retained based on NOT_RETAIN_SPAN_NAMES
+ * 2. For retained Spans, only keeps tags specified in RETAIN_TAGS
+ * 3. For each Span's Process, only keeps tags specified in RETAIN_PROCESS_TAGS
+ * If the input spans is null or empty, it returns the original spans directly
+ */
+ private List<Span> getSpansFilter(List<Span> spans) {
+ if (spans != null && spans.size() > 0) {
+ List<Span> newSpans = new ArrayList<>();
+ for (Span span : spans) {
+ String operationName = span.getOperationName();
+
+ boolean isRetain = true;
+ for (String filter : NOT_RETAIN_SPAN_NAMES) {
+ if (operationName.contains(filter)) {
+ isRetain = false;
+ }
+ }
+ if (isRetain) {
+ newSpans.add(span);
+ }
+ }
+ for (Span span : newSpans) {
+ List<JaegerAttribute> newTags = new ArrayList<>();
+ for (JaegerAttribute attribute : span.getTags()) {
+ if (RETAIN_TAGS.contains(attribute.getKey())) {
+ newTags.add(attribute);
+ }
+ }
+ span.setTags(newTags);
+ List<JaegerAttribute> newProcessTags = new ArrayList<>();
+ JaegerProcess process = span.getProcess();
+ List<JaegerAttribute> processTags = process.getTags();
+ for (JaegerAttribute processTag : processTags) {
+ if (RETAIN_PROCESS_TAGS.contains(processTag.getKey())) {
+ newProcessTags.add(processTag);
+ }
+ }
+ process.setTags(newProcessTags);
+ }
+ return newSpans;
+ }
+ return spans;
+ }
+
+ private boolean hasException(Span span) {
+ // Determine if the span contains any errors based on the actual situation
+ for (JaegerAttribute tag : span.getTags()) {
+ if ("error".equals(tag.getKey())) {
+ return true;
+
+
+ }
+ }
+ return false;
+ }
+
+ private void sortChildren(TraceTreeNode node) {
+ if (node.getChildren() != null) {
+ node.getChildren().sort(Comparator.comparingLong(n -> n.getSpan().getStartTime()));
+ for (TraceTreeNode child : node.getChildren()) {
+ sortChildren(child);
+ }
+ }
+ }
+
+ private void findSlowNode(TraceTreeNode root, List<TraceTreeNode> slowNodes, long rootDuration) {
+ // Find nodes with duration greater than 80% of the parent node's duration.
+ // If none found, look for 50%, then 30%, 20%, and finally 10%.
+ if (!root.getChildren().isEmpty()) {
+ for (TraceTreeNode childNode : root.getChildren()) {
+ if (findByPercent(childNode, slowNodes, rootDuration, 0.8)) {
+
+ findSlowNode(childNode, slowNodes, childNode.getSpan().getDuration());
+ } else if (findByPercent(childNode, slowNodes, rootDuration, 0.5)) {
+ findSlowNode(childNode, slowNodes, childNode.getSpan().getDuration());
+ } else if (findByPercent(childNode, slowNodes, rootDuration, 0.3)) {
+ findSlowNode(childNode, slowNodes, childNode.getSpan().getDuration());
+ } else if (findByPercent(childNode, slowNodes, rootDuration, 0.2)) {
+ findSlowNode(childNode, slowNodes, childNode.getSpan().getDuration());
+ } else if (findByPercent(childNode, slowNodes, rootDuration, 0.1)) {
+ findSlowNode(childNode, slowNodes, childNode.getSpan().getDuration());
+ }
+ }
+ }
+ }
+
+ private boolean findByPercent(TraceTreeNode parent, List<TraceTreeNode> slowNodes, long rootDuration, double percent) {
+ boolean result = false;
+ for (TraceTreeNode childNode : parent.getChildren()) {
+ if (childNode.getSpan().getDuration() > rootDuration * percent) {
+ result = true;
+ slowNodes.add(childNode);
+ }
+ }
+ return result;
+ }
+
+ private void findErrorTargetNode(TraceTreeNode node, List<TraceTreeNode> errorNodes) {
+ if (hasException(node.getSpan())) {
+ errorNodes.add(node);
+ }
+
+ if (node.getChildren() != null) {
+ for (TraceTreeNode child : node.getChildren()) {
+ findErrorTargetNode(child, errorNodes);
+ }
+ }
+ }
+
+ /**
+ * Add the specified number of child nodes of the targetNode to the result.
+ * First, add the first-level child nodes; if the quantity is not sufficient,
+ * then add the second-level child nodes. If the number of child nodes exceeds afterNumber,
+ * or if there are no more child nodes, the method returns.
+ *
+ * @param result List to store the resulting Span objects
+ * @param targetNode
+ * @param afterNumber
+ */
+ private void addNChild(List<Span> result, TraceTreeNode targetNode, int afterNumber, int currentChildCount) {
+ List<TraceTreeNode> children = targetNode.getChildren();
+ if (!children.isEmpty()) {
+ List<TraceTreeNode> childs = new ArrayList<>();
+ for (TraceTreeNode treeNode : children) {
+ result.add(treeNode.getSpan());
+ currentChildCount++;
+ if (currentChildCount >= afterNumber) {
+ return;
+ }
+ if (!treeNode.getChildren().isEmpty()) {
+ childs.add(treeNode);
+ }
+ }
+ // If the number of first-level child nodes is less than afterNumber, continue adding second-level child nodes
+ for (TraceTreeNode secondChild : childs) {
+ addNChild(result, secondChild, afterNumber, currentChildCount);
+ }
+ }
+ }
+
+ private TraceIdQueryVo buildTraceIdQueryVo(TraceQueryParam param) {
+
+ TraceIdQueryVo vo = new TraceIdQueryVo();
+ vo.setTraceId(param.getTraceId());
+ long startTime = param.getTimeStamp() - QUERY_TIME_RANGE;
+ long endTime = param.getTimeStamp() + QUERY_TIME_RANGE;
+ vo.setStartTime(startTime);
+ vo.setEndTime(endTime);
+ return vo;
+ }
+}
\ No newline at end of file
diff --git a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/util/CommitPoolUtil.java b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/util/CommitPoolUtil.java
new file mode 100644
index 0000000..ce873e9
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/util/CommitPoolUtil.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.util;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CommitPoolUtil {
+
+ public static final ThreadPoolExecutor HERA_SOLUTION_METRICS_POOL;
+ private static final BlockingQueue<Runnable> heraSolutionMetricsQueue = new ArrayBlockingQueue<>(30);
+ private static final AtomicInteger heraSolutionThreadNumber = new AtomicInteger(1);
+
+ static {
+ HERA_SOLUTION_METRICS_POOL = new ThreadPoolExecutor(15, 20,
+ 0L, TimeUnit.MILLISECONDS,
+ heraSolutionMetricsQueue, new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread thread = new Thread(r);
+ thread.setDaemon(false);
+ thread.setName("hera-solution-metrics" + heraSolutionThreadNumber.getAndIncrement());
+ return thread;
+ }
+ });
+ }
+
+}
diff --git a/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/util/HttpClient.java b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/util/HttpClient.java
new file mode 100644
index 0000000..f250ff1
--- /dev/null
+++ b/ozhera-intelligence/ozhera-intelligence-service/src/main/java/org/apache/ozhera/intelligence/util/HttpClient.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.intelligence.util;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author dingtao
+ * @date 2025/1/20 11:26
+ */
+@Slf4j
+public class HttpClient {
+
+ private static final OkHttpClient client;
+
+ static {
+ client = new OkHttpClient.Builder()
+ .connectTimeout(10, TimeUnit.SECONDS)
+ .writeTimeout(10, TimeUnit.SECONDS)
+ .readTimeout(60, TimeUnit.SECONDS)
+ .build();
+ }
+
+ public static JsonObject postRequest(String url, JsonObject jsonObject) {
+ MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+ RequestBody body = RequestBody.create(JSON, jsonObject.toString());
+ Request request = new Request.Builder()
+ .url(url)
+ .post(body)
+ .build();
+ try (Response response = client.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ throw new IOException("Unexpected code " + response);
+ }
+ return new JsonParser().parse(response.body().string()).getAsJsonObject();
+ } catch (Exception e) {
+ log.error("HttpClient post error, ", e);
+ return null;
+ }
+ }
+
+
+ public static String get(String url, Map<String, String> headers) {
+ Request.Builder requestBuilder = new Request.Builder().url(url);
+
+ if (headers != null) {
+ for (Map.Entry<String, String> entry : headers.entrySet()) {
+ requestBuilder.addHeader(entry.getKey(), entry.getValue());
+ }
+ }
+
+ Request request = requestBuilder.build();
+
+ try (Response response = client.newCall(request).execute()) {
+ if (!response.isSuccessful()) {
+ throw new IOException("Unexpected code " + response);
+ }
+ return response.body().string();
+ } catch (Exception e) {
+ log.error("HttpClient get error, ", e);
+ return null;
+ }
+
+ }
+}
diff --git a/ozhera-intelligence/pom.xml b/ozhera-intelligence/pom.xml
index 3f3c5ac..1a6d9b6 100644
--- a/ozhera-intelligence/pom.xml
+++ b/ozhera-intelligence/pom.xml
@@ -68,6 +68,21 @@
<artifactId>trace-etl-domain</artifactId>
<version>2.2.5-incubating</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.ozhera</groupId>
+ <artifactId>trace-etl-api</artifactId>
+ <version>2.2.5-incubating</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.ozhera</groupId>
+ <artifactId>log-api</artifactId>
+ <version>2.2.5-incubating</version>
+ </dependency>
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ <version>4.12.0</version>
+ </dependency>
</dependencies>
</dependencyManagement>
diff --git a/ozhera-operator/ozhera-operator-server/src/main/resources/ozhera_init/intelligence/ozhera_intelligence.yml b/ozhera-operator/ozhera-operator-server/src/main/resources/ozhera_init/intelligence/ozhera_intelligence.yml
new file mode 100644
index 0000000..90cb38a
--- /dev/null
+++ b/ozhera-operator/ozhera-operator-server/src/main/resources/ozhera_init/intelligence/ozhera_intelligence.yml
@@ -0,0 +1,41 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: ozhera-intelligence
+ namespace: ozhera-namespace
+ labels:
+ app: ozhera-intelligence
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: ozhera-intelligence
+ template:
+ metadata:
+ labels:
+ app: ozhera-intelligence
+ spec:
+ containers:
+ - name: ozhera-intelligence-container
+ image: herahub/opensource-pub:ozhera-intelligence-2.2.5-incubating-beta-v1
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 4446
+ resources:
+ limits:
+ cpu: '500m'
+ memory: 1Gi
diff --git "a/ozhera-operator/ozhera-operator-server/src/main/resources/ozhera_init/nacos/config/hera_intelligence_config_\043_DEFAULT_GROUP.properties" "b/ozhera-operator/ozhera-operator-server/src/main/resources/ozhera_init/nacos/config/hera_intelligence_config_\043_DEFAULT_GROUP.properties"
new file mode 100644
index 0000000..5bf79da
--- /dev/null
+++ "b/ozhera-operator/ozhera-operator-server/src/main/resources/ozhera_init/nacos/config/hera_intelligence_config_\043_DEFAULT_GROUP.properties"
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+analyze.token=
+dubbo.registry.address=nacos://${hera.nacos.address}
+prometheus.api.url=${hera.prometheus.url}
diff --git a/readme/images/ozhera-intelligence.png b/readme/images/ozhera-intelligence.png
new file mode 100644
index 0000000..79d9020
--- /dev/null
+++ b/readme/images/ozhera-intelligence.png
Binary files differ
diff --git a/trace-etl/trace-etl-api/src/main/java/org/apache/ozhera/trace/etl/api/service/TraceQueryService.java b/trace-etl/trace-etl-api/src/main/java/org/apache/ozhera/trace/etl/api/service/TraceQueryService.java
new file mode 100644
index 0000000..d657d5d
--- /dev/null
+++ b/trace-etl/trace-etl-api/src/main/java/org/apache/ozhera/trace/etl/api/service/TraceQueryService.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.trace.etl.api.service;
+
+import org.apache.ozhera.trace.etl.domain.tracequery.Trace;
+import org.apache.ozhera.trace.etl.domain.tracequery.TraceIdQueryVo;
+import org.apache.ozhera.trace.etl.domain.tracequery.TraceListQueryVo;
+
+import java.util.List;
+
+/**
+ * @author dingtao
+ * @date 2025/1/20 11:26
+ */
+public interface TraceQueryService {
+
+ List<Trace> getList(TraceListQueryVo vo);
+
+ Trace getByTraceId(TraceIdQueryVo vo);
+}
diff --git a/trace-etl/trace-etl-manager/src/main/java/org/apache/ozhera/trace/etl/manager/dubbo/TraceQueryServiceImpl.java b/trace-etl/trace-etl-manager/src/main/java/org/apache/ozhera/trace/etl/manager/dubbo/TraceQueryServiceImpl.java
new file mode 100644
index 0000000..bc8bc30
--- /dev/null
+++ b/trace-etl/trace-etl-manager/src/main/java/org/apache/ozhera/trace/etl/manager/dubbo/TraceQueryServiceImpl.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ozhera.trace.etl.manager.dubbo;
+
+import org.apache.dubbo.config.annotation.Service;
+import org.apache.ozhera.trace.etl.api.service.DataSourceService;
+import org.apache.ozhera.trace.etl.api.service.TraceQueryService;
+import org.apache.ozhera.trace.etl.domain.tracequery.Trace;
+import org.apache.ozhera.trace.etl.domain.tracequery.TraceIdQueryVo;
+import org.apache.ozhera.trace.etl.domain.tracequery.TraceListQueryVo;
+import org.apache.ozhera.trace.etl.domain.tracequery.TraceQueryResult;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.List;
+
+/**
+ * @author dingtao
+ * @date 2025/1/20 11:26
+ */
+@Service(interfaceClass = TraceQueryService.class, group = "${dubbo.group}", version = "1.0")
+public class TraceQueryServiceImpl implements TraceQueryService {
+
+ @Autowired
+ private DataSourceService dataSourceService;
+
+ @Override
+ public List<Trace> getList(TraceListQueryVo vo) {
+ TraceQueryResult<List<Trace>> result = dataSourceService.getList(vo);
+ if (result != null) {
+ return result.getData();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Trace getByTraceId(TraceIdQueryVo vo) {
+ TraceQueryResult<List<Trace>> result = dataSourceService.getByTraceId(vo);
+ if (result != null) {
+ return result.getData().getFirst();
+ } else {
+ return null;
+ }
+ }
+}