File size: 2,926 Bytes
e36aeda
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !plan9 && !windows

package main

// Regression test for https://go.dev/issue/72870. Go code called from C should
// never be reported as external code.

/*
#include <pthread.h>

void go_callback1();
void go_callback2();

static void *callback_pprof_thread(void *arg) {
    go_callback1();
    return 0;
}

static void c_callback(void) {
    go_callback2();
}

static void start_callback_pprof_thread() {
    pthread_t th;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_create(&th, &attr, callback_pprof_thread, 0);
    // Don't join, caller will watch pprof.
}
*/
import "C"

import (
	"bytes"
	"fmt"
	"internal/profile"
	"os"
	"runtime/pprof"
	"time"
)

func init() {
	register("CgoCallbackPprof", CgoCallbackPprof)
}

func CgoCallbackPprof() {
	C.start_callback_pprof_thread()

	var buf bytes.Buffer
	if err := pprof.StartCPUProfile(&buf); err != nil {
		fmt.Printf("Error starting CPU profile: %v\n", err)
		os.Exit(1)
	}
	time.Sleep(1 * time.Second)
	pprof.StopCPUProfile()

	p, err := profile.Parse(&buf)
	if err != nil {
		fmt.Printf("Error parsing profile: %v\n", err)
		os.Exit(1)
	}

	foundCallee := false
	for _, s := range p.Sample {
		funcs := flattenFrames(s)
		if len(funcs) == 0 {
			continue
		}

		leaf := funcs[0]
		if leaf.Name != "main.go_callback1_callee" {
			continue
		}
		foundCallee = true

		if len(funcs) < 2 {
			fmt.Printf("Profile: %s\n", p)
			frames := make([]string, len(funcs))
			for i := range funcs {
				frames[i] = funcs[i].Name
			}
			fmt.Printf("FAIL: main.go_callback1_callee sample missing caller in frames %v\n", frames)
			os.Exit(1)
		}

		if funcs[1].Name != "main.go_callback1" {
			// In https://go.dev/issue/72870, this will be runtime._ExternalCode.
			fmt.Printf("Profile: %s\n", p)
			frames := make([]string, len(funcs))
			for i := range funcs {
				frames[i] = funcs[i].Name
			}
			fmt.Printf("FAIL: main.go_callback1_callee sample caller got %s want main.go_callback1 in frames %v\n", funcs[1].Name, frames)
			os.Exit(1)
		}
	}

	if !foundCallee {
		fmt.Printf("Missing main.go_callback1_callee sample in profile %s\n", p)
		os.Exit(1)
	}

	fmt.Printf("OK\n")
}

// Return the frame functions in s, regardless of inlining.
func flattenFrames(s *profile.Sample) []*profile.Function {
	ret := make([]*profile.Function, 0, len(s.Location))
	for _, loc := range s.Location {
		for _, line := range loc.Line {
			ret = append(ret, line.Function)
		}
	}
	return ret
}

//export go_callback1
func go_callback1() {
	// This is a separate function just to ensure we have another Go
	// function as the caller in the profile.
	go_callback1_callee()
}

func go_callback1_callee() {
	C.c_callback()

	// Spin for CPU samples.
	for {
	}
}

//export go_callback2
func go_callback2() {
}