Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e29c3a9
Support Virtual-GenAI monitoring
peachisai Mar 16, 2026
3642ce3
fix changes
peachisai Mar 16, 2026
37708a2
Merge branch 'master' into Support-GenAI-monitoring
wu-sheng Mar 17, 2026
0de57e7
Merge remote-tracking branch 'origin/prd' into Support-GenAI-monitoring
peachisai Mar 18, 2026
45d255f
Merge branch 'master' into Support-GenAI-monitoring
wu-sheng Mar 19, 2026
f15e579
Merge branch 'master' into Support-GenAI-monitoring
wu-sheng Mar 20, 2026
d2c2165
fix some issues
peachisai Mar 20, 2026
f417ea5
Merge branches 'Support-GenAI-monitoring' and 'Support-GenAI-monitori…
peachisai Mar 20, 2026
4f1ea70
Merge branch 'master' into Support-GenAI-monitoring
wu-sheng Mar 20, 2026
ca9704e
fix
peachisai Mar 20, 2026
fab132c
Merge branch 'Support-GenAI-monitoring' of github.com:peachisai/skywa…
peachisai Mar 20, 2026
38af222
Merge branch 'master' into Support-GenAI-monitoring
peachisai Mar 22, 2026
8e915bd
fix some suggestions
peachisai Mar 22, 2026
30da5b4
fix some suggestions
peachisai Mar 23, 2026
3a0af32
Merge branch 'master' into Support-GenAI-monitoring
wu-sheng Mar 23, 2026
25394b0
fix some suggestions and add some default model pricing
peachisai Mar 23, 2026
68476df
Merge branch 'Support-GenAI-monitoring' of github.com:peachisai/skywa…
peachisai Mar 23, 2026
549e8e4
Merge branch 'master' into Support-GenAI-monitoring
wu-sheng Mar 23, 2026
ea7e330
fix
peachisai Mar 24, 2026
1f5d5ac
fix
peachisai Mar 24, 2026
227fef4
fix
peachisai Mar 24, 2026
a620889
fix
peachisai Mar 24, 2026
4679f01
Merge pull request #80 from peachisai/uat
peachisai Mar 24, 2026
3b36dc8
fix
peachisai Mar 25, 2026
0e5ce6b
fix
peachisai Mar 25, 2026
1627edf
Merge pull request #81 from peachisai/uat
peachisai Mar 25, 2026
28c606a
Add GenAI section to menu.yml
wu-sheng Mar 25, 2026
9cbbf12
Add GenAI observability section to menu.yaml
wu-sheng Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/skywalking.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,8 @@ jobs:
config: test/e2e-v2/cases/zipkin/kafka/e2e.yaml
- name: Zipkin BanyanDB
config: test/e2e-v2/cases/zipkin/banyandb/e2e.yaml
- name: Virtual GenAI
config: test/e2e-v2/cases/virtual-genai/e2e.yaml

