Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 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
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
63 changes: 63 additions & 0 deletions docs/en/setup/service-agent/virtual-genai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Virtual GenAI
Copy link
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
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
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 represents the Generative AI service nodes detected by [server agents' plugins](server-agents.md). The performance
metrics of the GenAI operations are from the GenAI client-side perspective.

For example, a 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, token usage (input/output), and estimated cost in the GenAI dashboard.

## Span Contract

The GenAI operation span should have the following properties:
- 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, 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 until the first token is received (streaming requests only)
- If the GenAI service is a remote API (e.g. OpenAI), the span's peer should be the network address (IP or domain) of the GenAI server.

## Provider Configuration

SkyWalking uses `gen-ai-config.yml` to map model names to providers and configure cost estimation.

When the `gen_ai.provider.name` tag is present in the span, it is used directly. Otherwise, SkyWalking matches the model name
against `prefix-match` rules to identify the provider. For example, a model name starting with `gpt` is mapped to `openai`.

To configure cost estimation, add `models` with pricing under the provider:


```yaml
providers:
- provider: openai
prefix-match:
- gpt
models:
- name: gpt-4o
input-estimated-cost-per-m: 2.5 # estimated cost per 1,000,000 input tokens
output-estimated-cost-per-m: 10 # estimated cost per 1,000,000 output tokens
```

## Metrics

The following metrics are available at the **provider** (service) level:
- `gen_ai_provider_cpm` - Calls per minute
- `gen_ai_provider_sla` - Success rate
- `gen_ai_provider_resp_time` - Average response time
- `gen_ai_provider_latency_percentile` - Latency percentiles
- `gen_ai_provider_input_tokens_sum / avg` - Input token usage
- `gen_ai_provider_output_tokens_sum / avg` - Output token usage
- `gen_ai_provider_total_estimated_cost / avg_estimated_cost` - Estimated cost

The following metrics are available at the **model** (service instance) level:
- `gen_ai_model_call_cpm` - Calls per minute
- `gen_ai_model_sla` - Success rate
- `gen_ai_model_latency_avg / percentile` - Latency
- `gen_ai_model_ttft_avg / percentile` - Time to first token (streaming only)
- `gen_ai_model_input_tokens_sum / avg` - Input token usage
- `gen_ai_model_output_tokens_sum / avg` - Output token usage
- `gen_ai_model_total_estimated_cost / avg_estimated_cost` - Estimated cost

## Requirement
`SkyWalking Java Agent` version >= 9.7
4 changes: 4 additions & 0 deletions docs/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,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,115 @@
/*
* 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.ServiceInstance;
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));
Copy link
Member

Choose a reason for hiding this comment

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

You should do naming control here, you miseed toService and toInstance, due to recent code changes.

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

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

private Source toInstance(GenAIMetrics metrics) {
ServiceInstance instance = new ServiceInstance();
instance.setTimeBucket(metrics.getTimeBucket());
instance.setName(namingControl.formatInstanceName(metrics.getModelName()));
instance.setServiceLayer(Layer.VIRTUAL_GENAI);
instance.setServiceName(metrics.getProviderName());
return instance;
}

private GenAIProviderAccess toProviderAccess(GenAIMetrics metrics) {
GenAIProviderAccess source = new GenAIProviderAccess();
source.setName(namingControl.formatServiceName(metrics.getProviderName()));
source.setInputTokens(metrics.getInputTokens());
source.setOutputTokens(metrics.getOutputTokens());
source.setTotalEstimatedCost(metrics.getTotalEstimatedCost());
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.setTotalEstimatedCost(metrics.getTotalEstimatedCost());
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>
Loading
Loading