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