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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions