-
Notifications
You must be signed in to change notification settings - Fork 241
Open
Description
Description
I was recently trying out the gRPC API's provided by the Parca-server to Query the profiling data. https://buf.build/parca-dev/parca
I faced a problem on decoding the data received.
{
"query_time": "2025-08-03T08:34:49Z",
"profile_type": "parca_agent:samples:count:cpu:nanoseconds:delta",
"executable_name": "zed-editor",
"total_samples": 101526313962,
"filtered_samples": 0,
"stacks": [
{
"stack": [
{
"function": ""
},
{
"function": ""
},
],
"flat": 0,
"cumulative": 52631578
},
ans sometimes shows
{
"query_time": "2025-08-03T09:37:18Z",
"profile_type": "parca_agent:samples:count:cpu:nanoseconds:delta",
"executable_name": "kubelet",
"total_samples": 43736841318,
"filtered_samples": 0,
"stacks": []
}
I need some help in decoding the data I tried decoding it similarly on how the parca-server
does it. In the pkg/profile/decode
directory
Code Snippet
// captureOnce makes a QueryService.Query call for one time-point.
// It uses FlamegraphTable to get stack trees and flattens them.
func captureOnce(
ctx context.Context,
client querypb.QueryServiceClient,
now time.Time,
fullType string,
) (*OutputJSON, error) {
ts := now.Add(-15 * time.Second)
// q := fullType + `{}`
req := &querypb.QueryRequest{
Mode: querypb.QueryRequest_MODE_SINGLE_UNSPECIFIED,
ReportType: querypb.QueryRequest_REPORT_TYPE_FLAMEGRAPH_TABLE, // ← CHANGE THIS
Options: &querypb.QueryRequest_Single{Single: &querypb.SingleProfile{
Time: timestamppb.New(ts),
Query: fullType + `{}`,
}},
}
resp, err := client.Query(ctx, req)
if err != nil {
if st, ok := status.FromError(err); ok && st.Code() == codes.NotFound {
log.Printf("no single‑time profile at %s; falling back to range query", ts.Format(time.RFC3339))
return tryQueryRange(ctx, client, now, fullType)
}
return nil, fmt.Errorf("single query failed: %w", err)
}
return extractFromFlamegraph(resp, ts, fullType), nil
}
func extractFromFlamegraph(resp *querypb.QueryResponse, ts time.Time, fullType string) *OutputJSON {
fg := resp.GetFlamegraph()
log.Printf("fg.Root.Children length: %d", len(fg.Root.Children))
log.Printf("Total: %d, Filtered: %d", resp.Total, resp.Filtered)
log.Printf("Report type: %T", resp.GetReport())
if fg == nil {
log.Printf("unexpected report from Query: %T", resp.GetReport())
return nil
}
out := &OutputJSON{
QueryTime: ts,
ProfileType: fullType,
Total: resp.Total,
Filtered: resp.Filtered,
Stacks: make([]StackJSON, 0),
}
for _, root := range fg.Root.Children {
walkStack(root, nil, &out.Stacks)
}
return out
}
func nonEmptyOrAddr(fn *querypb.FlamegraphNodeMeta, loc *querypb.FlamegraphNodeMeta) string {
if fn != nil {
if s := fn.Function.GetName(); s != "" {
return s
}
if sys := fn.Function.GetSystemName(); sys != "" {
return sys
}
}
if loc != nil && loc.Location.GetAddress() != 0 {
return fmt.Sprintf("0x%x", loc.Location.GetAddress())
}
return "<unknown>"
}
func walkStack(
node *querypb.FlamegraphNode,
prefix []FrameJSON,
accum *[]StackJSON,
) {
if node == nil || node.Meta == nil {
return
}
meta := node.Meta
name := nonEmptyOrAddr(meta, meta)
file := ""
buildID := ""
if m := meta.GetMapping(); m != nil {
file = m.GetFile()
buildID = m.GetBuildId()
}
ln := uint32(0)
if lnObj := meta.GetLine(); lnObj != nil {
ln = uint32(lnObj.GetLine())
}
frame := FrameJSON{
Function: name,
File: file,
Line: ln,
BuildID: buildID,
}
newStack := append(prefix, frame)
totalChild := int64(0)
for _, c := range node.Children {
totalChild += c.Cumulative
}
flat := node.Cumulative
if len(node.Children) > 0 {
flat = flat - totalChild
if flat < 0 {
flat = 0
}
}
if len(node.Children) == 0 {
*accum = append(*accum, StackJSON{
Stack: newStack,
Flat: flat,
Cumulative: node.Cumulative,
})
return
}
for _, child := range node.Children {
walkStack(child, newStack, accum)
}
}
Metadata
Metadata
Assignees
Labels
No labels