Skip to content

Can't decode the stack trace and the metadata while using gRPC API's #5862

@hanshal101

Description

@hanshal101

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions