1// +build linux
2
3package perf
4
5import (
6	"encoding/binary"
7	"fmt"
8	"syscall"
9
10	"go.uber.org/multierr"
11	"golang.org/x/sys/unix"
12)
13
14// ErrNoLeader is returned when a leader of a GroupProfiler is not defined.
15var ErrNoLeader = fmt.Errorf("No leader defined")
16
17// GroupProfileValue is returned from a GroupProfiler.
18type GroupProfileValue struct {
19	Events      uint64
20	TimeEnabled uint64
21	TimeRunning uint64
22	Values      []uint64
23}
24
25// GroupProfiler is used to setup a group profiler.
26type GroupProfiler interface {
27	Start() error
28	Reset() error
29	Stop() error
30	Close() error
31	Profile() (*GroupProfileValue, error)
32}
33
34// groupProfiler implements the GroupProfiler interface.
35type groupProfiler struct {
36	fds []int // leader is always element 0
37}
38
39// NewGroupProfiler returns a GroupProfiler.
40func NewGroupProfiler(pid, cpu, opts int, eventAttrs ...unix.PerfEventAttr) (GroupProfiler, error) {
41	fds := make([]int, len(eventAttrs))
42
43	for i, eventAttr := range eventAttrs {
44		// common configs
45		eventAttr.Size = EventAttrSize
46		eventAttr.Sample_type = PERF_SAMPLE_IDENTIFIER
47
48		// Leader fd must be opened first
49		if i == 0 {
50			// leader specific configs
51			eventAttr.Bits = unix.PerfBitDisabled | unix.PerfBitExcludeHv
52			eventAttr.Read_format = unix.PERF_FORMAT_TOTAL_TIME_RUNNING | unix.PERF_FORMAT_TOTAL_TIME_ENABLED | unix.PERF_FORMAT_GROUP
53
54			fd, err := unix.PerfEventOpen(
55				&eventAttr,
56				pid,
57				cpu,
58				-1,
59				opts,
60			)
61			if err != nil {
62				return nil, err
63			}
64			fds[i] = fd
65			continue
66		}
67
68		// non leader configs
69		eventAttr.Read_format = unix.PERF_FORMAT_TOTAL_TIME_RUNNING | unix.PERF_FORMAT_TOTAL_TIME_ENABLED | unix.PERF_FORMAT_GROUP
70		eventAttr.Bits = unix.PerfBitExcludeHv
71
72		fd, err := unix.PerfEventOpen(
73			&eventAttr,
74			pid,
75			cpu,
76			fds[0],
77			opts,
78		)
79		if err != nil {
80			// cleanup any old Fds
81			for ii, fd2 := range fds {
82				if ii == i {
83					break
84				}
85				err = multierr.Append(err, unix.Close(fd2))
86			}
87			return nil, err
88		}
89		fds[i] = fd
90	}
91
92	return &groupProfiler{
93		fds: fds,
94	}, nil
95}
96
97// Start is used to start the GroupProfiler.
98func (p *groupProfiler) Start() error {
99	if len(p.fds) == 0 {
100		return ErrNoLeader
101	}
102	return unix.IoctlSetInt(p.fds[0], unix.PERF_EVENT_IOC_ENABLE, unix.PERF_IOC_FLAG_GROUP)
103}
104
105// Reset is used to reset the GroupProfiler.
106func (p *groupProfiler) Reset() error {
107	if len(p.fds) == 0 {
108		return ErrNoLeader
109	}
110	return unix.IoctlSetInt(p.fds[0], unix.PERF_EVENT_IOC_RESET, unix.PERF_IOC_FLAG_GROUP)
111}
112
113// Stop is used to stop the GroupProfiler.
114func (p *groupProfiler) Stop() error {
115	if len(p.fds) == 0 {
116		return ErrNoLeader
117	}
118	return unix.IoctlSetInt(p.fds[0], unix.PERF_EVENT_IOC_DISABLE, unix.PERF_IOC_FLAG_GROUP)
119}
120
121// Close is used to close the GroupProfiler.
122func (p *groupProfiler) Close() error {
123	var err error
124	for _, fd := range p.fds {
125		err = multierr.Append(err, unix.Close(fd))
126	}
127	return err
128}
129
130// Profile is used to return the GroupProfileValue of the GroupProfiler.
131func (p *groupProfiler) Profile() (*GroupProfileValue, error) {
132	nEvents := len(p.fds)
133	if nEvents == 0 {
134		return nil, ErrNoLeader
135	}
136
137	// read format of the raw event looks like this:
138	/*
139		     struct read_format {
140			 u64 nr;            // The number of events /
141			 u64 time_enabled;  // if PERF_FORMAT_TOTAL_TIME_ENABLED
142			 u64 time_running;  // if PERF_FORMAT_TOTAL_TIME_RUNNING
143			 struct {
144			     u64 value;     // The value of the event
145			     u64 id;        // if PERF_FORMAT_ID
146			 } values[nr];
147		     };
148	*/
149
150	buf := make([]byte, 24+8*nEvents)
151	_, err := syscall.Read(p.fds[0], buf)
152	if err != nil {
153		return nil, err
154	}
155
156	val := &GroupProfileValue{
157		Events:      binary.LittleEndian.Uint64(buf[0:8]),
158		TimeEnabled: binary.LittleEndian.Uint64(buf[8:16]),
159		TimeRunning: binary.LittleEndian.Uint64(buf[16:24]),
160		Values:      make([]uint64, len(p.fds)),
161	}
162
163	offset := 24
164	for i := range p.fds {
165		val.Values[i] = binary.LittleEndian.Uint64(buf[offset : offset+8])
166		offset += 8
167	}
168
169	return val, nil
170}
171