Skip to content
Merged
Show file tree
Hide file tree
Changes from 72 commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
46ed258
Update apm-network proto submodule manually
cqhasy Sep 21, 2025
c556d8d
feat(add go profile data collect、prase、store)
cqhasy Sep 24, 2025
5f01462
feat(add go analyzer)
cqhasy Sep 25, 2025
332747d
fix(remove the start label)
cqhasy Sep 26, 2025
ac584f9
Merge branch 'master' into master
cqhasy Sep 26, 2025
e285f20
docs:(update the changes.md)
cqhasy Oct 17, 2025
b15c41c
feat(add go profile e2e test)
cqhasy Oct 23, 2025
1593082
test ci
cqhasy Oct 23, 2025
0eb4f7e
fix(delete wrong java expeected)
cqhasy Oct 23, 2025
50f2d34
feat(test:go;ci)add go profile test,add to ci
cqhasy Oct 24, 2025
1a1f882
feat ci error
cqhasy Oct 24, 2025
ee3e750
feat ci error
cqhasy Oct 24, 2025
c84db28
feat ci error
cqhasy Oct 24, 2025
bcdc2cc
feat ci error
cqhasy Oct 24, 2025
cc2b3cc
feat ci error
cqhasy Oct 24, 2025
07eef41
feat ci error
cqhasy Oct 24, 2025
ea46b39
feat ci error
cqhasy Oct 24, 2025
3c72f4d
feat ci error
cqhasy Oct 25, 2025
c4f9e1f
feat ci error
cqhasy Oct 25, 2025
412e752
feat ci error
cqhasy Oct 25, 2025
3add337
feat ci error
cqhasy Oct 25, 2025
3044195
merge pprof
cqhasy Oct 25, 2025
b0d71f8
update query-protocol
cqhasy Oct 25, 2025
120860e
update query-protocol
cqhasy Oct 25, 2025
882ae0d
add some log for go profile e2e
cqhasy Oct 25, 2025
d4136ab
add some log for go profile e2e
cqhasy Oct 25, 2025
49e0c76
fix ci
cqhasy Oct 25, 2025
ec1d993
fix ci
cqhasy Oct 25, 2025
71e059e
fix ci
cqhasy Oct 25, 2025
34656a8
fix ci
cqhasy Oct 25, 2025
f98ebc7
fix ci
cqhasy Oct 26, 2025
ce666ce
fix ci
cqhasy Oct 26, 2025
1098d3a
fix ci
cqhasy Oct 26, 2025
6c1ab7b
fix ci
cqhasy Oct 26, 2025
a28d2aa
fix ci
cqhasy Oct 26, 2025
fa82fc1
fix ci
cqhasy Oct 26, 2025
e81831e
fix ci
cqhasy Oct 26, 2025
e95dd79
fix ci
cqhasy Oct 26, 2025
1f47a62
fix ci
cqhasy Oct 26, 2025
72bf450
fix ci
cqhasy Oct 26, 2025
2963949
fix ci
cqhasy Oct 26, 2025
bb5b290
fix ci
cqhasy Oct 26, 2025
de1717f
fix ci
cqhasy Oct 26, 2025
625226f
fix ci
cqhasy Oct 26, 2025
f3322d1
fix ci
cqhasy Oct 26, 2025
1e5ceae
fix ci
cqhasy Oct 26, 2025
176d64f
fix ci
cqhasy Oct 26, 2025
9ba032f
fix ci
cqhasy Oct 26, 2025
600e083
fix ci
cqhasy Oct 26, 2025
f681970
fix ci
cqhasy Oct 26, 2025
256cf08
fix ci
cqhasy Oct 26, 2025
ba0fd1e
fix ci
cqhasy Oct 27, 2025
fbf76d2
fix ci
cqhasy Oct 27, 2025
3044b47
fix ci
cqhasy Oct 27, 2025
fa94f1d
test ci
cqhasy Oct 27, 2025
998ccdb
Set skywalking-ui to commit 51817f32de0cd5c4fc78f9c25399316f23d782e8
cqhasy Oct 27, 2025
16f1f72
test ci
cqhasy Oct 27, 2025
3a0b246
roll back ci
cqhasy Oct 27, 2025
7cb024a
fix(move go profile to pofiling)
cqhasy Oct 27, 2025
959479e
fix(move go profile to pofiling)
cqhasy Oct 27, 2025
9eac3b9
fix(move go profile to pofiling)
cqhasy Oct 27, 2025
d73595d
fix(move go profile to pofiling)
cqhasy Oct 27, 2025
cee4873
fix(move go profile to pofiling)
cqhasy Oct 28, 2025
eee3c65
fix(roll back ui)
cqhasy Oct 28, 2025
f0a9af4
fix ci
cqhasy Oct 28, 2025
a60c033
fix ci
cqhasy Oct 28, 2025
c471434
fix c
cqhasy Oct 28, 2025
49823d7
roll back ci
cqhasy Oct 28, 2025
50e7a3e
Merge branch 'master' into master
mrproliu Oct 28, 2025
82a490b
fix(use library pprof,roll back proto version);docs(update changes.md)
cqhasy Oct 29, 2025
46654e9
Merge branch 'master' of github.com:cqhasy/skywalking
cqhasy Oct 29, 2025
7537d87
refactor: extract method signature lookup into a separate function
cqhasy Oct 29, 2025
55ac28e
fix(analyze java first then go in profileAnalyzer )
cqhasy Oct 29, 2025
284d402
Merge branch 'master' into master
mrproliu Oct 29, 2025
b347616
refactor(separate language type a class)
cqhasy Oct 30, 2025
81681b4
Merge branch 'master' of github.com:cqhasy/skywalking
cqhasy Oct 30, 2025
8f3e9fd
roll submodule proto to d4da569
cqhasy Oct 30, 2025
dfaaac5
fix(delete no use build,change language_type store method)
cqhasy Oct 30, 2025
bd122b5
test ci
cqhasy Oct 30, 2025
f99831b
fix ci
cqhasy Oct 30, 2025
278fa18
fix ci
cqhasy Oct 30, 2025
eaaa361
fix ci
cqhasy Oct 30, 2025
5ba1a8e
fix ci
cqhasy Oct 31, 2025
0d6bcc7
fix ci
cqhasy Oct 31, 2025
dea8217
fix ci
cqhasy Oct 31, 2025
2f89908
role back ci
cqhasy Oct 31, 2025
dcf2796
fix(delete no use variable,roll back kong)
cqhasy Oct 31, 2025
582814a
fix(align field annotation and adjust log level in ProfileAnalyzer)
cqhasy Oct 31, 2025
5717d41
add NoIndexing for language
cqhasy Oct 31, 2025
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
5 changes: 4 additions & 1 deletion .github/workflows/skywalking.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,9 @@ jobs:
config: test/e2e-v2/cases/profiling/trace/opensearch/e2e.yaml
env: OPENSEARCH_VERSION=2.4.0