- name: Nginx
config: test/e2e-v2/cases/nginx/e2e.yaml
Expand Down
1 change: 1 addition & 0 deletions apm-dist/src/main/assembly/binary.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
<include>log-mal-rules/**</include>
<include>telegraf-rules/*</include>
<include>cilium-rules/*</include>
<include>gen-ai-config.yml</include>
</includes>
<outputDirectory>config</outputDirectory>
</fileSet>
Expand Down
1 change: 1 addition & 0 deletions docs/en/changes/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
* Update hierarchy rule documentation: `auto-matching-rules` in `hierarchy-definition.yml` no longer use Groovy scripts. Rules now use a dedicated expression grammar supporting property access, String methods, if/else, comparisons, and logical operators. All shipped rules are fully compatible.
* Activate `otlp-traces` handler in `receiver-otel` by default.
* Update Istio E2E test versions: remove EOL 1.20.0, add 1.25.0–1.29.0 for ALS/Metrics/Ambient tests. Update Rover with Istio Process test from 1.15.0 to 1.28.0 with Kubernetes 1.28.
* Support Virtual-GenAI monitoring.

#### UI
* Fix the missing icon in new native trace view.
Expand Down
17 changes: 17 additions & 0 deletions docs/en/setup/service-agent/virtual-genai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Virtual GenAI
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to update the demo to point to here. I think from Marketplace/General Service?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image like this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not just this. menu.yml is not updated in the /docs/en


Virtual GenAI represent the Generative AI service nodes detected by [server agents' plugins](server-agents.md). The performance
metrics of the GenAI operations are also from the GenAI client-side perspective.

For example, an Spring-ai plugin in the Java agent could detect the latency of a chat completion request.
As a result, SkyWalking would show traffic, latency, success rate, and token usage (input/output) powered by backend analysis capabilities in this dashboard.

The GenAI operation span should have
- It is an **Exit** span
- **Span's layer == GENAI**
- Tag key = `gen_ai.provider.name`, value = The Generative AI provider, e.g. openai, anthropic, ollama
- Tag key = `gen_ai.response.model`, value = The name of the GenAI model a response is being made to, e.g. gpt-4o, claude-3-5-sonnet
- Tag key = `gen_ai.usage.input_tokens`, value = The number of tokens used in the GenAI input (prompt)
- Tag key = `gen_ai.usage.output_tokens`, value = The number of tokens used in the GenAI response (completion)
- Tag key = `gen_ai.server.time_to_first_token`, value = The duration in milliseconds from the start of the request until the first token is received. Note: This metric is only available for streaming requests.
- If the GenAI service is a remote API (e.g. OpenAI), the span's peer would be the network address (IP or domain) of the GenAI server.
4 changes: 4 additions & 0 deletions docs/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ catalog:
path: "/en/setup/backend/dashboards-so11y-java-agent"
- name: "SkyWalking Go Agent self telemetry"
path: "/en/setup/backend/dashboards-so11y-go-agent"
- name: "GenAI"
catalog:
- name: "Virtual Genai"
path: "/en/setup/service-agent/virtual-genai"
- name: "Configuration Vocabulary"
path: "/en/setup/backend/configuration-vocabulary"
- name: "Advanced Setup"
Expand Down
5 changes: 5 additions & 0 deletions oap-server/analyzer/agent-analyzer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<artifactId>meter-analyzer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>gen-ai-analyzer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>server-testing</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@

import java.util.Arrays;
import java.util.List;

import lombok.RequiredArgsConstructor;
import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
import org.apache.skywalking.oap.analyzer.genai.module.GenAIAnalyzerModule;
import org.apache.skywalking.oap.analyzer.genai.service.IGenAIMeterAnalyzerService;
import org.apache.skywalking.oap.server.analyzer.provider.AnalyzerModuleConfig;
import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice.VirtualCacheProcessor;
import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice.VirtualDatabaseProcessor;
import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice.VirtualGenAIProcessor;
import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice.VirtualMQProcessor;
import org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice.VirtualServiceProcessor;
import org.apache.skywalking.oap.server.core.CoreModule;
Expand Down Expand Up @@ -71,23 +75,28 @@ public void parseEntry(final SpanObject span, final SegmentObject segmentObject)
public static class Factory implements AnalysisListenerFactory {
private final SourceReceiver sourceReceiver;
private final NamingControl namingControl;
private final IGenAIMeterAnalyzerService genAIMeterAnalyzerService;

public Factory(ModuleManager moduleManager) {
this.sourceReceiver = moduleManager.find(CoreModule.NAME).provider().getService(SourceReceiver.class);
this.namingControl = moduleManager.find(CoreModule.NAME)
.provider()
.getService(NamingControl.class);
this.genAIMeterAnalyzerService = moduleManager.find(GenAIAnalyzerModule.NAME)
.provider()
.getService(IGenAIMeterAnalyzerService.class);
}

@Override
public AnalysisListener create(ModuleManager moduleManager, AnalyzerModuleConfig config) {
return new VirtualServiceAnalysisListener(
sourceReceiver,
Arrays.asList(
new VirtualCacheProcessor(namingControl, config),
new VirtualDatabaseProcessor(namingControl, config),
new VirtualMQProcessor(namingControl)
)
sourceReceiver,
Arrays.asList(
new VirtualCacheProcessor(namingControl, config),
new VirtualDatabaseProcessor(namingControl, config),
new VirtualMQProcessor(namingControl),
new VirtualGenAIProcessor(namingControl, genAIMeterAnalyzerService)
)
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.skywalking.oap.server.analyzer.provider.trace.parser.listener.vservice;

import lombok.RequiredArgsConstructor;
import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject;
import org.apache.skywalking.apm.network.language.agent.v3.SpanLayer;
import org.apache.skywalking.apm.network.language.agent.v3.SpanObject;
import org.apache.skywalking.oap.analyzer.genai.service.IGenAIMeterAnalyzerService;
import org.apache.skywalking.oap.server.core.analysis.Layer;
import org.apache.skywalking.oap.server.core.config.NamingControl;
import org.apache.skywalking.oap.server.core.source.GenAIMetrics;
import org.apache.skywalking.oap.server.core.source.GenAIModelAccess;
import org.apache.skywalking.oap.server.core.source.GenAIProviderAccess;
import org.apache.skywalking.oap.server.core.source.ServiceMeta;
import org.apache.skywalking.oap.server.core.source.Source;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

@RequiredArgsConstructor
public class VirtualGenAIProcessor implements VirtualServiceProcessor {

private final NamingControl namingControl;

private final IGenAIMeterAnalyzerService meterAnalyzerService;

private final List<Source> recordList = new ArrayList<>();

@Override
public void prepareVSIfNecessary(SpanObject span, SegmentObject segmentObject) {
if (span.getSpanLayer() != SpanLayer.GenAI) {
return;
}

GenAIMetrics metrics = meterAnalyzerService.extractMetricsFromSWSpan(span, segmentObject);
if (metrics == null) {
return;
}

recordList.add(toServiceMeta(metrics));
recordList.add(toProviderAccess(metrics));
recordList.add(toModelAccess(metrics));
}

private ServiceMeta toServiceMeta(GenAIMetrics metrics) {
ServiceMeta service = new ServiceMeta();
service.setName(metrics.getProviderName());
service.setLayer(Layer.VIRTUAL_GENAI);
service.setTimeBucket(metrics.getTimeBucket());
return service;
}

private GenAIProviderAccess toProviderAccess(GenAIMetrics metrics) {
GenAIProviderAccess source = new GenAIProviderAccess();
source.setName(namingControl.formatServiceName(metrics.getProviderName()));
source.setInputTokens(metrics.getInputTokens());
source.setOutputTokens(metrics.getOutputTokens());
source.setTotalCost(metrics.getTotalCost());
source.setLatency(metrics.getLatency());
source.setStatus(metrics.isStatus());
source.setTimeBucket(metrics.getTimeBucket());
return source;
}

private GenAIModelAccess toModelAccess(GenAIMetrics metrics) {
GenAIModelAccess source = new GenAIModelAccess();
source.setServiceName(namingControl.formatServiceName(metrics.getProviderName()));
source.setModelName(namingControl.formatInstanceName(metrics.getModelName()));
source.setInputTokens(metrics.getInputTokens());
source.setOutputTokens(metrics.getOutputTokens());
source.setTotalCost(metrics.getTotalCost());
source.setTimeToFirstToken(metrics.getTimeToFirstToken());
source.setLatency(metrics.getLatency());
source.setStatus(metrics.isStatus());
source.setTimeBucket(metrics.getTimeBucket());
return source;
}

@Override
public void emitTo(Consumer<Source> consumer) {
for (Source source : recordList) {
if (source != null) {
consumer.accept(source);
}
}
}
}
37 changes: 37 additions & 0 deletions oap-server/analyzer/gen-ai-analyzer/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>analyzer</artifactId>
<groupId>org.apache.skywalking</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>gen-ai-analyzer</artifactId>

<dependencies>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>server-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.apache.skywalking.oap.analyzer.genai;

import org.apache.skywalking.oap.analyzer.genai.config.GenAIConfig;
import org.apache.skywalking.oap.analyzer.genai.config.GenAIConfigLoader;
import org.apache.skywalking.oap.analyzer.genai.config.GenAIOALDefine;
import org.apache.skywalking.oap.analyzer.genai.matcher.GenAIProviderPrefixMatcher;
import org.apache.skywalking.oap.analyzer.genai.module.GenAIAnalyzerModule;
import org.apache.skywalking.oap.analyzer.genai.service.GenAIMeterAnalyzer;
import org.apache.skywalking.oap.analyzer.genai.service.IGenAIMeterAnalyzerService;
import org.apache.skywalking.oap.server.core.CoreModule;
import org.apache.skywalking.oap.server.core.oal.rt.OALEngineLoaderService;
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;

public class GenAIAnalyzerModuleProvider extends ModuleProvider {

private GenAIConfig config;

@Override
public String name() {
return "default";
}

@Override
public Class<? extends ModuleDefine> module() {
return GenAIAnalyzerModule.class;
}

@Override
public ConfigCreator<? extends ModuleConfig> newConfigCreator() {
return new ConfigCreator<GenAIConfig>() {
@Override
public Class<GenAIConfig> type() {
return GenAIConfig.class;
}

@Override
public void onInitialized(final GenAIConfig initialized) {
config = initialized;
}
};
}

@Override
public void prepare() throws ServiceNotProvidedException, ModuleStartException {
GenAIConfigLoader loader = new GenAIConfigLoader(config);
config = loader.loadConfig();
GenAIProviderPrefixMatcher matcher = GenAIProviderPrefixMatcher.build(config);
this.registerServiceImplementation(
IGenAIMeterAnalyzerService.class,
new GenAIMeterAnalyzer(matcher)
);
}

@Override
public void start() throws ServiceNotProvidedException, ModuleStartException {
getManager().find(CoreModule.NAME)
.provider()
.getService(OALEngineLoaderService.class)
.load(GenAIOALDefine.INSTANCE);
}

@Override
public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {

}

@Override
public String[] requiredModules() {
return new String[] {
CoreModule.NAME
};
}
}
Loading