diff --git a/skywalking b/skywalking
index 8e529ee9560..4621e5191fc 160000
--- a/skywalking
+++ b/skywalking
@@ -1 +1 @@
-Subproject commit 8e529ee95604fb01a8bd31c272763393f3c70525
+Subproject commit 4621e5191fcc2fe8271a3049b788fd33c694f703
diff --git a/zipkin-server/health-query-plugin/src/main/java/zipkin/server/query/health/HealthQueryProvider.java b/zipkin-server/health-query-plugin/src/main/java/zipkin/server/query/health/HealthQueryProvider.java
index 005c67e2629..d7d164d2b59 100644
--- a/zipkin-server/health-query-plugin/src/main/java/zipkin/server/query/health/HealthQueryProvider.java
+++ b/zipkin-server/health-query-plugin/src/main/java/zipkin/server/query/health/HealthQueryProvider.java
@@ -24,6 +24,7 @@
import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
import org.apache.skywalking.oap.server.library.server.http.HTTPServer;
import org.apache.skywalking.oap.server.library.server.http.HTTPServerConfig;
+import zipkin.server.core.services.HTTPConfigurableServer;
import java.util.Collections;
@@ -67,7 +68,7 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
.acceptQueueSize(moduleConfig.getRestAcceptQueueSize())
.maxRequestHeaderSize(moduleConfig.getRestMaxRequestHeaderSize())
.build();
- httpServer = new HTTPServer(httpServerConfig);
+ httpServer = new HTTPConfigurableServer(httpServerConfig);
httpServer.initialize();
}
}
diff --git a/zipkin-server/http-query-plugin/pom.xml b/zipkin-server/http-query-plugin/pom.xml
index f4371f49179..43db33e1803 100644
--- a/zipkin-server/http-query-plugin/pom.xml
+++ b/zipkin-server/http-query-plugin/pom.xml
@@ -18,6 +18,16 @@
zipkin-query-plugin
${skywalking.version}
+
+ io.zipkin
+ zipkin-server-core
+ ${project.version}
+
+
+ io.zipkin
+ zipkin-dependency
+ ${project.version}
+
\ No newline at end of file
diff --git a/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryConfig.java b/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryConfig.java
index fd336dec3a0..d55fac3cdad 100644
--- a/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryConfig.java
+++ b/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryConfig.java
@@ -37,6 +37,14 @@ public class HTTPQueryConfig extends ModuleConfig {
private String uiEnvironment = "";
private long uiDefaultLookback = 900000L;
private boolean uiSearchEnabled = true;
+ private String allowedOrigins = "*";
+
+ private boolean uiEnable = true;
+ private String uiBasePath = "/zipkin";
+
+ private boolean dependencyEnabled = true;
+ private double dependencyLowErrorRate = 0.5; // 50% of calls in error turns line yellow
+ private double dependencyHighErrorRate = 0.75;// 75% of calls in error turns line red
public ZipkinQueryConfig toSkyWalkingConfig() {
final ZipkinQueryConfig result = new ZipkinQueryConfig();
@@ -160,4 +168,52 @@ public int getRestMaxRequestHeaderSize() {
public void setRestMaxRequestHeaderSize(int restMaxRequestHeaderSize) {
this.restMaxRequestHeaderSize = restMaxRequestHeaderSize;
}
+
+ public String getUiBasePath() {
+ return uiBasePath;
+ }
+
+ public void setUiBasePath(String uiBasePath) {
+ this.uiBasePath = uiBasePath;
+ }
+
+ public boolean getUiEnable() {
+ return uiEnable;
+ }
+
+ public void setUiEnable(boolean uiEnable) {
+ this.uiEnable = uiEnable;
+ }
+
+ public String getAllowedOrigins() {
+ return allowedOrigins;
+ }
+
+ public void setAllowedOrigins(String allowedOrigins) {
+ this.allowedOrigins = allowedOrigins;
+ }
+
+ public boolean getDependencyEnabled() {
+ return dependencyEnabled;
+ }
+
+ public void setDependencyEnabled(boolean dependencyEnabled) {
+ this.dependencyEnabled = dependencyEnabled;
+ }
+
+ public double getDependencyLowErrorRate() {
+ return dependencyLowErrorRate;
+ }
+
+ public void setDependencyLowErrorRate(double dependencyLowErrorRate) {
+ this.dependencyLowErrorRate = dependencyLowErrorRate;
+ }
+
+ public double getDependencyHighErrorRate() {
+ return dependencyHighErrorRate;
+ }
+
+ public void setDependencyHighErrorRate(double dependencyHighErrorRate) {
+ this.dependencyHighErrorRate = dependencyHighErrorRate;
+ }
}
diff --git a/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryHandler.java b/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryHandler.java
index e3ae7ae5950..fab3dc224fc 100644
--- a/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryHandler.java
+++ b/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryHandler.java
@@ -14,27 +14,38 @@
package zipkin.server.query.http;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.linecorp.armeria.common.AggregatedHttpResponse;
import com.linecorp.armeria.common.HttpData;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.common.ResponseHeaders;
+import com.linecorp.armeria.server.annotation.Blocking;
+import com.linecorp.armeria.server.annotation.Get;
+import com.linecorp.armeria.server.annotation.Param;
import org.apache.skywalking.oap.query.zipkin.handler.ZipkinQueryHandler;
import org.apache.skywalking.oap.server.core.storage.StorageModule;
import org.apache.skywalking.oap.server.core.storage.query.IZipkinQueryDAO;
import org.apache.skywalking.oap.server.library.module.ModuleManager;
import org.apache.skywalking.oap.server.library.util.CollectionUtils;
import org.apache.skywalking.oap.server.library.util.StringUtil;
+import zipkin.server.dependency.IZipkinDependencyQueryDAO;
+import zipkin.server.dependency.ZipkinDependencyModule;
+import zipkin2.DependencyLink;
import zipkin2.Span;
+import zipkin2.codec.DependencyLinkBytesEncoder;
import zipkin2.codec.SpanBytesEncoder;
import java.io.IOException;
+import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -47,12 +58,41 @@ public class HTTPQueryHandler extends ZipkinQueryHandler {
private final ModuleManager moduleManager;
private IZipkinQueryDAO zipkinQueryDAO;
+ private IZipkinDependencyQueryDAO dependencyQueryDAO;
public HTTPQueryHandler(HTTPQueryConfig config, ModuleManager moduleManager) {
super(config.toSkyWalkingConfig(), moduleManager);
this.config = config;
this.moduleManager = moduleManager;
}
+ @Override
+ public AggregatedHttpResponse getUIConfig() throws IOException {
+ StringWriter writer = new StringWriter();
+ JsonGenerator generator = new JsonFactory().createGenerator(writer);
+ generator.writeStartObject();
+ generator.writeStringField("environment", config.getUiEnvironment());
+ generator.writeNumberField("queryLimit", config.getUiQueryLimit());
+ generator.writeNumberField("defaultLookback", config.getUiDefaultLookback());
+ generator.writeBooleanField("searchEnabled", config.getUiSearchEnabled());
+ generator.writeObjectFieldStart("dependency");
+ generator.writeBooleanField("enabled", config.getDependencyEnabled());
+ generator.writeNumberField("lowErrorRate", config.getDependencyLowErrorRate());
+ generator.writeNumberField("highErrorRate", config.getDependencyHighErrorRate());
+ generator.writeEndObject();
+ generator.writeEndObject();
+ generator.close();
+ return AggregatedHttpResponse.of(HttpStatus.OK, MediaType.JSON, HttpData.ofUtf8(writer.toString()));
+ }
+
+ @Get("/api/v2/dependencies")
+ @Blocking
+ public AggregatedHttpResponse getDependencies(
+ @Param("endTs") long endTs,
+ @Param("lookback") Optional lookback) throws IOException {
+ final List dependencies = getDependencyQueryDAO().getDependencies(endTs, lookback.orElse(config.getLookback()));
+ return response(DependencyLinkBytesEncoder.JSON_V1.encodeList(dependencies));
+ }
+
@Override
public AggregatedHttpResponse getTraceById(String traceId) throws IOException {
if (StringUtil.isEmpty(traceId)) {
@@ -129,4 +169,11 @@ private IZipkinQueryDAO getZipkinQueryDAO() {
}
return zipkinQueryDAO;
}
+
+ public IZipkinDependencyQueryDAO getDependencyQueryDAO() {
+ if (dependencyQueryDAO == null) {
+ dependencyQueryDAO = moduleManager.find(ZipkinDependencyModule.NAME).provider().getService(IZipkinDependencyQueryDAO.class);
+ }
+ return dependencyQueryDAO;
+ }
}
diff --git a/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryProvider.java b/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryProvider.java
index 4e62c90104c..84bf5d103f4 100644
--- a/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryProvider.java
+++ b/zipkin-server/http-query-plugin/src/main/java/zipkin/server/query/http/HTTPQueryProvider.java
@@ -14,7 +14,18 @@
package zipkin.server.query.http;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpHeaderNames;
import com.linecorp.armeria.common.HttpMethod;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.MediaType;
+import com.linecorp.armeria.common.ServerCacheControl;
+import com.linecorp.armeria.server.HttpService;
+import com.linecorp.armeria.server.RedirectService;
+import com.linecorp.armeria.server.cors.CorsService;
+import com.linecorp.armeria.server.cors.CorsServiceBuilder;
+import com.linecorp.armeria.server.file.FileService;
+import com.linecorp.armeria.server.file.HttpFile;
import org.apache.skywalking.oap.query.zipkin.ZipkinQueryModule;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.server.HTTPHandlerRegister;
@@ -25,12 +36,25 @@
import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
import org.apache.skywalking.oap.server.library.server.http.HTTPServer;
import org.apache.skywalking.oap.server.library.server.http.HTTPServerConfig;
+import zipkin.server.core.services.HTTPConfigurableServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
public class HTTPQueryProvider extends ModuleProvider {
+ static final String DEFAULT_UI_BASEPATH = "/zipkin";
+
private HTTPQueryConfig moduleConfig;
private HTTPServer httpServer;
+
@Override
public String name() {
return "zipkin";
@@ -68,25 +92,88 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
.acceptQueueSize(moduleConfig.getRestAcceptQueueSize())
.maxRequestHeaderSize(moduleConfig.getRestMaxRequestHeaderSize())
.build();
- httpServer = new HTTPServer(httpServerConfig);
+ httpServer = new HTTPConfigurableServer(httpServerConfig);
httpServer.initialize();
}
}
@Override
public void start() throws ServiceNotProvidedException, ModuleStartException {
+ HTTPConfigurableServer.ServerConfiguration corsConfiguration = (server) -> {
+ CorsServiceBuilder corsBuilder = CorsService.builder(moduleConfig.getAllowedOrigins().split(","))
+ // NOTE: The property says query, and the UI does not use POST, but we allow POST?
+ //
+ // The reason is that our former CORS implementation accidentally allowed POST. People doing
+ // browser-based tracing relied on this, so we can't remove it by default. In the future, we
+ // could split the collector's CORS policy into a different property, still allowing POST
+ // with content-type by default.
+ .allowRequestMethods(HttpMethod.GET, HttpMethod.POST)
+ .allowRequestHeaders(HttpHeaderNames.CONTENT_TYPE,
+ // Use literals to avoid a runtime dependency on armeria-grpc types
+ HttpHeaderNames.of("X-GRPC-WEB"))
+ .exposeHeaders("grpc-status", "grpc-message", "armeria.grpc.ThrowableProto-bin");
+ server.decorator(corsBuilder::build);
+ };
+
+ final HTTPQueryHandler httpService = new HTTPQueryHandler(moduleConfig, getManager());
if (httpServer != null) {
- httpServer.addHandler(new HTTPQueryHandler(moduleConfig, getManager()),
- Collections.singletonList(HttpMethod.GET));
- } else {
- getManager().find(CoreModule.NAME).provider()
- .getService(HTTPHandlerRegister.class).addHandler(
- new HTTPQueryHandler(moduleConfig, getManager()),
- Collections.singletonList(HttpMethod.GET)
- );
+ httpServer.addHandler(httpService, Collections.singletonList(HttpMethod.GET));
+ httpServer.addHandler(corsConfiguration, Arrays.asList(HttpMethod.GET, HttpMethod.POST));
+
+ if (moduleConfig.getUiEnable()) {
+ loadUIServices((service, methods) -> httpServer.addHandler(service, methods), httpService);
+ }
+ return;
+ }
+
+ final HTTPHandlerRegister httpRegister = getManager().find(CoreModule.NAME).provider()
+ .getService(HTTPHandlerRegister.class);
+ httpRegister.addHandler(httpService, Collections.singletonList(HttpMethod.GET));
+ httpRegister.addHandler(corsConfiguration, Arrays.asList(HttpMethod.GET, HttpMethod.POST));
+
+ if (moduleConfig.getUiEnable()) {
+ loadUIServices(httpRegister, httpService);
}
}
+ private void loadUIServices(HTTPHandlerRegister httpRegister, HTTPQueryHandler httpService) {
+ HttpService lensIndex;
+ HttpService uiFileService;
+
+ final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ URL indexPage = contextClassLoader.getResource("zipkin-lens/index.html");
+ if (indexPage == null) {
+ throw new IllegalStateException("Cannot find ui pages");
+ }
+ final String uiBasePath = moduleConfig.getUiBasePath();
+ try {
+ lensIndex = maybeIndexService(uiBasePath, indexPage);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot load ui", e);
+ }
+
+ ServerCacheControl maxAgeYear =
+ ServerCacheControl.builder().maxAgeSeconds(TimeUnit.DAYS.toSeconds(365)).build();
+ uiFileService = FileService.builder(contextClassLoader, "zipkin-lens")
+ .cacheControl(maxAgeYear)
+ .build();
+
+ httpRegister.addHandler((HTTPConfigurableServer.ServerConfiguration) builder -> {
+ builder.annotatedService().pathPrefix(uiBasePath + "/").build(httpService);
+ builder.serviceUnder(uiBasePath + "/", uiFileService);
+
+ builder.service(uiBasePath+ "/", lensIndex)
+ .service(uiBasePath + "/index.html", lensIndex)
+ .service(uiBasePath + "/traces/{id}", lensIndex)
+ .service(uiBasePath + "/dependency", lensIndex)
+ .service(uiBasePath + "/traceViewer", lensIndex);
+
+ builder.service("/favicon.ico", new RedirectService(HttpStatus.FOUND, uiBasePath + "/favicon.ico"))
+ .service("/", new RedirectService(HttpStatus.FOUND, uiBasePath + "/"))
+ .service(uiBasePath, new RedirectService(HttpStatus.FOUND, uiBasePath + "/"));
+ }, Collections.singletonList(HttpMethod.GET));
+ }
+
@Override
public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
if (httpServer != null) {
@@ -100,4 +187,42 @@ public String[] requiredModules() {
CoreModule.NAME,
};
}
+
+ static HttpService maybeIndexService(String basePath, URL resource) throws IOException {
+ String maybeContent = maybeResource(basePath, resource);
+ if (maybeContent == null) return null;
+
+ ServerCacheControl maxAgeMinute = ServerCacheControl.builder().maxAgeSeconds(60).build();
+
+ return HttpFile.builder(HttpData.ofUtf8(maybeContent))
+ .contentType(MediaType.HTML_UTF_8).cacheControl(maxAgeMinute)
+ .build().asService();
+ }
+
+ static String maybeResource(String basePath, URL resource) throws IOException {
+ String content = copyToString(resource, UTF_8);
+ if (DEFAULT_UI_BASEPATH.equals(basePath)) return content;
+
+ String baseTagValue = "/".equals(basePath) ? "/" : basePath + "/";
+ // html-webpack-plugin seems to strip out quotes from the base tag when compiling so be
+ // careful with this matcher.
+ return content.replaceAll(
+ "]+>", ""
+ );
+ }
+
+ static String copyToString(URL in, Charset charset) throws IOException {
+ StringBuilder out = new StringBuilder(4096);
+ try (InputStream input = in.openStream(); InputStreamReader reader = new InputStreamReader(input, charset)) {
+ char[] buffer = new char[4096];
+
+ int charsRead;
+ while((charsRead = reader.read(buffer)) != -1) {
+ out.append(buffer, 0, charsRead);
+ }
+ }
+
+ return out.toString();
+ }
+
}
diff --git a/zipkin-server/pom.xml b/zipkin-server/pom.xml
index 83c620a8db7..f2bd95d2cf2 100644
--- a/zipkin-server/pom.xml
+++ b/zipkin-server/pom.xml
@@ -70,6 +70,9 @@
storage-cassandra
http-query-plugin
health-query-plugin
+ telemetry-zipkin
+ zipkin-dependency
+ zipkin-storage-ext
@@ -217,23 +220,4 @@
-
-
- include-lens
-
-
- !skipLens
-
-
-
-
-
- ${project.groupId}
- zipkin-lens
- ${project.version}
-
-
-
-
-
diff --git a/zipkin-server/receiver-zipkin-http/src/main/java/zipkin/server/receiver/zipkin/http/ZipkinHTTPReceiverProvider.java b/zipkin-server/receiver-zipkin-http/src/main/java/zipkin/server/receiver/zipkin/http/ZipkinHTTPReceiverProvider.java
index 78e10fb6379..c9a3e4cabaa 100644
--- a/zipkin-server/receiver-zipkin-http/src/main/java/zipkin/server/receiver/zipkin/http/ZipkinHTTPReceiverProvider.java
+++ b/zipkin-server/receiver-zipkin-http/src/main/java/zipkin/server/receiver/zipkin/http/ZipkinHTTPReceiverProvider.java
@@ -27,6 +27,7 @@
import org.apache.skywalking.oap.server.library.server.http.HTTPServerConfig;
import org.apache.skywalking.oap.server.receiver.zipkin.handler.ZipkinSpanHTTPHandler;
import org.apache.skywalking.oap.server.receiver.zipkin.trace.SpanForward;
+import zipkin.server.core.services.HTTPConfigurableServer;
import zipkin.server.core.services.ZipkinConfigService;
import java.util.Arrays;
@@ -73,7 +74,7 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
.acceptQueueSize(moduleConfig.getRestAcceptQueueSize())
.maxRequestHeaderSize(moduleConfig.getRestMaxRequestHeaderSize())
.build();
- httpServer = new HTTPServer(httpServerConfig);
+ httpServer = new HTTPConfigurableServer(httpServerConfig);
httpServer.initialize();
}
}
diff --git a/zipkin-server/receiver-zipkin-http/src/test/java/zipkin/server/receiver/zipkin/http/ITHTTPReceiver.java b/zipkin-server/receiver-zipkin-http/src/test/java/zipkin/server/receiver/zipkin/http/ITHTTPReceiver.java
index a6ef6ec0e95..0d72caaee4e 100644
--- a/zipkin-server/receiver-zipkin-http/src/test/java/zipkin/server/receiver/zipkin/http/ITHTTPReceiver.java
+++ b/zipkin-server/receiver-zipkin-http/src/test/java/zipkin/server/receiver/zipkin/http/ITHTTPReceiver.java
@@ -102,7 +102,7 @@ public void test() throws Exception {
}
int responseCode = connection.getResponseCode();
- if (responseCode != HttpURLConnection.HTTP_OK) { // success
+ if (responseCode != HttpURLConnection.HTTP_ACCEPTED) { // success
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
diff --git a/zipkin-server/receiver-zipkin-scribe/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/zipkin-server/receiver-zipkin-scribe/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
new file mode 100644
index 00000000000..ac22e0d7946
--- /dev/null
+++ b/zipkin-server/receiver-zipkin-scribe/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+
+zipkin.server.receiver.zipkin.scribe.ZipkinScribeModule
diff --git a/zipkin-server/receiver-zipkin-scribe/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/zipkin-server/receiver-zipkin-scribe/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100755
index 00000000000..b52d4407984
--- /dev/null
+++ b/zipkin-server/receiver-zipkin-scribe/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+
+zipkin.server.receiver.zipkin.scribe.ZipkinScribeProvider
\ No newline at end of file
diff --git a/zipkin-server/server-core/pom.xml b/zipkin-server/server-core/pom.xml
index 315695c31cf..02b3525b5a0 100644
--- a/zipkin-server/server-core/pom.xml
+++ b/zipkin-server/server-core/pom.xml
@@ -24,6 +24,11 @@
zipkin-receiver-plugin
${skywalking.version}
+
+ io.github.classgraph
+ classgraph
+ 4.8.162
+
\ No newline at end of file
diff --git a/zipkin-server/server-core/src/main/java/zipkin/server/core/CoreModuleProvider.java b/zipkin-server/server-core/src/main/java/zipkin/server/core/CoreModuleProvider.java
index 437910e6230..7b41cf933f2 100644
--- a/zipkin-server/server-core/src/main/java/zipkin/server/core/CoreModuleProvider.java
+++ b/zipkin-server/server-core/src/main/java/zipkin/server/core/CoreModuleProvider.java
@@ -13,6 +13,7 @@
*/
package zipkin.server.core;
+import com.linecorp.armeria.common.HttpMethod;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.RunningMode;
import org.apache.skywalking.oap.server.core.analysis.meter.MeterSystem;
@@ -84,11 +85,13 @@
import org.apache.skywalking.oap.server.library.server.http.HTTPServerConfig;
import org.apache.skywalking.oap.server.telemetry.api.TelemetryRelatedContext;
import zipkin.server.core.services.EmptyComponentLibraryCatalogService;
-import zipkin.server.core.services.EmptyHTTPHandlerRegister;
import zipkin.server.core.services.EmptyNetworkAddressAliasCache;
+import zipkin.server.core.services.HTTPConfigurableServer;
+import zipkin.server.core.services.HTTPInfoHandler;
import zipkin.server.core.services.ZipkinConfigService;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
public class CoreModuleProvider extends ModuleProvider {
@@ -96,14 +99,14 @@ public class CoreModuleProvider extends ModuleProvider {
private EndpointNameGrouping endpointNameGrouping;
private final ZipkinSourceReceiverImpl receiver;
- private final AnnotationScan annotationScan;
+ private final ZipkinAnnotationScan annotationScan;
private final StorageModels storageModels;
private RemoteClientManager remoteClientManager;
private GRPCServer grpcServer;
private HTTPServer httpServer;
public CoreModuleProvider() {
- this.annotationScan = new AnnotationScan();
+ this.annotationScan = new ZipkinAnnotationScan();
this.receiver = new ZipkinSourceReceiverImpl();
this.storageModels = new StorageModels();
}
@@ -144,16 +147,9 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
);
this.registerServiceImplementation(NamingControl.class, namingControl);
+ annotationScan.registerListener(new DefaultScopeDefine.Listener());
annotationScan.registerListener(new ZipkinStreamAnnotationListener(getManager()));
- AnnotationScan scopeScan = new AnnotationScan();
- scopeScan.registerListener(new DefaultScopeDefine.Listener());
- try {
- scopeScan.scan();
- } catch (Exception e) {
- throw new ModuleStartException(e.getMessage(), e);
- }
-
HTTPServerConfig httpServerConfig = HTTPServerConfig.builder()
.host(moduleConfig.getRestHost())
.port(moduleConfig.getRestPort())
@@ -165,8 +161,10 @@ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
.maxRequestHeaderSize(
moduleConfig.getRestMaxRequestHeaderSize())
.build();
- httpServer = new HTTPServer(httpServerConfig);
+ httpServer = new HTTPConfigurableServer(httpServerConfig);
httpServer.initialize();
+ // "/info" handler
+ httpServer.addHandler(new HTTPInfoHandler(), Arrays.asList(HttpMethod.GET, HttpMethod.POST));
// grpc
if (moduleConfig.getGRPCSslEnabled()) {
diff --git a/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinAnnotationScan.java b/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinAnnotationScan.java
new file mode 100644
index 00000000000..02c45b84d3d
--- /dev/null
+++ b/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinAnnotationScan.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.core;
+
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
+import org.apache.skywalking.oap.server.core.annotation.AnnotationListener;
+import org.apache.skywalking.oap.server.core.storage.StorageException;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ZipkinAnnotationScan {
+ private final List listeners;
+
+ public ZipkinAnnotationScan() {
+ this.listeners = new LinkedList<>();
+ }
+
+ /**
+ * Register the callback listener
+ *
+ * @param listener to be called after class found w/ annotation
+ */
+ public void registerListener(AnnotationListener listener) {
+ listeners.add(new AnnotationListenerCache(listener));
+ }
+
+ public void scan() throws IOException, StorageException {
+ ClassGraph classGraph = new ClassGraph();
+ classGraph.enableClassInfo();
+ final ScanResult scan = classGraph.scan();
+ for (ClassInfo classInfo : scan.getAllClasses()) {
+ // not skywalking package or a subclass should ignore
+ if (!classInfo.getName().startsWith("org.apache.skywalking") || classInfo.getName().contains("$")) {
+ continue;
+ }
+ final Class> aClass = classInfo.loadClass();
+ for (AnnotationListenerCache listener : listeners) {
+ if (aClass.isAnnotationPresent(listener.annotation())) {
+ listener.addMatch(aClass);
+ }
+ }
+ }
+
+ for (AnnotationListenerCache listener : listeners) {
+ listener.complete();
+ }
+ }
+
+ private class AnnotationListenerCache {
+ private AnnotationListener listener;
+ private List> matchedClass;
+
+ private AnnotationListenerCache(AnnotationListener listener) {
+ this.listener = listener;
+ matchedClass = new LinkedList<>();
+ }
+
+ private Class extends Annotation> annotation() {
+ return this.listener.annotation();
+ }
+
+ private void addMatch(Class aClass) {
+ matchedClass.add(aClass);
+ }
+
+ private void complete() throws StorageException {
+ matchedClass.sort(Comparator.comparing(Class::getName));
+ for (Class> aClass : matchedClass) {
+ listener.notify(aClass);
+ }
+ }
+ }
+
+}
diff --git a/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinDispatcherManager.java b/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinDispatcherManager.java
index c4c33d79d2e..81579eb44a1 100644
--- a/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinDispatcherManager.java
+++ b/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinDispatcherManager.java
@@ -14,11 +14,31 @@
package zipkin.server.core;
+import io.github.classgraph.ClassGraph;
+import io.github.classgraph.ClassInfo;
+import io.github.classgraph.ScanResult;
import org.apache.skywalking.oap.server.core.analysis.DispatcherManager;
import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.TagAutocompleteDispatcher;
+import java.io.IOException;
+
public class ZipkinDispatcherManager extends DispatcherManager {
+ @Override
+ public void scan() throws IOException, IllegalAccessException, InstantiationException {
+ ClassGraph classGraph = new ClassGraph();
+ classGraph.enableClassInfo();
+ final ScanResult scan = classGraph.scan();
+ for (ClassInfo classInfo : scan.getAllClasses()) {
+ // not skywalking package or a subclass should ignore
+ if (!classInfo.getName().startsWith("org.apache.skywalking") || classInfo.getName().contains("$")) {
+ continue;
+ }
+ final Class> aClass = classInfo.loadClass();
+ addIfAsSourceDispatcher(aClass);
+ }
+ }
+
@Override
public void addIfAsSourceDispatcher(Class aClass) throws IllegalAccessException, InstantiationException {
if (aClass.getSimpleName().startsWith("Zipkin") || aClass.equals(TagAutocompleteDispatcher.class)) {
diff --git a/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinStreamAnnotationListener.java b/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinStreamAnnotationListener.java
index a1ba4398eb6..1d605b67b80 100644
--- a/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinStreamAnnotationListener.java
+++ b/zipkin-server/server-core/src/main/java/zipkin/server/core/ZipkinStreamAnnotationListener.java
@@ -16,6 +16,7 @@
import org.apache.skywalking.oap.server.core.analysis.StreamAnnotationListener;
import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.TagAutocompleteData;
+import org.apache.skywalking.oap.server.core.analysis.manual.spanattach.SpanAttachedEventRecord;
import org.apache.skywalking.oap.server.core.storage.StorageException;
import org.apache.skywalking.oap.server.library.module.ModuleDefineHolder;
@@ -28,7 +29,7 @@ public ZipkinStreamAnnotationListener(ModuleDefineHolder moduleDefineHolder) {
@Override
public void notify(Class aClass) throws StorageException {
// only including all zipkin streaming
- if (aClass.getSimpleName().startsWith("Zipkin") || aClass.equals(TagAutocompleteData.class)) {
+ if (aClass.getSimpleName().startsWith("Zipkin") || aClass.equals(TagAutocompleteData.class) || aClass.equals(SpanAttachedEventRecord.class)) {
super.notify(aClass);
}
}
diff --git a/zipkin-server/server-core/src/main/java/zipkin/server/core/services/HTTPConfigurableServer.java b/zipkin-server/server-core/src/main/java/zipkin/server/core/services/HTTPConfigurableServer.java
new file mode 100644
index 00000000000..05d9e219e0f
--- /dev/null
+++ b/zipkin-server/server-core/src/main/java/zipkin/server/core/services/HTTPConfigurableServer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.core.services;
+
+import com.linecorp.armeria.common.HttpMethod;
+import com.linecorp.armeria.server.ServerBuilder;
+import org.apache.skywalking.oap.server.library.server.http.HTTPServer;
+import org.apache.skywalking.oap.server.library.server.http.HTTPServerConfig;
+
+import java.util.List;
+
+public class HTTPConfigurableServer extends HTTPServer {
+ public HTTPConfigurableServer(HTTPServerConfig config) {
+ super(config);
+ }
+
+ @Override
+ public void addHandler(Object handler, List httpMethods) {
+ if (handler instanceof ServerConfiguration) {
+ ((ServerConfiguration) handler).configure(sb);
+ allowedMethods.addAll(httpMethods);
+ return;
+ }
+ super.addHandler(handler, httpMethods);
+ }
+
+ public interface ServerConfiguration {
+ void configure(ServerBuilder builder);
+ }
+}
diff --git a/zipkin-server/server-core/src/main/java/zipkin/server/core/services/HTTPInfoHandler.java b/zipkin-server/server-core/src/main/java/zipkin/server/core/services/HTTPInfoHandler.java
new file mode 100644
index 00000000000..9b2bf1b3372
--- /dev/null
+++ b/zipkin-server/server-core/src/main/java/zipkin/server/core/services/HTTPInfoHandler.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.core.services;
+
+import com.google.gson.Gson;
+import com.linecorp.armeria.common.AggregatedHttpResponse;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.MediaType;
+import com.linecorp.armeria.server.annotation.Get;
+import org.apache.skywalking.oap.server.core.version.Version;
+
+public class HTTPInfoHandler {
+ private static final Gson GSON = new Gson();
+
+ @Get("/info")
+ public AggregatedHttpResponse info() {
+ return AggregatedHttpResponse.of(HttpStatus.OK, MediaType.JSON, HttpData.ofUtf8(
+ GSON.toJson(Version.CURRENT.getProperties())
+ ));
+ }
+}
diff --git a/zipkin-server/server-starter/pom.xml b/zipkin-server/server-starter/pom.xml
index bd21c9373ca..4bcb4288ff9 100644
--- a/zipkin-server/server-starter/pom.xml
+++ b/zipkin-server/server-starter/pom.xml
@@ -34,9 +34,9 @@
- org.apache.skywalking
- telemetry-prometheus
- ${skywalking.version}
+ io.zipkin
+ telemetry-zipkin
+ ${project.version}
@@ -61,6 +61,23 @@
${project.version}
+
+
+ io.zipkin
+ zipkin-dependency-storage-jdbc
+ ${project.version}
+
+
+ io.zipkin
+ zipkin-dependency-storage-elasticsearch
+ ${project.version}
+
+
+ io.zipkin
+ zipkin-dependency-storage-banyandb
+ ${project.version}
+
+
io.zipkin
@@ -117,8 +134,34 @@
slf4j-api
${slf4j.version}
+
+
+
+ com.mysql
+ mysql-connector-j
+ 8.0.33
+
+
+
+ include-lens
+
+
+ !skipLens
+
+
+
+
+
+ ${project.groupId}
+ zipkin-lens
+ ${project.version}
+
+
+
+
+
diff --git a/zipkin-server/server-starter/src/main/resources/application.yml b/zipkin-server/server-starter/src/main/resources/application.yml
index d1f98c4406e..1bafe4e3b14 100644
--- a/zipkin-server/server-starter/src/main/resources/application.yml
+++ b/zipkin-server/server-starter/src/main/resources/application.yml
@@ -56,7 +56,7 @@ core:
restAcceptQueueSize: ${ZIPKIN_REST_QUEUE_SIZE:0}
restMaxRequestHeaderSize: ${ZIPKIN_REST_MAX_REQUEST_HEADER_SIZE:8192}
-storage:
+storage: &storage
selector: ${ZIPKIN_STORAGE:h2}
elasticsearch:
namespace: ${ZIPKIN_NAMESPACE:""}
@@ -164,6 +164,8 @@ storage:
maxSizeOfBatchCql: ${ZIPKIN_STORAGE_CASSANDRA_MAX_SIZE_OF_BATCH_CQL:2000}
asyncBatchPersistentPoolSize: ${ZIPKIN_STORAGE_CASSANDRA_ASYNC_BATCH_PERSISTENT_POOL_SIZE:4}
+zipkin-dependency-storage-ext: *storage
+
receiver-zipkin-http:
selector: ${ZIPKIN_RECEIVER_ZIPKIN_HTTP:default}
default:
@@ -237,6 +239,7 @@ query-zipkin:
restAcceptQueueSize: ${ZIPKIN_QUERY_REST_QUEUE_SIZE:0}
restMaxRequestHeaderSize: ${ZIPKIN_QUERY_REST_MAX_REQUEST_HEADER_SIZE:8192}
strictTraceId: ${ZIPKIN_QUERY_STRICT_TRACE_ID:true}
+ allowedOrigins: ${ZIPKIN_QUERY_ALLOWED_ORIGINS:"*"}
# Default look back for traces and autocompleteTags, 1 day in millis
lookback: ${ZIPKIN_QUERY_LOOKBACK:86400000}
# The Cache-Control max-age (seconds) for serviceNames, remoteServiceNames and spanNames
@@ -246,6 +249,11 @@ query-zipkin:
uiQueryLimit: ${ZIPKIN_QUERY_UI_QUERY_LIMIT:10}
# Default look back on the UI for search traces, 15 minutes in millis
uiDefaultLookback: ${ZIPKIN_QUERY_UI_DEFAULT_LOOKBACK:900000}
+ uiEnable: ${ZIPKIN_QUERY_UI_ENABLE:true}
+ uiBasePath: ${ZIPKIN_QUERY_UI_BASE_PATH:/zipkin}
+ dependencyEnabled: ${ZIPKIN_QUERY_DEPENDENCY_ENABLED:true}
+ dependencyLowErrorRate: ${ZIPKIN_QUERY_DEPENDENCY_LOW_ERROR_RATE:0.5}
+ dependencyHighErrorRate: ${ZIPKIN_QUERY_DEPENDENCY_HIGH_ERROR_RATE:0.75}
query-health:
selector: ${ZIPKIN_QUERY_HEALTH:zipkin}
@@ -260,14 +268,17 @@ query-health:
restMaxRequestHeaderSize: ${ZIPKIN_QUERY_HEALTH_REST_MAX_REQUEST_HEADER_SIZE:8192}
telemetry:
- selector: ${ZIPKIN_TELEMETRY:none}
+ selector: ${ZIPKIN_TELEMETRY:zipkin}
none:
- prometheus:
- host: ${ZIPKIN_TELEMETRY_PROMETHEUS_HOST:0.0.0.0}
- port: ${ZIPKIN_TELEMETRY_PROMETHEUS_PORT:1234}
- sslEnabled: ${ZIPKIN_TELEMETRY_PROMETHEUS_SSL_ENABLED:false}
- sslKeyPath: ${ZIPKIN_TELEMETRY_PROMETHEUS_SSL_KEY_PATH:""}
- sslCertChainPath: ${ZIPKIN_TELEMETRY_PROMETHEUS_SSL_CERT_CHAIN_PATH:""}
+ zipkin:
+ restHost: ${ZIPKIN_QUERY_HEALTH_REST_HOST:0.0.0.0}
+ # The port number of the HTTP service, the default HTTP service in the core would be used if the value is smaller than 0.
+ restPort: ${ZIPKIN_QUERY_HEALTH_REST_PORT:-1}
+ restContextPath: ${ZIPKIN_QUERY_HEALTH_REST_CONTEXT_PATH:/}
+ restMaxThreads: ${ZIPKIN_QUERY_HEALTH_REST_MAX_THREADS:200}
+ restIdleTimeOut: ${ZIPKIN_QUERY_HEALTH_REST_IDLE_TIMEOUT:30000}
+ restAcceptQueueSize: ${ZIPKIN_QUERY_HEALTH_REST_QUEUE_SIZE:0}
+ restMaxRequestHeaderSize: ${ZIPKIN_QUERY_HEALTH_REST_MAX_REQUEST_HEADER_SIZE:8192}
cluster:
selector: standalone
diff --git a/zipkin-server/telemetry-zipkin/pom.xml b/zipkin-server/telemetry-zipkin/pom.xml
new file mode 100644
index 00000000000..cf74731f909
--- /dev/null
+++ b/zipkin-server/telemetry-zipkin/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+ zipkin-server-parent
+ io.zipkin
+ 2.24.4-SNAPSHOT
+
+
+ Telemetry Zipkin
+ telemetry-zipkin
+
+
+
+ org.apache.skywalking
+ telemetry-prometheus
+ ${skywalking.version}
+
+
+ io.zipkin
+ zipkin-server-core
+ ${project.version}
+
+
+
+
\ No newline at end of file
diff --git a/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryConfig.java b/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryConfig.java
new file mode 100644
index 00000000000..f5b81dda66a
--- /dev/null
+++ b/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryConfig.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.telemetry;
+
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+
+public class ZipkinTelemetryConfig extends ModuleConfig {
+ private String restHost;
+ private int restPort;
+ private String restContextPath;
+ private int restMaxThreads = 200;
+ private long restIdleTimeOut = 30000;
+ private int restAcceptQueueSize = 0;
+ /**
+ * The maximum size in bytes allowed for request headers.
+ * Use -1 to disable it.
+ */
+ private int restMaxRequestHeaderSize = 8192;
+
+ public String getRestHost() {
+ return restHost;
+ }
+
+ public void setRestHost(String restHost) {
+ this.restHost = restHost;
+ }
+
+ public int getRestPort() {
+ return restPort;
+ }
+
+ public void setRestPort(int restPort) {
+ this.restPort = restPort;
+ }
+
+ public String getRestContextPath() {
+ return restContextPath;
+ }
+
+ public void setRestContextPath(String restContextPath) {
+ this.restContextPath = restContextPath;
+ }
+
+ public int getRestMaxThreads() {
+ return restMaxThreads;
+ }
+
+ public void setRestMaxThreads(int restMaxThreads) {
+ this.restMaxThreads = restMaxThreads;
+ }
+
+ public long getRestIdleTimeOut() {
+ return restIdleTimeOut;
+ }
+
+ public void setRestIdleTimeOut(long restIdleTimeOut) {
+ this.restIdleTimeOut = restIdleTimeOut;
+ }
+
+ public int getRestAcceptQueueSize() {
+ return restAcceptQueueSize;
+ }
+
+ public void setRestAcceptQueueSize(int restAcceptQueueSize) {
+ this.restAcceptQueueSize = restAcceptQueueSize;
+ }
+
+ public int getRestMaxRequestHeaderSize() {
+ return restMaxRequestHeaderSize;
+ }
+
+ public void setRestMaxRequestHeaderSize(int restMaxRequestHeaderSize) {
+ this.restMaxRequestHeaderSize = restMaxRequestHeaderSize;
+ }
+}
diff --git a/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryHandler.java b/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryHandler.java
new file mode 100644
index 00000000000..e13fadf4156
--- /dev/null
+++ b/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.telemetry;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.linecorp.armeria.common.AggregatedHttpResponse;
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.MediaType;
+import com.linecorp.armeria.server.annotation.Get;
+import io.prometheus.client.Collector;
+import io.prometheus.client.CollectorRegistry;
+import io.prometheus.client.exporter.common.TextFormat;
+import org.apache.logging.log4j.core.util.StringBuilderWriter;
+
+import java.io.IOException;
+import java.util.Enumeration;
+
+public class ZipkinTelemetryHandler {
+ private final Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+ private final CollectorRegistry registry = CollectorRegistry.defaultRegistry;
+
+ @Get("/metrics")
+ public AggregatedHttpResponse metrics() throws IOException {
+ final JsonObject jsonObject = new JsonObject();
+ final Enumeration samples = registry.metricFamilySamples();
+ while (samples.hasMoreElements()) {
+ final Collector.MetricFamilySamples sampleFamily = samples.nextElement();
+ final JsonArray sampleFamilyJsonArray = new JsonArray();
+ jsonObject.add(sampleFamily.name, sampleFamilyJsonArray);
+ for (Collector.MetricFamilySamples.Sample sample : sampleFamily.samples) {
+ final JsonObject sampleJson = new JsonObject();
+ final JsonObject meterTags = new JsonObject();
+ sampleJson.add("tags", meterTags);
+ for (int i = 0; i < sample.labelNames.size(); i++) {
+ meterTags.addProperty(sample.labelNames.get(i), sample.labelValues.get(i));
+ }
+ sampleJson.addProperty("type", sampleFamily.type.name());
+ sampleJson.addProperty("value", sample.value);
+ sampleFamilyJsonArray.add(sampleJson);
+ }
+ }
+ return AggregatedHttpResponse.of(HttpStatus.OK, MediaType.JSON, HttpData.ofUtf8(gson.toJson(jsonObject)));
+ }
+
+ @Get("/prometheus")
+ public AggregatedHttpResponse prometheus() throws IOException {
+ StringBuilderWriter buf = new StringBuilderWriter();
+ TextFormat.write004(buf, registry.metricFamilySamples());
+ return AggregatedHttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, HttpData.ofUtf8(buf.toString()));
+ }
+}
diff --git a/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryProvider.java b/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryProvider.java
new file mode 100644
index 00000000000..839d2b1e152
--- /dev/null
+++ b/zipkin-server/telemetry-zipkin/src/main/java/zipkin/server/telemetry/ZipkinTelemetryProvider.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.telemetry;
+
+import com.linecorp.armeria.common.HttpMethod;
+import io.prometheus.client.hotspot.DefaultExports;
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.server.HTTPHandlerRegister;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
+import org.apache.skywalking.oap.server.library.server.http.HTTPServer;
+import org.apache.skywalking.oap.server.library.server.http.HTTPServerConfig;
+import org.apache.skywalking.oap.server.telemetry.TelemetryModule;
+import org.apache.skywalking.oap.server.telemetry.api.MetricsCollector;
+import org.apache.skywalking.oap.server.telemetry.api.MetricsCreator;
+import org.apache.skywalking.oap.server.telemetry.prometheus.PrometheusMetricsCollector;
+import org.apache.skywalking.oap.server.telemetry.prometheus.PrometheusMetricsCreator;
+import zipkin.server.core.services.HTTPConfigurableServer;
+
+import java.util.Arrays;
+
+public class ZipkinTelemetryProvider extends ModuleProvider {
+ private ZipkinTelemetryConfig moduleConfig;
+ private HTTPServer httpServer;
+ @Override
+ public String name() {
+ return "zipkin";
+ }
+
+ @Override
+ public Class extends ModuleDefine> module() {
+ return TelemetryModule.class;
+ }
+
+ @Override
+ public ConfigCreator extends ModuleConfig> newConfigCreator() {
+ return new ConfigCreator() {
+ @Override
+ public Class type() {
+ return ZipkinTelemetryConfig.class;
+ }
+
+ @Override
+ public void onInitialized(ZipkinTelemetryConfig initialized) {
+ moduleConfig = initialized;
+ }
+ };
+ }
+
+ @Override
+ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
+ this.registerServiceImplementation(MetricsCreator.class, new PrometheusMetricsCreator());
+ this.registerServiceImplementation(MetricsCollector.class, new PrometheusMetricsCollector());
+
+ if (moduleConfig.getRestPort() > 0) {
+ HTTPServerConfig httpServerConfig = HTTPServerConfig.builder()
+ .host(moduleConfig.getRestHost())
+ .port(moduleConfig.getRestPort())
+ .contextPath(moduleConfig.getRestContextPath())
+ .idleTimeOut(moduleConfig.getRestIdleTimeOut())
+ .maxThreads(moduleConfig.getRestMaxThreads())
+ .acceptQueueSize(moduleConfig.getRestAcceptQueueSize())
+ .maxRequestHeaderSize(moduleConfig.getRestMaxRequestHeaderSize())
+ .build();
+ httpServer = new HTTPConfigurableServer(httpServerConfig);
+ httpServer.initialize();
+ }
+
+ DefaultExports.initialize();
+ }
+
+ @Override
+ public void start() throws ServiceNotProvidedException, ModuleStartException {
+ final ZipkinTelemetryHandler handler = new ZipkinTelemetryHandler();
+ if (httpServer != null) {
+ httpServer.addHandler(handler, Arrays.asList(HttpMethod.GET, HttpMethod.POST));
+ return;
+ }
+ final HTTPHandlerRegister httpRegister = getManager().find(CoreModule.NAME).provider()
+ .getService(HTTPHandlerRegister.class);
+ httpRegister.addHandler(handler, Arrays.asList(HttpMethod.GET, HttpMethod.POST));
+ }
+
+ @Override
+ public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
+ if (httpServer != null) {
+ httpServer.start();
+ }
+ }
+
+ @Override
+ public String[] requiredModules() {
+ return new String[0];
+ }
+}
diff --git a/zipkin-server/telemetry-zipkin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/zipkin-server/telemetry-zipkin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100755
index 00000000000..7e588ef8c28
--- /dev/null
+++ b/zipkin-server/telemetry-zipkin/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+
+zipkin.server.telemetry.ZipkinTelemetryProvider
\ No newline at end of file
diff --git a/zipkin-server/zipkin-dependency/pom.xml b/zipkin-server/zipkin-dependency/pom.xml
new file mode 100644
index 00000000000..5eb2c4d7879
--- /dev/null
+++ b/zipkin-server/zipkin-dependency/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ zipkin-server-parent
+ io.zipkin
+ 2.24.4-SNAPSHOT
+
+
+ zipkin-dependency
+ Zipkin Dependency
+
+
+
+ io.zipkin
+ zipkin-server-core
+ ${project.version}
+
+
+
+
\ No newline at end of file
diff --git a/zipkin-server/zipkin-dependency/src/main/java/org/apache/skywalking/zipkin/dependency/entity/ZipkinDependency.java b/zipkin-server/zipkin-dependency/src/main/java/org/apache/skywalking/zipkin/dependency/entity/ZipkinDependency.java
new file mode 100644
index 00000000000..af62d54dd4f
--- /dev/null
+++ b/zipkin-server/zipkin-dependency/src/main/java/org/apache/skywalking/zipkin/dependency/entity/ZipkinDependency.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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.skywalking.zipkin.dependency.entity;
+
+import org.apache.skywalking.oap.server.core.analysis.MetricsExtension;
+import org.apache.skywalking.oap.server.core.analysis.Stream;
+import org.apache.skywalking.oap.server.core.analysis.metrics.Metrics;
+import org.apache.skywalking.oap.server.core.analysis.worker.MetricsStreamProcessor;
+import org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteData;
+import org.apache.skywalking.oap.server.core.source.ScopeDeclaration;
+import org.apache.skywalking.oap.server.core.storage.StorageID;
+import org.apache.skywalking.oap.server.core.storage.annotation.BanyanDB;
+import org.apache.skywalking.oap.server.core.storage.annotation.Column;
+import org.apache.skywalking.oap.server.core.storage.type.Convert2Entity;
+import org.apache.skywalking.oap.server.core.storage.type.Convert2Storage;
+import org.apache.skywalking.oap.server.core.storage.type.StorageBuilder;
+
+import java.util.Objects;
+
+@ScopeDeclaration(id = 10001, name = "ZipkinDependency")
+@Stream(name = ZipkinDependency.INDEX_NAME, scopeId = 10001, builder = ZipkinDependency.Builder.class, processor = MetricsStreamProcessor.class)
+@MetricsExtension(supportDownSampling = false, supportUpdate = false)
+@BanyanDB.TimestampColumn(ZipkinDependency.DAY)
+public class ZipkinDependency extends Metrics {
+ public static final String INDEX_NAME = "zipkin_dependency";
+ public static final String DAY = "analyze_day";
+ public static final String PARENT = "parent";
+ public static final String CHILD = "child";
+ public static final String CALL_COUNT = "call_count";
+ public static final String ERROR_COUNT = "error_count";
+
+ @Column(name = DAY)
+ @BanyanDB.SeriesID(index = 0)
+ private long day;
+ @Column(name = PARENT)
+ @BanyanDB.SeriesID(index = 1)
+ private String parent;
+ @Column(name = CHILD)
+ @BanyanDB.SeriesID(index = 2)
+ private String child;
+ @Column(name = CALL_COUNT)
+ @BanyanDB.MeasureField
+ private long callCount;
+ @Column(name = ERROR_COUNT)
+ @BanyanDB.MeasureField
+ private long errorCount;
+
+ @Override
+ public boolean combine(Metrics metrics) {
+ return true;
+ }
+
+ @Override
+ public void calculate() {
+ }
+
+ @Override
+ public Metrics toHour() {
+ return null;
+ }
+
+ @Override
+ public Metrics toDay() {
+ return null;
+ }
+
+ @Override
+ protected StorageID id0() {
+ return new StorageID().append(DAY, day)
+ .append(PARENT, parent).append(CHILD, child);
+ }
+
+ @Override
+ public void deserialize(RemoteData remoteData) {
+ }
+
+ @Override
+ public RemoteData.Builder serialize() {
+ // only query from the storage
+ return null;
+ }
+
+ @Override
+ public int remoteHashCode() {
+ return (int) this.day;
+ }
+
+ public static class Builder implements StorageBuilder {
+
+ @Override
+ public ZipkinDependency storage2Entity(Convert2Entity converter) {
+ final ZipkinDependency record = new ZipkinDependency();
+ record.setDay(((Number) converter.get(DAY)).longValue());
+ record.setParent((String) converter.get(PARENT));
+ record.setChild((String) converter.get(CHILD));
+ record.setCallCount(((Number) converter.get(CALL_COUNT)).longValue());
+ record.setErrorCount(((Number) converter.get(ERROR_COUNT)).longValue());
+ return record;
+ }
+
+ @Override
+ public void entity2Storage(ZipkinDependency entity, Convert2Storage converter) {
+ converter.accept(DAY, entity.getDay());
+ converter.accept(PARENT, entity.getParent());
+ converter.accept(CHILD, entity.getChild());
+ converter.accept(CALL_COUNT, entity.getCallCount());
+ converter.accept(ERROR_COUNT, entity.getErrorCount());
+ }
+ }
+
+ public Long getDay() {
+ return day;
+ }
+
+ public void setDay(Long day) {
+ this.day = day;
+ }
+
+ public String getParent() {
+ return parent;
+ }
+
+ public void setParent(String parent) {
+ this.parent = parent;
+ }
+
+ public String getChild() {
+ return child;
+ }
+
+ public void setChild(String child) {
+ this.child = child;
+ }
+
+ public Long getCallCount() {
+ return callCount;
+ }
+
+ public void setCallCount(Long callCount) {
+ this.callCount = callCount;
+ }
+
+ public Long getErrorCount() {
+ return errorCount;
+ }
+
+ public void setErrorCount(Long errorCount) {
+ this.errorCount = errorCount;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ZipkinDependency)) return false;
+ if (!super.equals(o)) return false;
+ ZipkinDependency that = (ZipkinDependency) o;
+ return getDay() == that.getDay() && Objects.equals(getParent(), that.getParent()) && Objects.equals(getChild(), that.getChild());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), getDay(), getParent(), getChild());
+ }
+}
diff --git a/zipkin-server/zipkin-dependency/src/main/java/zipkin/server/dependency/IZipkinDependencyQueryDAO.java b/zipkin-server/zipkin-dependency/src/main/java/zipkin/server/dependency/IZipkinDependencyQueryDAO.java
new file mode 100644
index 00000000000..10586b2e31e
--- /dev/null
+++ b/zipkin-server/zipkin-dependency/src/main/java/zipkin/server/dependency/IZipkinDependencyQueryDAO.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency;
+
+import org.apache.skywalking.oap.server.library.module.Service;
+import zipkin2.DependencyLink;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface IZipkinDependencyQueryDAO extends Service {
+
+ List getDependencies(long endTs, long lookback) throws IOException;
+}
diff --git a/zipkin-server/zipkin-dependency/src/main/java/zipkin/server/dependency/ZipkinDependencyModule.java b/zipkin-server/zipkin-dependency/src/main/java/zipkin/server/dependency/ZipkinDependencyModule.java
new file mode 100644
index 00000000000..1680003eb6a
--- /dev/null
+++ b/zipkin-server/zipkin-dependency/src/main/java/zipkin/server/dependency/ZipkinDependencyModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency;
+
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+
+public class ZipkinDependencyModule extends ModuleDefine {
+ public static final String NAME = "zipkin-dependency-storage-ext";
+
+ public ZipkinDependencyModule() {
+ super(NAME);
+ }
+
+ @Override
+ public Class[] services() {
+ return new Class[] {IZipkinDependencyQueryDAO.class};
+ }
+}
diff --git a/zipkin-server/zipkin-dependency/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/zipkin-server/zipkin-dependency/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
new file mode 100644
index 00000000000..578ab0f83a8
--- /dev/null
+++ b/zipkin-server/zipkin-dependency/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+
+zipkin.server.dependency.ZipkinDependencyModule
\ No newline at end of file
diff --git a/zipkin-server/zipkin-storage-ext/pom.xml b/zipkin-server/zipkin-storage-ext/pom.xml
new file mode 100644
index 00000000000..66c0033b42c
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+ pom
+
+ zipkin-server-parent
+ io.zipkin
+ 2.24.4-SNAPSHOT
+
+
+ zipkin-storage-ext
+ Zipkin Storage Extension
+
+
+ zipkin-dependency-storage-jdbc
+ zipkin-dependency-storage-elasticsearch
+ zipkin-dependency-storage-banyandb
+
+
+
+
+ io.zipkin
+ zipkin-dependency
+ ${project.version}
+
+
+
+
\ No newline at end of file
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/pom.xml b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/pom.xml
new file mode 100644
index 00000000000..17f0c460bbc
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ zipkin-storage-ext
+ io.zipkin
+ 2.24.4-SNAPSHOT
+
+
+ zipkin-dependency-storage-banyandb
+ Zipkin Dependency BanyanDB Extension
+
+
+
+ org.apache.skywalking
+ storage-banyandb-plugin
+ ${skywalking.version}
+
+
+
+
\ No newline at end of file
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/java/zipkin/server/dependency/storage/banyandb/ZipkinDependencyBanyanDBQueryDAO.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/java/zipkin/server/dependency/storage/banyandb/ZipkinDependencyBanyanDBQueryDAO.java
new file mode 100644
index 00000000000..67eb7f897b2
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/java/zipkin/server/dependency/storage/banyandb/ZipkinDependencyBanyanDBQueryDAO.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.banyandb;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.skywalking.banyandb.v1.client.MeasureQuery;
+import org.apache.skywalking.banyandb.v1.client.MeasureQueryResponse;
+import org.apache.skywalking.banyandb.v1.client.TimestampRange;
+import org.apache.skywalking.oap.server.core.query.enumeration.Step;
+import org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageClient;
+import org.apache.skywalking.oap.server.storage.plugin.banyandb.MetadataRegistry;
+import org.apache.skywalking.zipkin.dependency.entity.ZipkinDependency;
+import zipkin.server.dependency.IZipkinDependencyQueryDAO;
+import zipkin2.DependencyLink;
+import zipkin2.internal.DependencyLinker;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static zipkin2.internal.DateUtil.epochDays;
+
+public class ZipkinDependencyBanyanDBQueryDAO implements IZipkinDependencyQueryDAO {
+ protected BanyanDBStorageClient client;
+ final Set tags = ImmutableSet.of(ZipkinDependency.DAY, ZipkinDependency.PARENT, ZipkinDependency.CHILD);
+ final Set fields = ImmutableSet.of(ZipkinDependency.CALL_COUNT, ZipkinDependency.ERROR_COUNT);
+
+ @Override
+ public List getDependencies(long endTs, long lookback) throws IOException {
+ final List days = epochDays(endTs, lookback);
+ final TimestampRange timeRange = new TimestampRange(days.get(0), days.get(days.size() - 1) + 1);
+ MetadataRegistry.Schema schema = MetadataRegistry.INSTANCE.findMetadata(ZipkinDependency.INDEX_NAME, Step.MINUTE);
+ final MeasureQuery query = new MeasureQuery(schema.getMetadata().getGroup(), schema.getMetadata().name(), timeRange, tags, fields);
+ final MeasureQueryResponse result = client.query(query);
+ return DependencyLinker.merge(result.getDataPoints().stream().map(s -> DependencyLink.newBuilder()
+ .parent(s.getTagValue(ZipkinDependency.PARENT))
+ .child(s.getTagValue(ZipkinDependency.CHILD))
+ .callCount(((Number)s.getFieldValue(ZipkinDependency.CALL_COUNT)).longValue())
+ .errorCount(s.getFieldValue(ZipkinDependency.ERROR_COUNT) != null ? ((Number)s.getFieldValue(ZipkinDependency.ERROR_COUNT)).longValue() : 0)
+ .build()).collect(Collectors.toList()));
+ }
+
+ public void setClient(BanyanDBStorageClient client) {
+ this.client = client;
+ }
+
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/java/zipkin/server/dependency/storage/banyandb/ZipkinDependencyBanyanDBStorageProvider.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/java/zipkin/server/dependency/storage/banyandb/ZipkinDependencyBanyanDBStorageProvider.java
new file mode 100644
index 00000000000..0f7571077e8
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/java/zipkin/server/dependency/storage/banyandb/ZipkinDependencyBanyanDBStorageProvider.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.banyandb;
+
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.storage.StorageModule;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
+import org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageClient;
+import org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageConfig;
+import org.apache.skywalking.oap.server.storage.plugin.banyandb.BanyanDBStorageProvider;
+import zipkin.server.dependency.IZipkinDependencyQueryDAO;
+import zipkin.server.dependency.ZipkinDependencyModule;
+
+import java.lang.reflect.Field;
+
+public class ZipkinDependencyBanyanDBStorageProvider extends ModuleProvider {
+ private BanyanDBStorageConfig config;
+ private ZipkinDependencyBanyanDBQueryDAO queryDAO;
+
+ @Override
+ public String name() {
+ return "banyandb";
+ }
+
+ @Override
+ public Class extends ModuleDefine> module() {
+ return ZipkinDependencyModule.class;
+ }
+
+ @Override
+ public ConfigCreator extends ModuleConfig> newConfigCreator() {
+ return new ConfigCreator() {
+
+ @Override
+ public Class type() {
+ return BanyanDBStorageConfig.class;
+ }
+
+ @Override
+ public void onInitialized(BanyanDBStorageConfig initialized) {
+ config = initialized;
+ }
+ };
+ }
+
+ @Override
+ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
+ this.queryDAO = new ZipkinDependencyBanyanDBQueryDAO();
+ this.registerServiceImplementation(IZipkinDependencyQueryDAO.class, this.queryDAO);
+ }
+
+ @Override
+ public void start() throws ServiceNotProvidedException, ModuleStartException {
+
+ }
+
+ @Override
+ public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
+ BanyanDBStorageProvider provider =
+ (BanyanDBStorageProvider) getManager().find(StorageModule.NAME).provider();
+ try {
+ Field field = BanyanDBStorageProvider.class.getDeclaredField("client");
+ field.setAccessible(true);
+ BanyanDBStorageClient client = (BanyanDBStorageClient) field.get(provider);
+ queryDAO.setClient(client);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new ModuleStartException("Failed to get BanyanDBStorageClient.", e);
+ }
+ }
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME, StorageModule.NAME};
+ }
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100644
index 00000000000..dbee1d7b454
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-banyandb/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+
+zipkin.server.dependency.storage.banyandb.ZipkinDependencyBanyanDBStorageProvider
\ No newline at end of file
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/pom.xml b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/pom.xml
new file mode 100644
index 00000000000..18589f758b8
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+ zipkin-storage-ext
+ io.zipkin
+ 2.24.4-SNAPSHOT
+
+ 4.0.0
+
+ zipkin-dependency-storage-elasticsearch
+ Zipkin Dependency Elasticsearch Extension
+
+
+
+ org.apache.skywalking
+ storage-elasticsearch-plugin
+ ${skywalking.version}
+
+
+
+
\ No newline at end of file
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/java/zipkin/server/dependency/storage/elasticsearch/ZipkinDependencyElasticsearchQueryDAO.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/java/zipkin/server/dependency/storage/elasticsearch/ZipkinDependencyElasticsearchQueryDAO.java
new file mode 100644
index 00000000000..9615d8ef842
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/java/zipkin/server/dependency/storage/elasticsearch/ZipkinDependencyElasticsearchQueryDAO.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.elasticsearch;
+
+import org.apache.skywalking.library.elasticsearch.requests.search.BoolQueryBuilder;
+import org.apache.skywalking.library.elasticsearch.requests.search.Query;
+import org.apache.skywalking.library.elasticsearch.requests.search.Search;
+import org.apache.skywalking.library.elasticsearch.requests.search.SearchBuilder;
+import org.apache.skywalking.library.elasticsearch.response.search.SearchResponse;
+import org.apache.skywalking.oap.server.library.client.elasticsearch.ElasticSearchClient;
+import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.base.IndexController;
+import org.apache.skywalking.zipkin.dependency.entity.ZipkinDependency;
+import zipkin.server.dependency.IZipkinDependencyQueryDAO;
+import zipkin2.DependencyLink;
+import zipkin2.internal.DependencyLinker;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static zipkin2.internal.DateUtil.epochDays;
+
+public class ZipkinDependencyElasticsearchQueryDAO implements IZipkinDependencyQueryDAO {
+ private ElasticSearchClient client;
+ @Override
+ public List getDependencies(long endTs, long lookback) throws IOException {
+ final List days = epochDays(endTs, lookback);
+
+ final String index =
+ IndexController.LogicIndicesRegister.getPhysicalTableName(ZipkinDependency.INDEX_NAME);
+ final BoolQueryBuilder query = Query.bool();
+ if (IndexController.LogicIndicesRegister.isMergedTable(ZipkinDependency.INDEX_NAME)) {
+ query.must(Query.term(IndexController.LogicIndicesRegister.METRIC_TABLE_NAME, ZipkinDependency.INDEX_NAME));
+ }
+ query.must(Query.terms(ZipkinDependency.DAY, days));
+ final SearchBuilder search = Search.builder().query(query)
+ .size(days.size());
+
+ final SearchResponse response = client.search(index, search.build());
+ return DependencyLinker.merge(response.getHits().getHits().stream().map(h -> DependencyLink.newBuilder()
+ .parent((String) h.getSource().get(ZipkinDependency.PARENT))
+ .child((String) h.getSource().get(ZipkinDependency.CHILD))
+ .callCount(((Number) h.getSource().get(ZipkinDependency.CALL_COUNT)).longValue())
+ .errorCount(((Number) h.getSource().get(ZipkinDependency.ERROR_COUNT)).longValue())
+ .build()).collect(Collectors.toList()));
+ }
+
+ public void setClient(ElasticSearchClient client) {
+ this.client = client;
+ }
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/java/zipkin/server/dependency/storage/elasticsearch/ZipkinDependencyElasticsearchStorageProvider.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/java/zipkin/server/dependency/storage/elasticsearch/ZipkinDependencyElasticsearchStorageProvider.java
new file mode 100644
index 00000000000..39d318c73ab
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/java/zipkin/server/dependency/storage/elasticsearch/ZipkinDependencyElasticsearchStorageProvider.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.elasticsearch;
+
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.storage.StorageModule;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
+import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.StorageModuleElasticsearchConfig;
+import org.apache.skywalking.oap.server.storage.plugin.elasticsearch.StorageModuleElasticsearchProvider;
+import zipkin.server.dependency.IZipkinDependencyQueryDAO;
+import zipkin.server.dependency.ZipkinDependencyModule;
+
+import java.lang.reflect.Field;
+
+public class ZipkinDependencyElasticsearchStorageProvider extends ModuleProvider {
+ private StorageModuleElasticsearchConfig config;
+ private ZipkinDependencyElasticsearchQueryDAO queryDAO;
+
+ @Override
+ public String name() {
+ return "elasticsearch";
+ }
+
+ @Override
+ public Class extends ModuleDefine> module() {
+ return ZipkinDependencyModule.class;
+ }
+
+ @Override
+ public ConfigCreator extends ModuleConfig> newConfigCreator() {
+ return new ConfigCreator() {
+
+ @Override
+ public Class type() {
+ return StorageModuleElasticsearchConfig.class;
+ }
+
+ @Override
+ public void onInitialized(StorageModuleElasticsearchConfig initialized) {
+ config = initialized;
+ }
+ };
+ }
+
+ @Override
+ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
+ this.queryDAO = new ZipkinDependencyElasticsearchQueryDAO();
+ this.registerServiceImplementation(IZipkinDependencyQueryDAO.class, this.queryDAO);
+ }
+
+ @Override
+ public void start() throws ServiceNotProvidedException, ModuleStartException {
+
+ }
+
+ @Override
+ public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
+ StorageModuleElasticsearchProvider provider = (StorageModuleElasticsearchProvider) getManager().find(StorageModule.NAME)
+ .provider();
+ queryDAO.setClient(getFieldValue(provider, StorageModuleElasticsearchProvider.class, "elasticSearchClient"));
+ }
+
+ private T getFieldValue(Object from, Class fieldBelongClass, String fieldName) throws ModuleStartException {
+ try {
+ Field field = fieldBelongClass.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return (T) field.get(from);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new ModuleStartException("Failed to get " + fieldName, e);
+ }
+ }
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME, StorageModule.NAME};
+ }
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100644
index 00000000000..49876f6e19c
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-elasticsearch/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+#
+
+zipkin.server.dependency.storage.elasticsearch.ZipkinDependencyElasticsearchStorageProvider
\ No newline at end of file
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/pom.xml b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/pom.xml
new file mode 100644
index 00000000000..5cc241a35a3
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+ zipkin-storage-ext
+ io.zipkin
+ 2.24.4-SNAPSHOT
+
+ 4.0.0
+
+ zipkin-dependency-storage-jdbc
+ Zipkin Dependency JDBC Extension
+
+
+
+ org.apache.skywalking
+ storage-jdbc-hikaricp-plugin
+ ${skywalking.version}
+
+
+
+
\ No newline at end of file
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/common/ZipkinDependencyJDBCStorageProvider.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/common/ZipkinDependencyJDBCStorageProvider.java
new file mode 100644
index 00000000000..b3eb49ad88b
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/common/ZipkinDependencyJDBCStorageProvider.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.jdbc.common;
+
+import org.apache.skywalking.oap.server.core.CoreModule;
+import org.apache.skywalking.oap.server.core.storage.StorageModule;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
+import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.JDBCStorageConfig;
+import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.JDBCStorageProvider;
+import zipkin.server.dependency.IZipkinDependencyQueryDAO;
+import zipkin.server.dependency.ZipkinDependencyModule;
+import zipkin.server.dependency.storage.jdbc.common.dao.ZipkinDependencyJDBCQueryDAO;
+
+import java.lang.reflect.Field;
+
+public abstract class ZipkinDependencyJDBCStorageProvider extends ModuleProvider {
+ private JDBCStorageConfig config;
+ private ZipkinDependencyJDBCQueryDAO queryDAO;
+
+ @Override
+ public Class extends ModuleDefine> module() {
+ return ZipkinDependencyModule.class;
+ }
+
+ @Override
+ public ConfigCreator extends ModuleConfig> newConfigCreator() {
+ return new ConfigCreator() {
+ @Override
+ public Class type() {
+ return JDBCStorageConfig.class;
+ }
+
+ @Override
+ public void onInitialized(JDBCStorageConfig initialized) {
+ config = initialized;
+ }
+ };
+ }
+
+ @Override
+ public void prepare() throws ServiceNotProvidedException, ModuleStartException {
+ this.queryDAO = new ZipkinDependencyJDBCQueryDAO();
+ this.registerServiceImplementation(IZipkinDependencyQueryDAO.class, queryDAO);
+ }
+
+ @Override
+ public void start() throws ServiceNotProvidedException, ModuleStartException {
+
+ }
+
+ @Override
+ public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
+ JDBCStorageProvider provider =
+ (JDBCStorageProvider) getManager().find(StorageModule.NAME).provider();
+ queryDAO.setClient(getFieldValue(provider, JDBCStorageProvider.class, "jdbcClient"));
+ queryDAO.setTableHelper(getFieldValue(provider, JDBCStorageProvider.class, "tableHelper"));
+ }
+
+ private T getFieldValue(Object from, Class fieldBelongClass, String fieldName) throws ModuleStartException {
+ try {
+ Field field = fieldBelongClass.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return (T) field.get(from);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new ModuleStartException("Failed to get " + fieldName, e);
+ }
+ }
+
+ @Override
+ public String[] requiredModules() {
+ return new String[] {CoreModule.NAME, StorageModule.NAME};
+ }
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/common/dao/ZipkinDependencyJDBCQueryDAO.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/common/dao/ZipkinDependencyJDBCQueryDAO.java
new file mode 100644
index 00000000000..17a8cee50b1
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/common/dao/ZipkinDependencyJDBCQueryDAO.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.jdbc.common.dao;
+
+import org.apache.skywalking.oap.server.core.analysis.DownSampling;
+import org.apache.skywalking.oap.server.core.analysis.TimeBucket;
+import org.apache.skywalking.oap.server.library.client.jdbc.hikaricp.JDBCClient;
+import org.apache.skywalking.oap.server.storage.plugin.jdbc.common.TableHelper;
+import org.apache.skywalking.zipkin.dependency.entity.ZipkinDependency;
+import zipkin.server.dependency.IZipkinDependencyQueryDAO;
+import zipkin2.DependencyLink;
+import zipkin2.internal.DependencyLinker;
+
+import java.io.IOException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.stream.Collectors.joining;
+import static zipkin2.internal.DateUtil.epochDays;
+
+public class ZipkinDependencyJDBCQueryDAO implements IZipkinDependencyQueryDAO {
+ private JDBCClient client;
+ private TableHelper tableHelper;
+
+ @Override
+ public List getDependencies(long endTs, long lookback) throws IOException {
+ final List days = epochDays(endTs, lookback);
+
+ final long startBucket = TimeBucket.getTimeBucket(endTs - lookback, DownSampling.Day);
+ final long endBucket = TimeBucket.getTimeBucket(endTs, DownSampling.Day);
+ final List result = new ArrayList<>();
+
+ for (String table : tableHelper.getTablesForRead(ZipkinDependency.INDEX_NAME, startBucket, endBucket)) {
+ try {
+ client.executeQuery("select * from " + table + " where " + ZipkinDependency.DAY + " in "
+ + days.stream().map(it -> "?").collect(joining(",", "(", ")")),
+ resultSet -> {
+ while (resultSet.next()) {
+ result.add(DependencyLink.newBuilder()
+ .parent(resultSet.getString(ZipkinDependency.PARENT))
+ .child(resultSet.getString(ZipkinDependency.CHILD))
+ .callCount(resultSet.getLong(ZipkinDependency.CALL_COUNT))
+ .errorCount(resultSet.getLong(ZipkinDependency.ERROR_COUNT))
+ .build());
+ }
+ return null;
+ }, days.toArray());
+ } catch (SQLException e) {
+ throw new IOException(e);
+ }
+ }
+ return DependencyLinker.merge(result);
+ }
+
+ public void setClient(JDBCClient client) {
+ this.client = client;
+ }
+
+ public void setTableHelper(TableHelper tableHelper) {
+ this.tableHelper = tableHelper;
+ }
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/h2/ZipkinDependencyH2StorageProvider.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/h2/ZipkinDependencyH2StorageProvider.java
new file mode 100644
index 00000000000..3ab0f6beb14
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/h2/ZipkinDependencyH2StorageProvider.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.jdbc.h2;
+
+import zipkin.server.dependency.storage.jdbc.common.ZipkinDependencyJDBCStorageProvider;
+
+public class ZipkinDependencyH2StorageProvider extends ZipkinDependencyJDBCStorageProvider {
+ @Override
+ public String name() {
+ return "h2";
+ }
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/mysql/ZipkinDependencyMySQLStorageProvider.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/mysql/ZipkinDependencyMySQLStorageProvider.java
new file mode 100644
index 00000000000..8adad7198d8
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/mysql/ZipkinDependencyMySQLStorageProvider.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.jdbc.mysql;
+
+import zipkin.server.dependency.storage.jdbc.common.ZipkinDependencyJDBCStorageProvider;
+
+public class ZipkinDependencyMySQLStorageProvider extends ZipkinDependencyJDBCStorageProvider {
+ @Override
+ public String name() {
+ return "mysql";
+ }
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/postgres/ZipkinDependencyPostgresStorageProvider.java b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/postgres/ZipkinDependencyPostgresStorageProvider.java
new file mode 100644
index 00000000000..485a49e482d
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/java/zipkin/server/dependency/storage/jdbc/postgres/ZipkinDependencyPostgresStorageProvider.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015-2023 The OpenZipkin Authors
+ *
+ * Licensed 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 zipkin.server.dependency.storage.jdbc.postgres;
+
+import zipkin.server.dependency.storage.jdbc.common.ZipkinDependencyJDBCStorageProvider;
+
+public class ZipkinDependencyPostgresStorageProvider extends ZipkinDependencyJDBCStorageProvider {
+ @Override
+ public String name() {
+ return "postgresql";
+ }
+}
diff --git a/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100644
index 00000000000..60a365293b3
--- /dev/null
+++ b/zipkin-server/zipkin-storage-ext/zipkin-dependency-storage-jdbc/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+#
+
+zipkin.server.dependency.storage.jdbc.h2.ZipkinDependencyH2StorageProvider
+zipkin.server.dependency.storage.jdbc.mysql.ZipkinDependencyMySQLStorageProvider
+zipkin.server.dependency.storage.jdbc.postgres.ZipkinDependencyPostgresStorageProvider
\ No newline at end of file