- name: Go Trace Profiling
config: test/e2e-v2/cases/profiling/trace/go/e2e.yaml

- name: eBPF Profiling On CPU BanyanDB
config: test/e2e-v2/cases/profiling/ebpf/oncpu/banyandb/e2e.yaml
docker:
Expand Down Expand Up @@ -1118,4 +1121,4 @@ jobs:
[[ ${e2eJavaVersionResults} == 'success' ]] || [[ ${execute} != 'true' && ${e2eJavaVersionResults} == 'skipped' ]] || exit -7;
[[ ${timeConsumingITResults} == 'success' ]] || [[ ${execute} != 'true' && ${timeConsumingITResults} == 'skipped' ]] || exit -8;

exit 0;
exit 0;
4 changes: 3 additions & 1 deletion docs/en/changes/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@
* Update Grafana dashboards for OAP observability.
* BanyanDB: fix query `getInstance` by instance ID.
* Support the go agent(0.7.0 release) bundled pprof profiling feature.

* Library-pprof-parser: feat: add PprofSegmentParser.
* Storage: feat: add languageType column to ProfileThreadSnapshotRecord.
* Feat: add go profile analyzer

#### UI

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class ProfileThreadSnapshotRecord extends Record {
public static final String DUMP_TIME = "dump_time";
public static final String SEQUENCE = "sequence";
public static final String STACK_BINARY = "stack_binary";
public static final String LANGUAGE_TYPE = "language_type";

@Column(name = TASK_ID)
@SQLDatabase.CompositeIndex(withColumns = {SEGMENT_ID})
Expand All @@ -69,6 +70,42 @@ public class ProfileThreadSnapshotRecord extends Record {
private int sequence;
@Column(name = STACK_BINARY)
private byte[] stackBinary;
@ElasticSearch.EnableDocValues
@Column(name = LANGUAGE_TYPE)
@BanyanDB.NoIndexing
private int languageType; // store as 0/1 for storage compatibility
Copy link
Member

Choose a reason for hiding this comment

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

This is not a good way to implement. Please refer to ServiceTraffic#layer

Copy link
Contributor

Choose a reason for hiding this comment

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

@cqhasy please fix.


public enum Language {
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 not use a public non-static enum. Please move this to a separate class.

JAVA(0),
GO(1);

private final int value;

Language(int value) {
this.value = value;
}

public int getValue() {
return value;
}

public static Language fromValue(int value) {
for (Language language : values()) {
if (language.value == value) {
return language;
}
}
return JAVA; // default to Java
}
}

public Language getLanguage() {
return Language.fromValue(languageType);
}

public void setLanguage(final Language language) {
this.languageType = language.getValue();
}

@Override
public StorageID id() {
Expand All @@ -88,6 +125,8 @@ public ProfileThreadSnapshotRecord storage2Entity(final Convert2Entity converter
snapshot.setSequence(((Number) converter.get(SEQUENCE)).intValue());
snapshot.setTimeBucket(((Number) converter.get(TIME_BUCKET)).intValue());
snapshot.setStackBinary(converter.getBytes(STACK_BINARY));
final Number languageTypeNum = (Number) converter.get(LANGUAGE_TYPE);
snapshot.setLanguage(Language.fromValue(languageTypeNum != null ? languageTypeNum.intValue() : 0));
return snapshot;
}

Expand All @@ -99,6 +138,7 @@ public void entity2Storage(final ProfileThreadSnapshotRecord storageData, final
converter.accept(SEQUENCE, storageData.getSequence());
converter.accept(TIME_BUCKET, storageData.getTimeBucket());
converter.accept(STACK_BINARY, storageData.getStackBinary());
converter.accept(LANGUAGE_TYPE, storageData.getLanguage().getValue());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* 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.core.profiling.trace.analyze;

import com.google.perftools.profiles.ProfileProto;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.skywalking.oap.server.core.profiling.trace.ProfileThreadSnapshotRecord;
import org.apache.skywalking.oap.server.core.query.input.SegmentProfileAnalyzeQuery;
import org.apache.skywalking.oap.server.core.query.type.ProfileAnalyzation;
import org.apache.skywalking.oap.server.core.query.type.ProfileStackElement;
import org.apache.skywalking.oap.server.core.query.type.ProfileStackTree;
import org.apache.skywalking.oap.server.library.pprof.parser.PprofSegmentParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Analyzer for Go pprof samples. Builds a stack tree with total/self durations using sampling period.
* This works independently from ThreadSnapshot, for Go profiles only.
*/
public class GoProfileAnalyzer {
private static final Logger LOGGER = LoggerFactory.getLogger(GoProfileAnalyzer.class);

/**
* Analyze a pprof profile for a specific segment and time window.
*/
public ProfileAnalyzation analyze(final String segmentId,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this already done in the proof library?

final long startTimeInclusive,
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

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

The parameter 'startTimeInclusive' is never used.

Suggested change
final long startTimeInclusive,

Copilot uses AI. Check for mistakes.
final long endTimeInclusive,
Copy link

Copilot AI Oct 30, 2025

Choose a reason for hiding this comment

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

The parameter 'endTimeInclusive' is never used.

Suggested change
final long endTimeInclusive,

Copilot uses AI. Check for mistakes.
final ProfileProto.Profile profile) {
final long periodMs = PprofSegmentParser.resolvePeriodMillis(profile);

// Build ProfileStackElement directly (reuse FrameTreeBuilder's mergeSample logic)
Map<String, Integer> key2Id = new HashMap<>(); // "parentId|name" -> id
List<ProfileStackElement> elements = new ArrayList<>();

// Strict per-segment filtering
final List<String> stringTable = profile.getStringTableList();

for (ProfileProto.Sample sample : profile.getSampleList()) {
final String seg = PprofSegmentParser.extractSegmentIdFromLabels(sample.getLabelList(), stringTable);
if (seg == null || !seg.equals(segmentId)) {
continue;
}
long sampleCount = sample.getValueCount() > 0 ? sample.getValue(0) : 1L;
long weightMs = sampleCount * periodMs;

// Build function stack then ensure root->leaf order for aggregation
List<String> stack = PprofSegmentParser.extractStackFromSample(sample, profile);
Collections.reverse(stack);

// Aggregate along path (similar to FrameTreeBuilder.mergeSample)
int parentId = -1; // root
for (String fn : stack) {
String key = parentId + "|" + fn;
Integer nodeId = key2Id.get(key);

if (nodeId == null) {
ProfileStackElement element = new ProfileStackElement();
element.setId(elements.size());
element.setParentId(parentId);
element.setCodeSignature(fn);
element.setDuration(0);
element.setDurationChildExcluded(0);
element.setCount(0);
elements.add(element);
nodeId = element.getId();
key2Id.put(key, nodeId);
}

ProfileStackElement element = elements.get(nodeId);
element.setDuration(element.getDuration() + (int) weightMs);
element.setCount(element.getCount() + (int) sampleCount);

parentId = nodeId;
}
}

// Calculate self = total - sum(children) (reuse FrameTreeBuilder pattern)
for (int i = elements.size() - 1; i >= 0; i--) {
ProfileStackElement elem = elements.get(i);
long childrenSum = 0;
for (ProfileStackElement other : elements) {
if (other.getParentId() == elem.getId()) {
childrenSum += other.getDuration();
}
}
elem.setDurationChildExcluded(Math.max(0, elem.getDuration() - (int) childrenSum));
}

ProfileStackTree tree = new ProfileStackTree();
tree.setElements(elements);

ProfileAnalyzation result = new ProfileAnalyzation();
result.getTrees().add(tree);
return result;
}

/**
* Analyze multiple Go profile records and return combined results
*/
public ProfileAnalyzation analyzeRecords(List<ProfileThreadSnapshotRecord> records, List<SegmentProfileAnalyzeQuery> queries) {
ProfileAnalyzation result = new ProfileAnalyzation();

// Build query map for O(1) lookup
Map<String, SegmentProfileAnalyzeQuery> queryMap = queries.stream()
.collect(Collectors.toMap(SegmentProfileAnalyzeQuery::getSegmentId, q -> q));

for (ProfileThreadSnapshotRecord record : records) {
try {
// Find the corresponding query for this segment
SegmentProfileAnalyzeQuery query = queryMap.get(record.getSegmentId());

if (query == null) {
LOGGER.warn("No query found for Go profile segment: {}", record.getSegmentId());
continue;
}

// Parse pprof data from stackBinary
ProfileProto.Profile profile = ProfileProto.Profile.parseFrom(record.getStackBinary());

// Analyze this record
ProfileAnalyzation recordAnalyzation = analyze(
record.getSegmentId(),
query.getTimeRange().getStart(),
query.getTimeRange().getEnd(),
profile
);

if (recordAnalyzation != null && !recordAnalyzation.getTrees().isEmpty()) {
result.getTrees().addAll(recordAnalyzation.getTrees());

if (LOGGER.isInfoEnabled()) {
LOGGER.info("Go profile analysis completed: segmentId={}, window=[{}-{}], trees={}",
record.getSegmentId(), query.getTimeRange().getStart(), query.getTimeRange().getEnd(),
recordAnalyzation.getTrees().size());
}
}
} catch (Exception e) {
LOGGER.error("Failed to analyze Go profile record: segmentId={}, sequence={}, dumpTime={}",
record.getSegmentId(), record.getSequence(), record.getDumpTime(), e);
}
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,79 @@ public ProfileAnalyzation analyze(final List<SegmentProfileAnalyzeQuery> queries
}

// query snapshots
List<ProfileStack> stacks = sequenceSearch.getRanges().parallelStream().map(r -> {
List<ProfileThreadSnapshotRecord> records = sequenceSearch.getRanges().parallelStream().map(r -> {
try {
return getProfileThreadSnapshotQueryDAO().queryRecords(r.getSegmentId(), r.getMinSequence(), r.getMaxSequence());
} catch (IOException e) {
LOGGER.warn(e.getMessage(), e);
return Collections.<ProfileThreadSnapshotRecord>emptyList();
}
}).flatMap(Collection::stream).map(ProfileStack::deserialize).distinct().collect(Collectors.toList());
}).flatMap(Collection::stream)
.collect(Collectors.toList());

if (LOGGER.isInfoEnabled()) {
final int totalRanges = sequenceSearch.getRanges().size();
LOGGER.info("Profile analyze fetched records, segmentId(s)={}, ranges={}, recordsCount={}",
sequenceSearch.getRanges().stream().map(SequenceRange::getSegmentId).distinct().collect(Collectors.toList()),
totalRanges, records.size());
}

// For Java
List<ProfileThreadSnapshotRecord> javaRecords = records.stream()
.filter(rec -> rec.getLanguage() == ProfileThreadSnapshotRecord.Language.JAVA)
.collect(Collectors.toList());

// For Go
List<ProfileThreadSnapshotRecord> goRecords = new ArrayList<>();
for (SegmentProfileAnalyzeQuery q : queries) {
final String segId = q.getSegmentId();
try {
int minSeq = getProfileThreadSnapshotQueryDAO().queryMinSequence(segId, 0L, Long.MAX_VALUE);
int maxSeqExclusive = getProfileThreadSnapshotQueryDAO().queryMaxSequence(segId, 0L, Long.MAX_VALUE) + 1;
if (maxSeqExclusive > minSeq) {
List<ProfileThreadSnapshotRecord> full = getProfileThreadSnapshotQueryDAO().queryRecords(segId, minSeq, maxSeqExclusive);
for (ProfileThreadSnapshotRecord r : full) {
if (r.getLanguage() == ProfileThreadSnapshotRecord.Language.GO) {
goRecords.add(r);
}
}
}
} catch (IOException e) {
LOGGER.warn("Go full-range fetch failed for segmentId={}", segId, e);
}
}

// analyze
final List<ProfileStackTree> trees = analyzeByStack(stacks);
if (trees != null) {
analyzation.getTrees().addAll(trees);
// Analyze Go profiles
if (!goRecords.isEmpty()) {
LOGGER.info("Analyzing {} Go profile records", goRecords.size());
GoProfileAnalyzer goAnalyzer = new GoProfileAnalyzer();
ProfileAnalyzation goAnalyzation = goAnalyzer.analyzeRecords(goRecords, queries);
if (goAnalyzation != null && !goAnalyzation.getTrees().isEmpty()) {
analyzation.getTrees().addAll(goAnalyzation.getTrees());
}
}

// Analyze Java profiles (original logic)
if (!javaRecords.isEmpty()) {
LOGGER.info("Analyzing {} Java profile records", javaRecords.size());
List<ProfileStack> stacks = javaRecords.stream()
.map(rec -> {
try {
return ProfileStack.deserialize(rec);
} catch (Exception ex) {
LOGGER.warn("Deserialize stack failed, segmentId={}, sequence={}, dumpTime={}",
rec.getSegmentId(), rec.getSequence(), rec.getDumpTime(), ex);
return null;
}
})
.filter(java.util.Objects::nonNull)
.distinct()
.collect(Collectors.toList());

final List<ProfileStackTree> trees = analyzeByStack(stacks);
if (trees != null) {
analyzation.getTrees().addAll(trees);
}
}

return analyzation;
Expand All @@ -115,8 +175,15 @@ protected SequenceSearch getAllSequenceRange(String segmentId, long start, long
int minSequence = getProfileThreadSnapshotQueryDAO().queryMinSequence(segmentId, start, end);
int maxSequence = getProfileThreadSnapshotQueryDAO().queryMaxSequence(segmentId, start, end) + 1;

if (LOGGER.isInfoEnabled()) {
LOGGER.info("Profile analyze sequence window: segmentId={}, start={}, end={}, minSeq={}, maxSeq(exclusive)={}",
segmentId, start, end, minSequence, maxSequence);
}

// data not found
if (maxSequence <= 0) {
LOGGER.info("Profile analyze not found any sequence in window: segmentId={}, start={}, end={}",
segmentId, start, end);
return null;
}

Expand Down Expand Up @@ -207,4 +274,5 @@ public int getMaxSequence() {
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,20 @@ public static FrameTree dumpTree(String filePath) throws IOException {
FrameTree tree = new FrameTreeBuilder(profile).build();
return tree;
}

/**
* Resolve function signature for a given location id. The signature format matches FrameTreeBuilder
* (functionName:line;... when inlined, joined by ';').
*/
public static String resolveSignature(long locationId, ProfileProto.Profile profile) {
if (locationId == 0) {
return "root";
}
ProfileProto.Location location = profile.getLocation((int) locationId - 1);
return location.getLineList().stream().map(line -> {
ProfileProto.Function function = profile.getFunction((int) line.getFunctionId() - 1);
String functionName = profile.getStringTable((int) function.getName());
return functionName + ":" + line.getLine();
}).collect(java.util.stream.Collectors.joining(";"));
}
}
Loading