├── README.md ├── LICENSE ├── pprof ├── prune.go ├── filter.go ├── proto.go ├── encode.go ├── profile.go └── legacy_profile.go └── main.go /README.md: -------------------------------------------------------------------------------- 1 | go get [-u] rsc.io/perf2pprof 2 | 3 | https://godoc.org/rsc.io/perf2pprof 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /pprof/prune.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Implements methods to remove frames from profiles. 6 | 7 | package pprof 8 | 9 | import ( 10 | "fmt" 11 | "regexp" 12 | ) 13 | 14 | // Prune removes all nodes beneath a node matching dropRx, and not 15 | // matching keepRx. If the root node of a Sample matches, the sample 16 | // will have an empty stack. 17 | func (p *Profile) Prune(dropRx, keepRx *regexp.Regexp) { 18 | prune := make(map[uint64]bool) 19 | pruneBeneath := make(map[uint64]bool) 20 | 21 | for _, loc := range p.Location { 22 | var i int 23 | for i = len(loc.Line) - 1; i >= 0; i-- { 24 | if fn := loc.Line[i].Function; fn != nil && fn.Name != "" { 25 | funcName := fn.Name 26 | // Account for leading '.' on the PPC ELF v1 ABI. 27 | if funcName[0] == '.' { 28 | funcName = funcName[1:] 29 | } 30 | if dropRx.MatchString(funcName) { 31 | if keepRx == nil || !keepRx.MatchString(funcName) { 32 | break 33 | } 34 | } 35 | } 36 | } 37 | 38 | if i >= 0 { 39 | // Found matching entry to prune. 40 | pruneBeneath[loc.ID] = true 41 | 42 | // Remove the matching location. 43 | if i == len(loc.Line)-1 { 44 | // Matched the top entry: prune the whole location. 45 | prune[loc.ID] = true 46 | } else { 47 | loc.Line = loc.Line[i+1:] 48 | } 49 | } 50 | } 51 | 52 | // Prune locs from each Sample 53 | for _, sample := range p.Sample { 54 | // Scan from the root to the leaves to find the prune location. 55 | // Do not prune frames before the first user frame, to avoid 56 | // pruning everything. 57 | foundUser := false 58 | for i := len(sample.Location) - 1; i >= 0; i-- { 59 | id := sample.Location[i].ID 60 | if !prune[id] && !pruneBeneath[id] { 61 | foundUser = true 62 | continue 63 | } 64 | if !foundUser { 65 | continue 66 | } 67 | if prune[id] { 68 | sample.Location = sample.Location[i+1:] 69 | break 70 | } 71 | if pruneBeneath[id] { 72 | sample.Location = sample.Location[i:] 73 | break 74 | } 75 | } 76 | } 77 | } 78 | 79 | // RemoveUninteresting prunes and elides profiles using built-in 80 | // tables of uninteresting function names. 81 | func (p *Profile) RemoveUninteresting() error { 82 | var keep, drop *regexp.Regexp 83 | var err error 84 | 85 | if p.DropFrames != "" { 86 | if drop, err = regexp.Compile("^(" + p.DropFrames + ")$"); err != nil { 87 | return fmt.Errorf("failed to compile regexp %s: %v", p.DropFrames, err) 88 | } 89 | if p.KeepFrames != "" { 90 | if keep, err = regexp.Compile("^(" + p.KeepFrames + ")$"); err != nil { 91 | return fmt.Errorf("failed to compile regexp %s: %v", p.KeepFrames, err) 92 | } 93 | } 94 | p.Prune(drop, keep) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /pprof/filter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Implements methods to filter samples from profiles. 6 | 7 | package pprof 8 | 9 | import "regexp" 10 | 11 | // FilterSamplesByName filters the samples in a profile and only keeps 12 | // samples where at least one frame matches focus but none match ignore. 13 | // Returns true is the corresponding regexp matched at least one sample. 14 | func (p *Profile) FilterSamplesByName(focus, ignore, hide *regexp.Regexp) (fm, im, hm bool) { 15 | focusOrIgnore := make(map[uint64]bool) 16 | hidden := make(map[uint64]bool) 17 | for _, l := range p.Location { 18 | if ignore != nil && l.matchesName(ignore) { 19 | im = true 20 | focusOrIgnore[l.ID] = false 21 | } else if focus == nil || l.matchesName(focus) { 22 | fm = true 23 | focusOrIgnore[l.ID] = true 24 | } 25 | if hide != nil && l.matchesName(hide) { 26 | hm = true 27 | l.Line = l.unmatchedLines(hide) 28 | if len(l.Line) == 0 { 29 | hidden[l.ID] = true 30 | } 31 | } 32 | } 33 | 34 | s := make([]*Sample, 0, len(p.Sample)) 35 | for _, sample := range p.Sample { 36 | if focusedAndNotIgnored(sample.Location, focusOrIgnore) { 37 | if len(hidden) > 0 { 38 | var locs []*Location 39 | for _, loc := range sample.Location { 40 | if !hidden[loc.ID] { 41 | locs = append(locs, loc) 42 | } 43 | } 44 | if len(locs) == 0 { 45 | // Remove sample with no locations (by not adding it to s). 46 | continue 47 | } 48 | sample.Location = locs 49 | } 50 | s = append(s, sample) 51 | } 52 | } 53 | p.Sample = s 54 | 55 | return 56 | } 57 | 58 | // matchesName returns whether the function name or file in the 59 | // location matches the regular expression. 60 | func (loc *Location) matchesName(re *regexp.Regexp) bool { 61 | for _, ln := range loc.Line { 62 | if fn := ln.Function; fn != nil { 63 | if re.MatchString(fn.Name) { 64 | return true 65 | } 66 | if re.MatchString(fn.Filename) { 67 | return true 68 | } 69 | } 70 | } 71 | return false 72 | } 73 | 74 | // unmatchedLines returns the lines in the location that do not match 75 | // the regular expression. 76 | func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line { 77 | var lines []Line 78 | for _, ln := range loc.Line { 79 | if fn := ln.Function; fn != nil { 80 | if re.MatchString(fn.Name) { 81 | continue 82 | } 83 | if re.MatchString(fn.Filename) { 84 | continue 85 | } 86 | } 87 | lines = append(lines, ln) 88 | } 89 | return lines 90 | } 91 | 92 | // focusedAndNotIgnored looks up a slice of ids against a map of 93 | // focused/ignored locations. The map only contains locations that are 94 | // explicitly focused or ignored. Returns whether there is at least 95 | // one focused location but no ignored locations. 96 | func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool { 97 | var f bool 98 | for _, loc := range locs { 99 | if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore { 100 | if focus { 101 | // Found focused location. Must keep searching in case there 102 | // is an ignored one as well. 103 | f = true 104 | } else { 105 | // Found ignored location. Can return false right away. 106 | return false 107 | } 108 | } 109 | } 110 | return f 111 | } 112 | 113 | // TagMatch selects tags for filtering 114 | type TagMatch func(key, val string, nval int64) bool 115 | 116 | // FilterSamplesByTag removes all samples from the profile, except 117 | // those that match focus and do not match the ignore regular 118 | // expression. 119 | func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) { 120 | samples := make([]*Sample, 0, len(p.Sample)) 121 | for _, s := range p.Sample { 122 | focused, ignored := focusedSample(s, focus, ignore) 123 | fm = fm || focused 124 | im = im || ignored 125 | if focused && !ignored { 126 | samples = append(samples, s) 127 | } 128 | } 129 | p.Sample = samples 130 | return 131 | } 132 | 133 | // focusedTag checks a sample against focus and ignore regexps. 134 | // Returns whether the focus/ignore regexps match any tags 135 | func focusedSample(s *Sample, focus, ignore TagMatch) (fm, im bool) { 136 | fm = focus == nil 137 | for key, vals := range s.Label { 138 | for _, val := range vals { 139 | if ignore != nil && ignore(key, val, 0) { 140 | im = true 141 | } 142 | if !fm && focus(key, val, 0) { 143 | fm = true 144 | } 145 | } 146 | } 147 | for key, vals := range s.NumLabel { 148 | for _, val := range vals { 149 | if ignore != nil && ignore(key, "", val) { 150 | im = true 151 | } 152 | if !fm && focus(key, "", val) { 153 | fm = true 154 | } 155 | } 156 | } 157 | return fm, im 158 | } 159 | -------------------------------------------------------------------------------- /pprof/proto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file is a simple protocol buffer encoder and decoder. 6 | // 7 | // A protocol message must implement the message interface: 8 | // decoder() []decoder 9 | // encode(*buffer) 10 | // 11 | // The decode method returns a slice indexed by field number that gives the 12 | // function to decode that field. 13 | // The encode method encodes its receiver into the given buffer. 14 | // 15 | // The two methods are simple enough to be implemented by hand rather than 16 | // by using a protocol compiler. 17 | // 18 | // See profile.go for examples of messages implementing this interface. 19 | // 20 | // There is no support for groups, message sets, or "has" bits. 21 | 22 | package pprof 23 | 24 | import "errors" 25 | 26 | type buffer struct { 27 | field int 28 | typ int 29 | u64 uint64 30 | data []byte 31 | tmp [16]byte 32 | } 33 | 34 | type decoder func(*buffer, message) error 35 | 36 | type message interface { 37 | decoder() []decoder 38 | encode(*buffer) 39 | } 40 | 41 | func marshal(m message) []byte { 42 | var b buffer 43 | m.encode(&b) 44 | return b.data 45 | } 46 | 47 | func encodeVarint(b *buffer, x uint64) { 48 | for x >= 128 { 49 | b.data = append(b.data, byte(x)|0x80) 50 | x >>= 7 51 | } 52 | b.data = append(b.data, byte(x)) 53 | } 54 | 55 | func encodeLength(b *buffer, tag int, len int) { 56 | encodeVarint(b, uint64(tag)<<3|2) 57 | encodeVarint(b, uint64(len)) 58 | } 59 | 60 | func encodeUint64(b *buffer, tag int, x uint64) { 61 | // append varint to b.data 62 | encodeVarint(b, uint64(tag)<<3|0) 63 | encodeVarint(b, x) 64 | } 65 | 66 | func encodeUint64s(b *buffer, tag int, x []uint64) { 67 | for _, u := range x { 68 | encodeUint64(b, tag, u) 69 | } 70 | } 71 | 72 | func encodeUint64Opt(b *buffer, tag int, x uint64) { 73 | if x == 0 { 74 | return 75 | } 76 | encodeUint64(b, tag, x) 77 | } 78 | 79 | func encodeInt64(b *buffer, tag int, x int64) { 80 | u := uint64(x) 81 | encodeUint64(b, tag, u) 82 | } 83 | 84 | func encodeInt64Opt(b *buffer, tag int, x int64) { 85 | if x == 0 { 86 | return 87 | } 88 | encodeInt64(b, tag, x) 89 | } 90 | 91 | func encodeString(b *buffer, tag int, x string) { 92 | encodeLength(b, tag, len(x)) 93 | b.data = append(b.data, x...) 94 | } 95 | 96 | func encodeStrings(b *buffer, tag int, x []string) { 97 | for _, s := range x { 98 | encodeString(b, tag, s) 99 | } 100 | } 101 | 102 | func encodeStringOpt(b *buffer, tag int, x string) { 103 | if x == "" { 104 | return 105 | } 106 | encodeString(b, tag, x) 107 | } 108 | 109 | func encodeBool(b *buffer, tag int, x bool) { 110 | if x { 111 | encodeUint64(b, tag, 1) 112 | } else { 113 | encodeUint64(b, tag, 0) 114 | } 115 | } 116 | 117 | func encodeBoolOpt(b *buffer, tag int, x bool) { 118 | if x == false { 119 | return 120 | } 121 | encodeBool(b, tag, x) 122 | } 123 | 124 | func encodeMessage(b *buffer, tag int, m message) { 125 | n1 := len(b.data) 126 | m.encode(b) 127 | n2 := len(b.data) 128 | encodeLength(b, tag, n2-n1) 129 | n3 := len(b.data) 130 | copy(b.tmp[:], b.data[n2:n3]) 131 | copy(b.data[n1+(n3-n2):], b.data[n1:n2]) 132 | copy(b.data[n1:], b.tmp[:n3-n2]) 133 | } 134 | 135 | func unmarshal(data []byte, m message) (err error) { 136 | b := buffer{data: data, typ: 2} 137 | return decodeMessage(&b, m) 138 | } 139 | 140 | func le64(p []byte) uint64 { 141 | return uint64(p[0]) | uint64(p[1])<<8 | uint64(p[2])<<16 | uint64(p[3])<<24 | uint64(p[4])<<32 | uint64(p[5])<<40 | uint64(p[6])<<48 | uint64(p[7])<<56 142 | } 143 | 144 | func le32(p []byte) uint32 { 145 | return uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24 146 | } 147 | 148 | func decodeVarint(data []byte) (uint64, []byte, error) { 149 | var i int 150 | var u uint64 151 | for i = 0; ; i++ { 152 | if i >= 10 || i >= len(data) { 153 | return 0, nil, errors.New("bad varint") 154 | } 155 | u |= uint64(data[i]&0x7F) << uint(7*i) 156 | if data[i]&0x80 == 0 { 157 | return u, data[i+1:], nil 158 | } 159 | } 160 | } 161 | 162 | func decodeField(b *buffer, data []byte) ([]byte, error) { 163 | x, data, err := decodeVarint(data) 164 | if err != nil { 165 | return nil, err 166 | } 167 | b.field = int(x >> 3) 168 | b.typ = int(x & 7) 169 | b.data = nil 170 | b.u64 = 0 171 | switch b.typ { 172 | case 0: 173 | b.u64, data, err = decodeVarint(data) 174 | if err != nil { 175 | return nil, err 176 | } 177 | case 1: 178 | if len(data) < 8 { 179 | return nil, errors.New("not enough data") 180 | } 181 | b.u64 = le64(data[:8]) 182 | data = data[8:] 183 | case 2: 184 | var n uint64 185 | n, data, err = decodeVarint(data) 186 | if err != nil { 187 | return nil, err 188 | } 189 | if n > uint64(len(data)) { 190 | return nil, errors.New("too much data") 191 | } 192 | b.data = data[:n] 193 | data = data[n:] 194 | case 5: 195 | if len(data) < 4 { 196 | return nil, errors.New("not enough data") 197 | } 198 | b.u64 = uint64(le32(data[:4])) 199 | data = data[4:] 200 | default: 201 | return nil, errors.New("unknown type: " + string(b.typ)) 202 | } 203 | 204 | return data, nil 205 | } 206 | 207 | func checkType(b *buffer, typ int) error { 208 | if b.typ != typ { 209 | return errors.New("type mismatch") 210 | } 211 | return nil 212 | } 213 | 214 | func decodeMessage(b *buffer, m message) error { 215 | if err := checkType(b, 2); err != nil { 216 | return err 217 | } 218 | dec := m.decoder() 219 | data := b.data 220 | for len(data) > 0 { 221 | // pull varint field# + type 222 | var err error 223 | data, err = decodeField(b, data) 224 | if err != nil { 225 | return err 226 | } 227 | if b.field >= len(dec) || dec[b.field] == nil { 228 | continue 229 | } 230 | if err := dec[b.field](b, m); err != nil { 231 | return err 232 | } 233 | } 234 | return nil 235 | } 236 | 237 | func decodeInt64(b *buffer, x *int64) error { 238 | if err := checkType(b, 0); err != nil { 239 | return err 240 | } 241 | *x = int64(b.u64) 242 | return nil 243 | } 244 | 245 | func decodeInt64s(b *buffer, x *[]int64) error { 246 | var i int64 247 | if err := decodeInt64(b, &i); err != nil { 248 | return err 249 | } 250 | *x = append(*x, i) 251 | return nil 252 | } 253 | 254 | func decodeUint64(b *buffer, x *uint64) error { 255 | if err := checkType(b, 0); err != nil { 256 | return err 257 | } 258 | *x = b.u64 259 | return nil 260 | } 261 | 262 | func decodeUint64s(b *buffer, x *[]uint64) error { 263 | var u uint64 264 | if err := decodeUint64(b, &u); err != nil { 265 | return err 266 | } 267 | *x = append(*x, u) 268 | return nil 269 | } 270 | 271 | func decodeString(b *buffer, x *string) error { 272 | if err := checkType(b, 2); err != nil { 273 | return err 274 | } 275 | *x = string(b.data) 276 | return nil 277 | } 278 | 279 | func decodeStrings(b *buffer, x *[]string) error { 280 | var s string 281 | if err := decodeString(b, &s); err != nil { 282 | return err 283 | } 284 | *x = append(*x, s) 285 | return nil 286 | } 287 | 288 | func decodeBool(b *buffer, x *bool) error { 289 | if err := checkType(b, 0); err != nil { 290 | return err 291 | } 292 | if int64(b.u64) == 0 { 293 | *x = false 294 | } else { 295 | *x = true 296 | } 297 | return nil 298 | } 299 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Perf2pprof converts Linux perf profile data into Go pprof format. 6 | // (The Linux perf suite is sometimes referred to as perf_events.) 7 | // 8 | // usage: perf2pprof [-list] [-o outfile] [-x exe] perf.data 9 | // 10 | // Perf2pprof converts perf.data, generated by the Linux perf suite, 11 | // into pprof format. The default output file name is the input file 12 | // name (including any directory) with .pprof appended. 13 | // The -o flag instructs perf2pprof to write to outfile instead. 14 | // 15 | // Perf is a system-wide profiler for Linux: it captures data from every binary 16 | // running on the system, along with the operating system kernel. 17 | // Although in principle it should be possible to express those multiple 18 | // event sources in a single pprof output file, the current perf2pprof 19 | // is limited to extracting the samples from just one executable that 20 | // was running. The -x flag specifies the executable, typically a full path. 21 | // The -list flag causes perf2pprof to list all the executables with 22 | // samples in the profile. One of -list or -x must be specified. 23 | // 24 | // Go and Perf 25 | // 26 | // By default, Go does not maintain x86 frame pointers, which means 27 | // that perf cannot sample from Go's execution reliably. 28 | // To build a Go 1.5 or later toolchain that works with perf, use: 29 | // 30 | // GOEXPERIMENT=framepointer ./make.bash 31 | // 32 | // Bugs 33 | // 34 | // This is a very rough sketch. It does seem to work for simple perf profiles 35 | // tracking only a single event, but it could be made much richer. 36 | // 37 | // It should not be necessary to specify the -x flag, as explained above. 38 | // Even if limited to one executable's samples, perf2pprof could infer the 39 | // correct setting for -x by reading the profile to count samples per executable, 40 | // set -x to the executable with the most samples, and read the profile again. 41 | // 42 | package main // import "rsc.io/perf2pprof" 43 | 44 | import ( 45 | "encoding/binary" 46 | "flag" 47 | "fmt" 48 | "log" 49 | "os" 50 | 51 | "github.com/aclements/go-perf/perffile" 52 | "rsc.io/perf2pprof/pprof" 53 | ) 54 | 55 | var ( 56 | listBinaries = flag.Bool("list", false, "list executables found in profile") 57 | outFlag = flag.String("o", "", "output `file` name") 58 | targetBinary = flag.String("x", "", "include only samples from the named `executable`") 59 | ) 60 | 61 | func usage() { 62 | fmt.Fprintf(os.Stderr, "usage: perf2pprof [-list] [-o outfile] [-x exe] perf.data\n") 63 | flag.PrintDefaults() 64 | os.Exit(2) 65 | } 66 | 67 | var numRecords int 68 | 69 | func main() { 70 | log.SetPrefix("perf2pprof: ") 71 | log.SetFlags(0) 72 | flag.Usage = usage 73 | flag.Parse() 74 | if flag.NArg() != 1 { 75 | usage() 76 | } 77 | 78 | if !*listBinaries && *targetBinary == "" { 79 | fmt.Fprintf(os.Stderr, "-list or -x flag is required for now\n") 80 | os.Exit(2) 81 | } 82 | 83 | infile := flag.Arg(0) 84 | outfile := *outFlag 85 | if outfile == "" { 86 | outfile = infile + ".pprof" 87 | } 88 | perf, err := perffile.Open(infile) 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | 93 | var w *os.File 94 | if !*listBinaries { 95 | w, err = os.Create(outfile) 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | } 100 | 101 | printed := make(map[string]bool) 102 | rs := perf.Records(perffile.RecordsCausalOrder) 103 | for rs.Next() { 104 | if numRecords++; numRecords&(numRecords-1) == 0 { 105 | fmt.Printf("%d records\n", numRecords) 106 | } 107 | switch r := rs.Record.(type) { 108 | case *perffile.RecordThrottle, *perffile.RecordComm, *perffile.RecordExit, *perffile.RecordFork: 109 | // ignore 110 | case *perffile.RecordMmap: 111 | if *listBinaries { 112 | if !printed[r.Filename] { 113 | printed[r.Filename] = true 114 | fmt.Printf("%q\n", r.Filename) 115 | } 116 | continue 117 | } 118 | rr := *r 119 | mmap[r.PID] = append(mmap[r.PID], &rr) 120 | case *perffile.RecordSample: 121 | if *listBinaries { 122 | continue 123 | } 124 | switch { 125 | default: 126 | fmt.Printf("bad sample: %+v\n", *r) 127 | continue 128 | case r.Format&perffile.SampleFormatCallchain != 0: 129 | addSample(r.PID, r.Callchain) 130 | case r.Format&perffile.SampleFormatIP != 0: 131 | addSample(r.PID, []uint64{r.IP}) 132 | } 133 | default: 134 | fmt.Printf("found %T\n", rs.Record) 135 | } 136 | } 137 | if err := rs.Err(); err != nil { 138 | log.Fatalf("reading %s: %v", infile, err) 139 | } 140 | 141 | if *listBinaries { 142 | return 143 | } 144 | 145 | fmt.Printf("%d samples for %d records\n", len(samples), numRecords) 146 | 147 | // TODO(rsc): Get actual event type from profile. 148 | // TODO(rsc): A profile can actually have multiple events in it. 149 | // Include the event type in the stack trace map key. 150 | p := new(pprof.Profile) 151 | p.SampleType = []*pprof.ValueType{{Type: "event", Unit: "count"}} 152 | p.PeriodType = p.SampleType[0] 153 | p.Period = 1 154 | 155 | var stack []uint64 156 | var pid int 157 | locations := make(map[uint64]*pprof.Location) 158 | for _, s := range samples { 159 | pid, stack = decode(s.key, stack) 160 | _ = pid 161 | locs := make([]*pprof.Location, 0, len(stack)) 162 | for _, pc := range stack { 163 | if len(locs) > 0 { // pprof stores caller PC with -1 added so it lands in the call instruction 164 | pc-- 165 | } 166 | loc := locations[pc] 167 | if loc == nil { 168 | loc = &pprof.Location{ 169 | Address: pc, 170 | ID: uint64(1 + len(locations)), 171 | } 172 | locations[pc] = loc 173 | p.Location = append(p.Location, loc) 174 | } 175 | locs = append(locs, loc) 176 | } 177 | p.Sample = append(p.Sample, &pprof.Sample{ 178 | Location: locs, 179 | Value: []int64{s.count}, 180 | }) 181 | } 182 | 183 | if err := p.Write(w); err != nil { 184 | log.Fatal("writing %s: %v", outfile, err) 185 | } 186 | if err := w.Close(); err != nil { 187 | log.Fatal("writing %s: %v", outfile, err) 188 | } 189 | } 190 | 191 | var ( 192 | samples = make(map[string]sample) 193 | mmap = make(map[int][]*perffile.RecordMmap) 194 | ) 195 | 196 | type sample struct { 197 | key string 198 | count int64 199 | } 200 | 201 | var buf = make([]byte, 1024) 202 | 203 | func filterStack(pid int, stack []uint64) []uint64 { 204 | mem := mmap[pid] 205 | stk := stack[:0] 206 | for _, pc := range stack { 207 | for _, m := range mem { 208 | if m.Addr <= pc && pc-m.Addr < m.Len && m.Filename == *targetBinary { 209 | stk = append(stk, pc) 210 | break 211 | } 212 | } 213 | } 214 | return stk 215 | } 216 | 217 | func addSample(pid int, stack []uint64) { 218 | stack = filterStack(pid, stack) 219 | if len(stack) == 0 { 220 | return 221 | } 222 | for cap(buf) < (1+len(stack))*10 { 223 | buf = make([]byte, cap(buf)*2) 224 | } 225 | buf = buf[:10*(1+len(stack))] 226 | n := 0 227 | n += binary.PutUvarint(buf[n:], uint64(pid)) 228 | for _, x := range stack { 229 | n += binary.PutUvarint(buf[n:], x) 230 | } 231 | 232 | buf = buf[:n] 233 | s, ok := samples[string(buf)] 234 | if !ok { 235 | s.key = string(buf) 236 | if len(samples)&(len(samples)-1) == 0 { 237 | fmt.Printf("%d samples for %d records\n", len(samples)+1, numRecords) 238 | } 239 | } 240 | s.count++ 241 | samples[s.key] = s 242 | } 243 | 244 | func decode(s string, stack []uint64) (pid int, outstack []uint64) { 245 | stack = stack[:0] 246 | v, n := uvarint(s) 247 | if n <= 0 { 248 | log.Fatal("malformed internal encoding") 249 | } 250 | pid = int(v) 251 | s = s[n:] 252 | for len(s) > 0 { 253 | v, n := uvarint(s) 254 | if n <= 0 { 255 | log.Fatal("malformed internal encoding") 256 | } 257 | stack = append(stack, v) 258 | s = s[n:] 259 | } 260 | return pid, stack 261 | } 262 | 263 | func uvarint(buf string) (uint64, int) { 264 | var x uint64 265 | var s uint 266 | for i := 0; i < len(buf); i++ { 267 | b := buf[i] 268 | if b < 0x80 { 269 | if i > 9 || i == 9 && b > 1 { 270 | return 0, -(i + 1) // overflow 271 | } 272 | return x | uint64(b)< 0 { 258 | s.Label = labels 259 | } 260 | if len(numLabels) > 0 { 261 | s.NumLabel = numLabels 262 | } 263 | s.Location = nil 264 | for _, lid := range s.locationIDX { 265 | s.Location = append(s.Location, locations[lid]) 266 | } 267 | s.locationIDX = nil 268 | } 269 | 270 | p.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err) 271 | p.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err) 272 | 273 | if pt := p.PeriodType; pt == nil { 274 | p.PeriodType = &ValueType{} 275 | } 276 | 277 | if pt := p.PeriodType; pt != nil { 278 | pt.Type, err = getString(p.stringTable, &pt.typeX, err) 279 | pt.Unit, err = getString(p.stringTable, &pt.unitX, err) 280 | } 281 | p.stringTable = nil 282 | return nil 283 | } 284 | 285 | func (p *ValueType) decoder() []decoder { 286 | return valueTypeDecoder 287 | } 288 | 289 | func (p *ValueType) encode(b *buffer) { 290 | encodeInt64Opt(b, 1, p.typeX) 291 | encodeInt64Opt(b, 2, p.unitX) 292 | } 293 | 294 | var valueTypeDecoder = []decoder{ 295 | nil, // 0 296 | // optional int64 type = 1 297 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) }, 298 | // optional int64 unit = 2 299 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) }, 300 | } 301 | 302 | func (p *Sample) decoder() []decoder { 303 | return sampleDecoder 304 | } 305 | 306 | func (p *Sample) encode(b *buffer) { 307 | encodeUint64s(b, 1, p.locationIDX) 308 | for _, x := range p.Value { 309 | encodeInt64(b, 2, x) 310 | } 311 | for _, x := range p.labelX { 312 | encodeMessage(b, 3, x) 313 | } 314 | } 315 | 316 | var sampleDecoder = []decoder{ 317 | nil, // 0 318 | // repeated uint64 location = 1 319 | func(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) }, 320 | // repeated int64 value = 2 321 | func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) }, 322 | // repeated Label label = 3 323 | func(b *buffer, m message) error { 324 | s := m.(*Sample) 325 | n := len(s.labelX) 326 | s.labelX = append(s.labelX, Label{}) 327 | return decodeMessage(b, &s.labelX[n]) 328 | }, 329 | } 330 | 331 | func (p Label) decoder() []decoder { 332 | return labelDecoder 333 | } 334 | 335 | func (p Label) encode(b *buffer) { 336 | encodeInt64Opt(b, 1, p.keyX) 337 | encodeInt64Opt(b, 2, p.strX) 338 | encodeInt64Opt(b, 3, p.numX) 339 | } 340 | 341 | var labelDecoder = []decoder{ 342 | nil, // 0 343 | // optional int64 key = 1 344 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).keyX) }, 345 | // optional int64 str = 2 346 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).strX) }, 347 | // optional int64 num = 3 348 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Label).numX) }, 349 | } 350 | 351 | func (p *Mapping) decoder() []decoder { 352 | return mappingDecoder 353 | } 354 | 355 | func (p *Mapping) encode(b *buffer) { 356 | encodeUint64Opt(b, 1, p.ID) 357 | encodeUint64Opt(b, 2, p.Start) 358 | encodeUint64Opt(b, 3, p.Limit) 359 | encodeUint64Opt(b, 4, p.Offset) 360 | encodeInt64Opt(b, 5, p.fileX) 361 | encodeInt64Opt(b, 6, p.buildIDX) 362 | encodeBoolOpt(b, 7, p.HasFunctions) 363 | encodeBoolOpt(b, 8, p.HasFilenames) 364 | encodeBoolOpt(b, 9, p.HasLineNumbers) 365 | encodeBoolOpt(b, 10, p.HasInlineFrames) 366 | } 367 | 368 | var mappingDecoder = []decoder{ 369 | nil, // 0 370 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) }, // optional uint64 id = 1 371 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) }, // optional uint64 memory_offset = 2 372 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) }, // optional uint64 memory_limit = 3 373 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) }, // optional uint64 file_offset = 4 374 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) }, // optional int64 filename = 5 375 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) }, // optional int64 build_id = 6 376 | func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) }, // optional bool has_functions = 7 377 | func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) }, // optional bool has_filenames = 8 378 | func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) }, // optional bool has_line_numbers = 9 379 | func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10 380 | } 381 | 382 | func (p *Location) decoder() []decoder { 383 | return locationDecoder 384 | } 385 | 386 | func (p *Location) encode(b *buffer) { 387 | encodeUint64Opt(b, 1, p.ID) 388 | encodeUint64Opt(b, 2, p.mappingIDX) 389 | encodeUint64Opt(b, 3, p.Address) 390 | for i := range p.Line { 391 | encodeMessage(b, 4, &p.Line[i]) 392 | } 393 | } 394 | 395 | var locationDecoder = []decoder{ 396 | nil, // 0 397 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) }, // optional uint64 id = 1; 398 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2; 399 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) }, // optional uint64 address = 3; 400 | func(b *buffer, m message) error { // repeated Line line = 4 401 | pp := m.(*Location) 402 | n := len(pp.Line) 403 | pp.Line = append(pp.Line, Line{}) 404 | return decodeMessage(b, &pp.Line[n]) 405 | }, 406 | } 407 | 408 | func (p *Line) decoder() []decoder { 409 | return lineDecoder 410 | } 411 | 412 | func (p *Line) encode(b *buffer) { 413 | encodeUint64Opt(b, 1, p.functionIDX) 414 | encodeInt64Opt(b, 2, p.Line) 415 | } 416 | 417 | var lineDecoder = []decoder{ 418 | nil, // 0 419 | // optional uint64 function_id = 1 420 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) }, 421 | // optional int64 line = 2 422 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) }, 423 | } 424 | 425 | func (p *Function) decoder() []decoder { 426 | return functionDecoder 427 | } 428 | 429 | func (p *Function) encode(b *buffer) { 430 | encodeUint64Opt(b, 1, p.ID) 431 | encodeInt64Opt(b, 2, p.nameX) 432 | encodeInt64Opt(b, 3, p.systemNameX) 433 | encodeInt64Opt(b, 4, p.filenameX) 434 | encodeInt64Opt(b, 5, p.StartLine) 435 | } 436 | 437 | var functionDecoder = []decoder{ 438 | nil, // 0 439 | // optional uint64 id = 1 440 | func(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) }, 441 | // optional int64 function_name = 2 442 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) }, 443 | // optional int64 function_system_name = 3 444 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) }, 445 | // repeated int64 filename = 4 446 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) }, 447 | // optional int64 start_line = 5 448 | func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) }, 449 | } 450 | 451 | func addString(strings map[string]int, s string) int64 { 452 | i, ok := strings[s] 453 | if !ok { 454 | i = len(strings) 455 | strings[s] = i 456 | } 457 | return int64(i) 458 | } 459 | 460 | func getString(strings []string, strng *int64, err error) (string, error) { 461 | if err != nil { 462 | return "", err 463 | } 464 | s := int(*strng) 465 | if s < 0 || s >= len(strings) { 466 | return "", errMalformed 467 | } 468 | *strng = 0 469 | return strings[s], nil 470 | } 471 | -------------------------------------------------------------------------------- /pprof/profile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Forked from Go distributions cmd/pprof/internal/profile. 6 | 7 | // Package pprof provides a representation of profile.proto and 8 | // methods to encode/decode profiles in this format. 9 | package pprof 10 | 11 | import ( 12 | "bytes" 13 | "compress/gzip" 14 | "fmt" 15 | "io" 16 | "io/ioutil" 17 | "regexp" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | // Profile is an in-memory representation of profile.proto. 23 | type Profile struct { 24 | SampleType []*ValueType 25 | Sample []*Sample 26 | Mapping []*Mapping 27 | Location []*Location 28 | Function []*Function 29 | 30 | DropFrames string 31 | KeepFrames string 32 | 33 | TimeNanos int64 34 | DurationNanos int64 35 | PeriodType *ValueType 36 | Period int64 37 | 38 | dropFramesX int64 39 | keepFramesX int64 40 | stringTable []string 41 | } 42 | 43 | // ValueType corresponds to Profile.ValueType 44 | type ValueType struct { 45 | Type string // cpu, wall, inuse_space, etc 46 | Unit string // seconds, nanoseconds, bytes, etc 47 | 48 | typeX int64 49 | unitX int64 50 | } 51 | 52 | // Sample corresponds to Profile.Sample 53 | type Sample struct { 54 | Location []*Location 55 | Value []int64 56 | Label map[string][]string 57 | NumLabel map[string][]int64 58 | 59 | locationIDX []uint64 60 | labelX []Label 61 | } 62 | 63 | // Label corresponds to Profile.Label 64 | type Label struct { 65 | keyX int64 66 | // Exactly one of the two following values must be set 67 | strX int64 68 | numX int64 // Integer value for this label 69 | } 70 | 71 | // Mapping corresponds to Profile.Mapping 72 | type Mapping struct { 73 | ID uint64 74 | Start uint64 75 | Limit uint64 76 | Offset uint64 77 | File string 78 | BuildID string 79 | HasFunctions bool 80 | HasFilenames bool 81 | HasLineNumbers bool 82 | HasInlineFrames bool 83 | 84 | fileX int64 85 | buildIDX int64 86 | } 87 | 88 | // Location corresponds to Profile.Location 89 | type Location struct { 90 | ID uint64 91 | Mapping *Mapping 92 | Address uint64 93 | Line []Line 94 | 95 | mappingIDX uint64 96 | } 97 | 98 | // Line corresponds to Profile.Line 99 | type Line struct { 100 | Function *Function 101 | Line int64 102 | 103 | functionIDX uint64 104 | } 105 | 106 | // Function corresponds to Profile.Function 107 | type Function struct { 108 | ID uint64 109 | Name string 110 | SystemName string 111 | Filename string 112 | StartLine int64 113 | 114 | nameX int64 115 | systemNameX int64 116 | filenameX int64 117 | } 118 | 119 | // Parse parses a profile and checks for its validity. The input 120 | // may be a gzip-compressed encoded protobuf or one of many legacy 121 | // profile formats which may be unsupported in the future. 122 | func Parse(r io.Reader) (*Profile, error) { 123 | orig, err := ioutil.ReadAll(r) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | var p *Profile 129 | if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b { 130 | var data []byte 131 | 132 | if gz, err := gzip.NewReader(bytes.NewBuffer(orig)); err == nil { 133 | data, err = ioutil.ReadAll(gz) 134 | } 135 | if err != nil { 136 | return nil, fmt.Errorf("decompressing profile: %v", err) 137 | } 138 | orig = data 139 | } 140 | if p, err = parseUncompressed(orig); err != nil { 141 | if p, err = parseLegacy(orig); err != nil { 142 | return nil, fmt.Errorf("parsing profile: %v", err) 143 | } 144 | } 145 | 146 | if err := p.CheckValid(); err != nil { 147 | return nil, fmt.Errorf("malformed profile: %v", err) 148 | } 149 | return p, nil 150 | } 151 | 152 | var errUnrecognized = fmt.Errorf("unrecognized profile format") 153 | var errMalformed = fmt.Errorf("malformed profile format") 154 | 155 | func parseLegacy(data []byte) (*Profile, error) { 156 | parsers := []func([]byte) (*Profile, error){ 157 | parseCPU, 158 | parseHeap, 159 | parseGoCount, // goroutine, threadcreate 160 | parseThread, 161 | parseContention, 162 | } 163 | 164 | for _, parser := range parsers { 165 | p, err := parser(data) 166 | if err == nil { 167 | p.setMain() 168 | p.addLegacyFrameInfo() 169 | return p, nil 170 | } 171 | if err != errUnrecognized { 172 | return nil, err 173 | } 174 | } 175 | return nil, errUnrecognized 176 | } 177 | 178 | func parseUncompressed(data []byte) (*Profile, error) { 179 | p := &Profile{} 180 | if err := unmarshal(data, p); err != nil { 181 | return nil, err 182 | } 183 | 184 | if err := p.postDecode(); err != nil { 185 | return nil, err 186 | } 187 | 188 | return p, nil 189 | } 190 | 191 | var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`) 192 | 193 | // setMain scans Mapping entries and guesses which entry is main 194 | // because legacy profiles don't obey the convention of putting main 195 | // first. 196 | func (p *Profile) setMain() { 197 | for i := 0; i < len(p.Mapping); i++ { 198 | file := strings.TrimSpace(strings.Replace(p.Mapping[i].File, "(deleted)", "", -1)) 199 | if len(file) == 0 { 200 | continue 201 | } 202 | if len(libRx.FindStringSubmatch(file)) > 0 { 203 | continue 204 | } 205 | if strings.HasPrefix(file, "[") { 206 | continue 207 | } 208 | // Swap what we guess is main to position 0. 209 | tmp := p.Mapping[i] 210 | p.Mapping[i] = p.Mapping[0] 211 | p.Mapping[0] = tmp 212 | break 213 | } 214 | } 215 | 216 | // Write writes the profile as a gzip-compressed marshaled protobuf. 217 | func (p *Profile) Write(w io.Writer) error { 218 | p.preEncode() 219 | b := marshal(p) 220 | zw := gzip.NewWriter(w) 221 | defer zw.Close() 222 | _, err := zw.Write(b) 223 | return err 224 | } 225 | 226 | // CheckValid tests whether the profile is valid. Checks include, but are 227 | // not limited to: 228 | // - len(Profile.Sample[n].value) == len(Profile.value_unit) 229 | // - Sample.id has a corresponding Profile.Location 230 | func (p *Profile) CheckValid() error { 231 | // Check that sample values are consistent 232 | sampleLen := len(p.SampleType) 233 | if sampleLen == 0 && len(p.Sample) != 0 { 234 | return fmt.Errorf("missing sample type information") 235 | } 236 | for _, s := range p.Sample { 237 | if len(s.Value) != sampleLen { 238 | return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType)) 239 | } 240 | } 241 | 242 | // Check that all mappings/locations/functions are in the tables 243 | // Check that there are no duplicate ids 244 | mappings := make(map[uint64]*Mapping, len(p.Mapping)) 245 | for _, m := range p.Mapping { 246 | if m.ID == 0 { 247 | return fmt.Errorf("found mapping with reserved ID=0") 248 | } 249 | if mappings[m.ID] != nil { 250 | return fmt.Errorf("multiple mappings with same id: %d", m.ID) 251 | } 252 | mappings[m.ID] = m 253 | } 254 | functions := make(map[uint64]*Function, len(p.Function)) 255 | for _, f := range p.Function { 256 | if f.ID == 0 { 257 | return fmt.Errorf("found function with reserved ID=0") 258 | } 259 | if functions[f.ID] != nil { 260 | return fmt.Errorf("multiple functions with same id: %d", f.ID) 261 | } 262 | functions[f.ID] = f 263 | } 264 | locations := make(map[uint64]*Location, len(p.Location)) 265 | for _, l := range p.Location { 266 | if l.ID == 0 { 267 | return fmt.Errorf("found location with reserved id=0") 268 | } 269 | if locations[l.ID] != nil { 270 | return fmt.Errorf("multiple locations with same id: %d", l.ID) 271 | } 272 | locations[l.ID] = l 273 | if m := l.Mapping; m != nil { 274 | if m.ID == 0 || mappings[m.ID] != m { 275 | return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID) 276 | } 277 | } 278 | for _, ln := range l.Line { 279 | if f := ln.Function; f != nil { 280 | if f.ID == 0 || functions[f.ID] != f { 281 | return fmt.Errorf("inconsistent function %p: %d", f, f.ID) 282 | } 283 | } 284 | } 285 | } 286 | return nil 287 | } 288 | 289 | // Aggregate merges the locations in the profile into equivalence 290 | // classes preserving the request attributes. It also updates the 291 | // samples to point to the merged locations. 292 | func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error { 293 | for _, m := range p.Mapping { 294 | m.HasInlineFrames = m.HasInlineFrames && inlineFrame 295 | m.HasFunctions = m.HasFunctions && function 296 | m.HasFilenames = m.HasFilenames && filename 297 | m.HasLineNumbers = m.HasLineNumbers && linenumber 298 | } 299 | 300 | // Aggregate functions 301 | if !function || !filename { 302 | for _, f := range p.Function { 303 | if !function { 304 | f.Name = "" 305 | f.SystemName = "" 306 | } 307 | if !filename { 308 | f.Filename = "" 309 | } 310 | } 311 | } 312 | 313 | // Aggregate locations 314 | if !inlineFrame || !address || !linenumber { 315 | for _, l := range p.Location { 316 | if !inlineFrame && len(l.Line) > 1 { 317 | l.Line = l.Line[len(l.Line)-1:] 318 | } 319 | if !linenumber { 320 | for i := range l.Line { 321 | l.Line[i].Line = 0 322 | } 323 | } 324 | if !address { 325 | l.Address = 0 326 | } 327 | } 328 | } 329 | 330 | return p.CheckValid() 331 | } 332 | 333 | // Print dumps a text representation of a profile. Intended mainly 334 | // for debugging purposes. 335 | func (p *Profile) String() string { 336 | 337 | ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location)) 338 | if pt := p.PeriodType; pt != nil { 339 | ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit)) 340 | } 341 | ss = append(ss, fmt.Sprintf("Period: %d", p.Period)) 342 | if p.TimeNanos != 0 { 343 | ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos))) 344 | } 345 | if p.DurationNanos != 0 { 346 | ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos))) 347 | } 348 | 349 | ss = append(ss, "Samples:") 350 | var sh1 string 351 | for _, s := range p.SampleType { 352 | sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit) 353 | } 354 | ss = append(ss, strings.TrimSpace(sh1)) 355 | for _, s := range p.Sample { 356 | var sv string 357 | for _, v := range s.Value { 358 | sv = fmt.Sprintf("%s %10d", sv, v) 359 | } 360 | sv = sv + ": " 361 | for _, l := range s.Location { 362 | sv = sv + fmt.Sprintf("%d ", l.ID) 363 | } 364 | ss = append(ss, sv) 365 | const labelHeader = " " 366 | if len(s.Label) > 0 { 367 | ls := labelHeader 368 | for k, v := range s.Label { 369 | ls = ls + fmt.Sprintf("%s:%v ", k, v) 370 | } 371 | ss = append(ss, ls) 372 | } 373 | if len(s.NumLabel) > 0 { 374 | ls := labelHeader 375 | for k, v := range s.NumLabel { 376 | ls = ls + fmt.Sprintf("%s:%v ", k, v) 377 | } 378 | ss = append(ss, ls) 379 | } 380 | } 381 | 382 | ss = append(ss, "Locations") 383 | for _, l := range p.Location { 384 | locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address) 385 | if m := l.Mapping; m != nil { 386 | locStr = locStr + fmt.Sprintf("M=%d ", m.ID) 387 | } 388 | if len(l.Line) == 0 { 389 | ss = append(ss, locStr) 390 | } 391 | for li := range l.Line { 392 | lnStr := "??" 393 | if fn := l.Line[li].Function; fn != nil { 394 | lnStr = fmt.Sprintf("%s %s:%d s=%d", 395 | fn.Name, 396 | fn.Filename, 397 | l.Line[li].Line, 398 | fn.StartLine) 399 | if fn.Name != fn.SystemName { 400 | lnStr = lnStr + "(" + fn.SystemName + ")" 401 | } 402 | } 403 | ss = append(ss, locStr+lnStr) 404 | // Do not print location details past the first line 405 | locStr = " " 406 | } 407 | } 408 | 409 | ss = append(ss, "Mappings") 410 | for _, m := range p.Mapping { 411 | bits := "" 412 | if m.HasFunctions { 413 | bits = bits + "[FN]" 414 | } 415 | if m.HasFilenames { 416 | bits = bits + "[FL]" 417 | } 418 | if m.HasLineNumbers { 419 | bits = bits + "[LN]" 420 | } 421 | if m.HasInlineFrames { 422 | bits = bits + "[IN]" 423 | } 424 | ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s", 425 | m.ID, 426 | m.Start, m.Limit, m.Offset, 427 | m.File, 428 | m.BuildID, 429 | bits)) 430 | } 431 | 432 | return strings.Join(ss, "\n") + "\n" 433 | } 434 | 435 | // Merge adds profile p adjusted by ratio r into profile p. Profiles 436 | // must be compatible (same Type and SampleType). 437 | // TODO(rsilvera): consider normalizing the profiles based on the 438 | // total samples collected. 439 | func (p *Profile) Merge(pb *Profile, r float64) error { 440 | if err := p.Compatible(pb); err != nil { 441 | return err 442 | } 443 | 444 | pb = pb.Copy() 445 | 446 | // Keep the largest of the two periods. 447 | if pb.Period > p.Period { 448 | p.Period = pb.Period 449 | } 450 | 451 | p.DurationNanos += pb.DurationNanos 452 | 453 | p.Mapping = append(p.Mapping, pb.Mapping...) 454 | for i, m := range p.Mapping { 455 | m.ID = uint64(i + 1) 456 | } 457 | p.Location = append(p.Location, pb.Location...) 458 | for i, l := range p.Location { 459 | l.ID = uint64(i + 1) 460 | } 461 | p.Function = append(p.Function, pb.Function...) 462 | for i, f := range p.Function { 463 | f.ID = uint64(i + 1) 464 | } 465 | 466 | if r != 1.0 { 467 | for _, s := range pb.Sample { 468 | for i, v := range s.Value { 469 | s.Value[i] = int64((float64(v) * r)) 470 | } 471 | } 472 | } 473 | p.Sample = append(p.Sample, pb.Sample...) 474 | return p.CheckValid() 475 | } 476 | 477 | // Compatible determines if two profiles can be compared/merged. 478 | // returns nil if the profiles are compatible; otherwise an error with 479 | // details on the incompatibility. 480 | func (p *Profile) Compatible(pb *Profile) error { 481 | if !compatibleValueTypes(p.PeriodType, pb.PeriodType) { 482 | return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType) 483 | } 484 | 485 | if len(p.SampleType) != len(pb.SampleType) { 486 | return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) 487 | } 488 | 489 | for i := range p.SampleType { 490 | if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) { 491 | return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) 492 | } 493 | } 494 | 495 | return nil 496 | } 497 | 498 | // HasFunctions determines if all locations in this profile have 499 | // symbolized function information. 500 | func (p *Profile) HasFunctions() bool { 501 | for _, l := range p.Location { 502 | if l.Mapping == nil || !l.Mapping.HasFunctions { 503 | return false 504 | } 505 | } 506 | return true 507 | } 508 | 509 | // HasFileLines determines if all locations in this profile have 510 | // symbolized file and line number information. 511 | func (p *Profile) HasFileLines() bool { 512 | for _, l := range p.Location { 513 | if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) { 514 | return false 515 | } 516 | } 517 | return true 518 | } 519 | 520 | func compatibleValueTypes(v1, v2 *ValueType) bool { 521 | if v1 == nil || v2 == nil { 522 | return true // No grounds to disqualify. 523 | } 524 | return v1.Type == v2.Type && v1.Unit == v2.Unit 525 | } 526 | 527 | // Copy makes a fully independent copy of a profile. 528 | func (p *Profile) Copy() *Profile { 529 | p.preEncode() 530 | b := marshal(p) 531 | 532 | pp := &Profile{} 533 | if err := unmarshal(b, pp); err != nil { 534 | panic(err) 535 | } 536 | if err := pp.postDecode(); err != nil { 537 | panic(err) 538 | } 539 | 540 | return pp 541 | } 542 | 543 | // Demangler maps symbol names to a human-readable form. This may 544 | // include C++ demangling and additional simplification. Names that 545 | // are not demangled may be missing from the resulting map. 546 | type Demangler func(name []string) (map[string]string, error) 547 | 548 | // Demangle attempts to demangle and optionally simplify any function 549 | // names referenced in the profile. It works on a best-effort basis: 550 | // it will silently preserve the original names in case of any errors. 551 | func (p *Profile) Demangle(d Demangler) error { 552 | // Collect names to demangle. 553 | var names []string 554 | for _, fn := range p.Function { 555 | names = append(names, fn.SystemName) 556 | } 557 | 558 | // Update profile with demangled names. 559 | demangled, err := d(names) 560 | if err != nil { 561 | return err 562 | } 563 | for _, fn := range p.Function { 564 | if dd, ok := demangled[fn.SystemName]; ok { 565 | fn.Name = dd 566 | } 567 | } 568 | return nil 569 | } 570 | -------------------------------------------------------------------------------- /pprof/legacy_profile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file implements parsers to convert legacy profiles into the 6 | // profile.proto format. 7 | 8 | package pprof 9 | 10 | import ( 11 | "bufio" 12 | "bytes" 13 | "fmt" 14 | "io" 15 | "math" 16 | "regexp" 17 | "strconv" 18 | "strings" 19 | ) 20 | 21 | var ( 22 | countStartRE = regexp.MustCompile(`\A(\w+) profile: total \d+\n\z`) 23 | countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`) 24 | 25 | heapHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`) 26 | heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`) 27 | 28 | contentionSampleRE = regexp.MustCompile(`(\d+) *(\d+) @([ x0-9a-f]*)`) 29 | 30 | hexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`) 31 | 32 | growthHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`) 33 | 34 | fragmentationHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`) 35 | 36 | threadzStartRE = regexp.MustCompile(`--- threadz \d+ ---`) 37 | threadStartRE = regexp.MustCompile(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`) 38 | 39 | procMapsRE = regexp.MustCompile(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`) 40 | 41 | briefMapsRE = regexp.MustCompile(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`) 42 | 43 | // LegacyHeapAllocated instructs the heapz parsers to use the 44 | // allocated memory stats instead of the default in-use memory. Note 45 | // that tcmalloc doesn't provide all allocated memory, only in-use 46 | // stats. 47 | LegacyHeapAllocated bool 48 | ) 49 | 50 | func isSpaceOrComment(line string) bool { 51 | trimmed := strings.TrimSpace(line) 52 | return len(trimmed) == 0 || trimmed[0] == '#' 53 | } 54 | 55 | // parseGoCount parses a Go count profile (e.g., threadcreate or 56 | // goroutine) and returns a new Profile. 57 | func parseGoCount(b []byte) (*Profile, error) { 58 | r := bytes.NewBuffer(b) 59 | 60 | var line string 61 | var err error 62 | for { 63 | // Skip past comments and empty lines seeking a real header. 64 | line, err = r.ReadString('\n') 65 | if err != nil { 66 | return nil, err 67 | } 68 | if !isSpaceOrComment(line) { 69 | break 70 | } 71 | } 72 | 73 | m := countStartRE.FindStringSubmatch(line) 74 | if m == nil { 75 | return nil, errUnrecognized 76 | } 77 | profileType := string(m[1]) 78 | p := &Profile{ 79 | PeriodType: &ValueType{Type: profileType, Unit: "count"}, 80 | Period: 1, 81 | SampleType: []*ValueType{{Type: profileType, Unit: "count"}}, 82 | } 83 | locations := make(map[uint64]*Location) 84 | for { 85 | line, err = r.ReadString('\n') 86 | if err != nil { 87 | if err == io.EOF { 88 | break 89 | } 90 | return nil, err 91 | } 92 | if isSpaceOrComment(line) { 93 | continue 94 | } 95 | if strings.HasPrefix(line, "---") { 96 | break 97 | } 98 | m := countRE.FindStringSubmatch(line) 99 | if m == nil { 100 | return nil, errMalformed 101 | } 102 | n, err := strconv.ParseInt(string(m[1]), 0, 64) 103 | if err != nil { 104 | return nil, errMalformed 105 | } 106 | fields := strings.Fields(string(m[2])) 107 | locs := make([]*Location, 0, len(fields)) 108 | for _, stk := range fields { 109 | addr, err := strconv.ParseUint(stk, 0, 64) 110 | if err != nil { 111 | return nil, errMalformed 112 | } 113 | // Adjust all frames by -1 (except the leaf) to land on top of 114 | // the call instruction. 115 | if len(locs) > 0 { 116 | addr-- 117 | } 118 | loc := locations[addr] 119 | if loc == nil { 120 | loc = &Location{ 121 | Address: addr, 122 | } 123 | locations[addr] = loc 124 | p.Location = append(p.Location, loc) 125 | } 126 | locs = append(locs, loc) 127 | } 128 | p.Sample = append(p.Sample, &Sample{ 129 | Location: locs, 130 | Value: []int64{n}, 131 | }) 132 | } 133 | 134 | if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil { 135 | return nil, err 136 | } 137 | return p, nil 138 | } 139 | 140 | // remapLocationIDs ensures there is a location for each address 141 | // referenced by a sample, and remaps the samples to point to the new 142 | // location ids. 143 | func (p *Profile) remapLocationIDs() { 144 | seen := make(map[*Location]bool, len(p.Location)) 145 | var locs []*Location 146 | 147 | for _, s := range p.Sample { 148 | for _, l := range s.Location { 149 | if seen[l] { 150 | continue 151 | } 152 | l.ID = uint64(len(locs) + 1) 153 | locs = append(locs, l) 154 | seen[l] = true 155 | } 156 | } 157 | p.Location = locs 158 | } 159 | 160 | func (p *Profile) remapFunctionIDs() { 161 | seen := make(map[*Function]bool, len(p.Function)) 162 | var fns []*Function 163 | 164 | for _, l := range p.Location { 165 | for _, ln := range l.Line { 166 | fn := ln.Function 167 | if fn == nil || seen[fn] { 168 | continue 169 | } 170 | fn.ID = uint64(len(fns) + 1) 171 | fns = append(fns, fn) 172 | seen[fn] = true 173 | } 174 | } 175 | p.Function = fns 176 | } 177 | 178 | // remapMappingIDs matches location addresses with existing mappings 179 | // and updates them appropriately. This is O(N*M), if this ever shows 180 | // up as a bottleneck, evaluate sorting the mappings and doing a 181 | // binary search, which would make it O(N*log(M)). 182 | func (p *Profile) remapMappingIDs() { 183 | if len(p.Mapping) == 0 { 184 | return 185 | } 186 | 187 | // Some profile handlers will incorrectly set regions for the main 188 | // executable if its section is remapped. Fix them through heuristics. 189 | 190 | // Remove the initial mapping if named '/anon_hugepage' and has a 191 | // consecutive adjacent mapping. 192 | if m := p.Mapping[0]; strings.HasPrefix(m.File, "/anon_hugepage") { 193 | if len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start { 194 | p.Mapping = p.Mapping[1:] 195 | } 196 | } 197 | 198 | // Subtract the offset from the start of the main mapping if it 199 | // ends up at a recognizable start address. 200 | const expectedStart = 0x400000 201 | if m := p.Mapping[0]; m.Start-m.Offset == expectedStart { 202 | m.Start = expectedStart 203 | m.Offset = 0 204 | } 205 | 206 | for _, l := range p.Location { 207 | if a := l.Address; a != 0 { 208 | for _, m := range p.Mapping { 209 | if m.Start <= a && a < m.Limit { 210 | l.Mapping = m 211 | break 212 | } 213 | } 214 | } 215 | } 216 | 217 | // Reset all mapping IDs. 218 | for i, m := range p.Mapping { 219 | m.ID = uint64(i + 1) 220 | } 221 | } 222 | 223 | var cpuInts = []func([]byte) (uint64, []byte){ 224 | get32l, 225 | get32b, 226 | get64l, 227 | get64b, 228 | } 229 | 230 | func get32l(b []byte) (uint64, []byte) { 231 | if len(b) < 4 { 232 | return 0, nil 233 | } 234 | return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:] 235 | } 236 | 237 | func get32b(b []byte) (uint64, []byte) { 238 | if len(b) < 4 { 239 | return 0, nil 240 | } 241 | return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:] 242 | } 243 | 244 | func get64l(b []byte) (uint64, []byte) { 245 | if len(b) < 8 { 246 | return 0, nil 247 | } 248 | return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:] 249 | } 250 | 251 | func get64b(b []byte) (uint64, []byte) { 252 | if len(b) < 8 { 253 | return 0, nil 254 | } 255 | return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:] 256 | } 257 | 258 | // ParseTracebacks parses a set of tracebacks and returns a newly 259 | // populated profile. It will accept any text file and generate a 260 | // Profile out of it with any hex addresses it can identify, including 261 | // a process map if it can recognize one. Each sample will include a 262 | // tag "source" with the addresses recognized in string format. 263 | func ParseTracebacks(b []byte) (*Profile, error) { 264 | r := bytes.NewBuffer(b) 265 | 266 | p := &Profile{ 267 | PeriodType: &ValueType{Type: "trace", Unit: "count"}, 268 | Period: 1, 269 | SampleType: []*ValueType{ 270 | {Type: "trace", Unit: "count"}, 271 | }, 272 | } 273 | 274 | var sources []string 275 | var sloc []*Location 276 | 277 | locs := make(map[uint64]*Location) 278 | for { 279 | l, err := r.ReadString('\n') 280 | if err != nil { 281 | if err != io.EOF { 282 | return nil, err 283 | } 284 | if l == "" { 285 | break 286 | } 287 | } 288 | if sectionTrigger(l) == memoryMapSection { 289 | break 290 | } 291 | if s, addrs := extractHexAddresses(l); len(s) > 0 { 292 | for _, addr := range addrs { 293 | // Addresses from stack traces point to the next instruction after 294 | // each call. Adjust by -1 to land somewhere on the actual call 295 | // (except for the leaf, which is not a call). 296 | if len(sloc) > 0 { 297 | addr-- 298 | } 299 | loc := locs[addr] 300 | if locs[addr] == nil { 301 | loc = &Location{ 302 | Address: addr, 303 | } 304 | p.Location = append(p.Location, loc) 305 | locs[addr] = loc 306 | } 307 | sloc = append(sloc, loc) 308 | } 309 | 310 | sources = append(sources, s...) 311 | } else { 312 | if len(sources) > 0 || len(sloc) > 0 { 313 | addTracebackSample(sloc, sources, p) 314 | sloc, sources = nil, nil 315 | } 316 | } 317 | } 318 | 319 | // Add final sample to save any leftover data. 320 | if len(sources) > 0 || len(sloc) > 0 { 321 | addTracebackSample(sloc, sources, p) 322 | } 323 | 324 | if err := p.ParseMemoryMap(r); err != nil { 325 | return nil, err 326 | } 327 | return p, nil 328 | } 329 | 330 | func addTracebackSample(l []*Location, s []string, p *Profile) { 331 | p.Sample = append(p.Sample, 332 | &Sample{ 333 | Value: []int64{1}, 334 | Location: l, 335 | Label: map[string][]string{"source": s}, 336 | }) 337 | } 338 | 339 | // parseCPU parses a profilez legacy profile and returns a newly 340 | // populated Profile. 341 | // 342 | // The general format for profilez samples is a sequence of words in 343 | // binary format. The first words are a header with the following data: 344 | // 1st word -- 0 345 | // 2nd word -- 3 346 | // 3rd word -- 0 if a c++ application, 1 if a java application. 347 | // 4th word -- Sampling period (in microseconds). 348 | // 5th word -- Padding. 349 | func parseCPU(b []byte) (*Profile, error) { 350 | var parse func([]byte) (uint64, []byte) 351 | var n1, n2, n3, n4, n5 uint64 352 | for _, parse = range cpuInts { 353 | var tmp []byte 354 | n1, tmp = parse(b) 355 | n2, tmp = parse(tmp) 356 | n3, tmp = parse(tmp) 357 | n4, tmp = parse(tmp) 358 | n5, tmp = parse(tmp) 359 | 360 | if tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 { 361 | b = tmp 362 | return cpuProfile(b, int64(n4), parse) 363 | } 364 | } 365 | return nil, errUnrecognized 366 | } 367 | 368 | // cpuProfile returns a new Profile from C++ profilez data. 369 | // b is the profile bytes after the header, period is the profiling 370 | // period, and parse is a function to parse 8-byte chunks from the 371 | // profile in its native endianness. 372 | func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) { 373 | p := &Profile{ 374 | Period: period * 1000, 375 | PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"}, 376 | SampleType: []*ValueType{ 377 | {Type: "samples", Unit: "count"}, 378 | {Type: "cpu", Unit: "nanoseconds"}, 379 | }, 380 | } 381 | var err error 382 | if b, _, err = parseCPUSamples(b, parse, true, p); err != nil { 383 | return nil, err 384 | } 385 | 386 | // If all samples have the same second-to-the-bottom frame, it 387 | // strongly suggests that it is an uninteresting artifact of 388 | // measurement -- a stack frame pushed by the signal handler. The 389 | // bottom frame is always correct as it is picked up from the signal 390 | // structure, not the stack. Check if this is the case and if so, 391 | // remove. 392 | if len(p.Sample) > 1 && len(p.Sample[0].Location) > 1 { 393 | allSame := true 394 | id1 := p.Sample[0].Location[1].Address 395 | for _, s := range p.Sample { 396 | if len(s.Location) < 2 || id1 != s.Location[1].Address { 397 | allSame = false 398 | break 399 | } 400 | } 401 | if allSame { 402 | for _, s := range p.Sample { 403 | s.Location = append(s.Location[:1], s.Location[2:]...) 404 | } 405 | } 406 | } 407 | 408 | if err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil { 409 | return nil, err 410 | } 411 | return p, nil 412 | } 413 | 414 | // parseCPUSamples parses a collection of profilez samples from a 415 | // profile. 416 | // 417 | // profilez samples are a repeated sequence of stack frames of the 418 | // form: 419 | // 1st word -- The number of times this stack was encountered. 420 | // 2nd word -- The size of the stack (StackSize). 421 | // 3rd word -- The first address on the stack. 422 | // ... 423 | // StackSize + 2 -- The last address on the stack 424 | // The last stack trace is of the form: 425 | // 1st word -- 0 426 | // 2nd word -- 1 427 | // 3rd word -- 0 428 | // 429 | // Addresses from stack traces may point to the next instruction after 430 | // each call. Optionally adjust by -1 to land somewhere on the actual 431 | // call (except for the leaf, which is not a call). 432 | func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) { 433 | locs := make(map[uint64]*Location) 434 | for len(b) > 0 { 435 | var count, nstk uint64 436 | count, b = parse(b) 437 | nstk, b = parse(b) 438 | if b == nil || nstk > uint64(len(b)/4) { 439 | return nil, nil, errUnrecognized 440 | } 441 | var sloc []*Location 442 | addrs := make([]uint64, nstk) 443 | for i := 0; i < int(nstk); i++ { 444 | addrs[i], b = parse(b) 445 | } 446 | 447 | if count == 0 && nstk == 1 && addrs[0] == 0 { 448 | // End of data marker 449 | break 450 | } 451 | for i, addr := range addrs { 452 | if adjust && i > 0 { 453 | addr-- 454 | } 455 | loc := locs[addr] 456 | if loc == nil { 457 | loc = &Location{ 458 | Address: addr, 459 | } 460 | locs[addr] = loc 461 | p.Location = append(p.Location, loc) 462 | } 463 | sloc = append(sloc, loc) 464 | } 465 | p.Sample = append(p.Sample, 466 | &Sample{ 467 | Value: []int64{int64(count), int64(count) * int64(p.Period)}, 468 | Location: sloc, 469 | }) 470 | } 471 | // Reached the end without finding the EOD marker. 472 | return b, locs, nil 473 | } 474 | 475 | // parseHeap parses a heapz legacy or a growthz profile and 476 | // returns a newly populated Profile. 477 | func parseHeap(b []byte) (p *Profile, err error) { 478 | r := bytes.NewBuffer(b) 479 | l, err := r.ReadString('\n') 480 | if err != nil { 481 | return nil, errUnrecognized 482 | } 483 | 484 | sampling := "" 485 | 486 | if header := heapHeaderRE.FindStringSubmatch(l); header != nil { 487 | p = &Profile{ 488 | SampleType: []*ValueType{ 489 | {Type: "objects", Unit: "count"}, 490 | {Type: "space", Unit: "bytes"}, 491 | }, 492 | PeriodType: &ValueType{Type: "objects", Unit: "bytes"}, 493 | } 494 | 495 | var period int64 496 | if len(header[6]) > 0 { 497 | if period, err = strconv.ParseInt(string(header[6]), 10, 64); err != nil { 498 | return nil, errUnrecognized 499 | } 500 | } 501 | 502 | switch header[5] { 503 | case "heapz_v2", "heap_v2": 504 | sampling, p.Period = "v2", period 505 | case "heapprofile": 506 | sampling, p.Period = "", 1 507 | case "heap": 508 | sampling, p.Period = "v2", period/2 509 | default: 510 | return nil, errUnrecognized 511 | } 512 | } else if header = growthHeaderRE.FindStringSubmatch(l); header != nil { 513 | p = &Profile{ 514 | SampleType: []*ValueType{ 515 | {Type: "objects", Unit: "count"}, 516 | {Type: "space", Unit: "bytes"}, 517 | }, 518 | PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"}, 519 | Period: 1, 520 | } 521 | } else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil { 522 | p = &Profile{ 523 | SampleType: []*ValueType{ 524 | {Type: "objects", Unit: "count"}, 525 | {Type: "space", Unit: "bytes"}, 526 | }, 527 | PeriodType: &ValueType{Type: "allocations", Unit: "count"}, 528 | Period: 1, 529 | } 530 | } else { 531 | return nil, errUnrecognized 532 | } 533 | 534 | if LegacyHeapAllocated { 535 | for _, st := range p.SampleType { 536 | st.Type = "alloc_" + st.Type 537 | } 538 | } else { 539 | for _, st := range p.SampleType { 540 | st.Type = "inuse_" + st.Type 541 | } 542 | } 543 | 544 | locs := make(map[uint64]*Location) 545 | for { 546 | l, err = r.ReadString('\n') 547 | if err != nil { 548 | if err != io.EOF { 549 | return nil, err 550 | } 551 | 552 | if l == "" { 553 | break 554 | } 555 | } 556 | 557 | if l = strings.TrimSpace(l); l == "" { 558 | continue 559 | } 560 | 561 | if sectionTrigger(l) != unrecognizedSection { 562 | break 563 | } 564 | 565 | value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling) 566 | if err != nil { 567 | return nil, err 568 | } 569 | var sloc []*Location 570 | for i, addr := range addrs { 571 | // Addresses from stack traces point to the next instruction after 572 | // each call. Adjust by -1 to land somewhere on the actual call 573 | // (except for the leaf, which is not a call). 574 | if i > 0 { 575 | addr-- 576 | } 577 | loc := locs[addr] 578 | if locs[addr] == nil { 579 | loc = &Location{ 580 | Address: addr, 581 | } 582 | p.Location = append(p.Location, loc) 583 | locs[addr] = loc 584 | } 585 | sloc = append(sloc, loc) 586 | } 587 | 588 | p.Sample = append(p.Sample, &Sample{ 589 | Value: value, 590 | Location: sloc, 591 | NumLabel: map[string][]int64{"bytes": []int64{blocksize}}, 592 | }) 593 | } 594 | 595 | if err = parseAdditionalSections(l, r, p); err != nil { 596 | return nil, err 597 | } 598 | return p, nil 599 | } 600 | 601 | // parseHeapSample parses a single row from a heap profile into a new Sample. 602 | func parseHeapSample(line string, rate int64, sampling string) (value []int64, blocksize int64, addrs []uint64, err error) { 603 | sampleData := heapSampleRE.FindStringSubmatch(line) 604 | if len(sampleData) != 6 { 605 | return value, blocksize, addrs, fmt.Errorf("unexpected number of sample values: got %d, want 6", len(sampleData)) 606 | } 607 | 608 | // Use first two values by default; tcmalloc sampling generates the 609 | // same value for both, only the older heap-profile collect separate 610 | // stats for in-use and allocated objects. 611 | valueIndex := 1 612 | if LegacyHeapAllocated { 613 | valueIndex = 3 614 | } 615 | 616 | var v1, v2 int64 617 | if v1, err = strconv.ParseInt(sampleData[valueIndex], 10, 64); err != nil { 618 | return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) 619 | } 620 | if v2, err = strconv.ParseInt(sampleData[valueIndex+1], 10, 64); err != nil { 621 | return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) 622 | } 623 | 624 | if v1 == 0 { 625 | if v2 != 0 { 626 | return value, blocksize, addrs, fmt.Errorf("allocation count was 0 but allocation bytes was %d", v2) 627 | } 628 | } else { 629 | blocksize = v2 / v1 630 | if sampling == "v2" { 631 | v1, v2 = scaleHeapSample(v1, v2, rate) 632 | } 633 | } 634 | 635 | value = []int64{v1, v2} 636 | addrs = parseHexAddresses(sampleData[5]) 637 | 638 | return value, blocksize, addrs, nil 639 | } 640 | 641 | // extractHexAddresses extracts hex numbers from a string and returns 642 | // them, together with their numeric value, in a slice. 643 | func extractHexAddresses(s string) ([]string, []uint64) { 644 | hexStrings := hexNumberRE.FindAllString(s, -1) 645 | var ids []uint64 646 | for _, s := range hexStrings { 647 | if id, err := strconv.ParseUint(s, 0, 64); err == nil { 648 | ids = append(ids, id) 649 | } else { 650 | // Do not expect any parsing failures due to the regexp matching. 651 | panic("failed to parse hex value:" + s) 652 | } 653 | } 654 | return hexStrings, ids 655 | } 656 | 657 | // parseHexAddresses parses hex numbers from a string and returns them 658 | // in a slice. 659 | func parseHexAddresses(s string) []uint64 { 660 | _, ids := extractHexAddresses(s) 661 | return ids 662 | } 663 | 664 | // scaleHeapSample adjusts the data from a heapz Sample to 665 | // account for its probability of appearing in the collected 666 | // data. heapz profiles are a sampling of the memory allocations 667 | // requests in a program. We estimate the unsampled value by dividing 668 | // each collected sample by its probability of appearing in the 669 | // profile. heapz v2 profiles rely on a poisson process to determine 670 | // which samples to collect, based on the desired average collection 671 | // rate R. The probability of a sample of size S to appear in that 672 | // profile is 1-exp(-S/R). 673 | func scaleHeapSample(count, size, rate int64) (int64, int64) { 674 | if count == 0 || size == 0 { 675 | return 0, 0 676 | } 677 | 678 | if rate <= 1 { 679 | // if rate==1 all samples were collected so no adjustment is needed. 680 | // if rate<1 treat as unknown and skip scaling. 681 | return count, size 682 | } 683 | 684 | avgSize := float64(size) / float64(count) 685 | scale := 1 / (1 - math.Exp(-avgSize/float64(rate))) 686 | 687 | return int64(float64(count) * scale), int64(float64(size) * scale) 688 | } 689 | 690 | // parseContention parses a contentionz profile and returns a newly 691 | // populated Profile. 692 | func parseContention(b []byte) (p *Profile, err error) { 693 | r := bytes.NewBuffer(b) 694 | l, err := r.ReadString('\n') 695 | if err != nil { 696 | return nil, errUnrecognized 697 | } 698 | 699 | if !strings.HasPrefix(l, "--- contention") { 700 | return nil, errUnrecognized 701 | } 702 | 703 | p = &Profile{ 704 | PeriodType: &ValueType{Type: "contentions", Unit: "count"}, 705 | Period: 1, 706 | SampleType: []*ValueType{ 707 | {Type: "contentions", Unit: "count"}, 708 | {Type: "delay", Unit: "nanoseconds"}, 709 | }, 710 | } 711 | 712 | var cpuHz int64 713 | // Parse text of the form "attribute = value" before the samples. 714 | const delimiter = "=" 715 | for { 716 | l, err = r.ReadString('\n') 717 | if err != nil { 718 | if err != io.EOF { 719 | return nil, err 720 | } 721 | 722 | if l == "" { 723 | break 724 | } 725 | } 726 | 727 | if l = strings.TrimSpace(l); l == "" { 728 | continue 729 | } 730 | 731 | if strings.HasPrefix(l, "---") { 732 | break 733 | } 734 | 735 | attr := strings.SplitN(l, delimiter, 2) 736 | if len(attr) != 2 { 737 | break 738 | } 739 | key, val := strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1]) 740 | var err error 741 | switch key { 742 | case "cycles/second": 743 | if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil { 744 | return nil, errUnrecognized 745 | } 746 | case "sampling period": 747 | if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil { 748 | return nil, errUnrecognized 749 | } 750 | case "ms since reset": 751 | ms, err := strconv.ParseInt(val, 0, 64) 752 | if err != nil { 753 | return nil, errUnrecognized 754 | } 755 | p.DurationNanos = ms * 1000 * 1000 756 | case "format": 757 | // CPP contentionz profiles don't have format. 758 | return nil, errUnrecognized 759 | case "resolution": 760 | // CPP contentionz profiles don't have resolution. 761 | return nil, errUnrecognized 762 | case "discarded samples": 763 | default: 764 | return nil, errUnrecognized 765 | } 766 | } 767 | 768 | locs := make(map[uint64]*Location) 769 | for { 770 | if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") { 771 | break 772 | } 773 | value, addrs, err := parseContentionSample(l, p.Period, cpuHz) 774 | if err != nil { 775 | return nil, err 776 | } 777 | var sloc []*Location 778 | for i, addr := range addrs { 779 | // Addresses from stack traces point to the next instruction after 780 | // each call. Adjust by -1 to land somewhere on the actual call 781 | // (except for the leaf, which is not a call). 782 | if i > 0 { 783 | addr-- 784 | } 785 | loc := locs[addr] 786 | if locs[addr] == nil { 787 | loc = &Location{ 788 | Address: addr, 789 | } 790 | p.Location = append(p.Location, loc) 791 | locs[addr] = loc 792 | } 793 | sloc = append(sloc, loc) 794 | } 795 | p.Sample = append(p.Sample, &Sample{ 796 | Value: value, 797 | Location: sloc, 798 | }) 799 | 800 | if l, err = r.ReadString('\n'); err != nil { 801 | if err != io.EOF { 802 | return nil, err 803 | } 804 | if l == "" { 805 | break 806 | } 807 | } 808 | } 809 | 810 | if err = parseAdditionalSections(l, r, p); err != nil { 811 | return nil, err 812 | } 813 | 814 | return p, nil 815 | } 816 | 817 | // parseContentionSample parses a single row from a contention profile 818 | // into a new Sample. 819 | func parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) { 820 | sampleData := contentionSampleRE.FindStringSubmatch(line) 821 | if sampleData == nil { 822 | return value, addrs, errUnrecognized 823 | } 824 | 825 | v1, err := strconv.ParseInt(sampleData[1], 10, 64) 826 | if err != nil { 827 | return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) 828 | } 829 | v2, err := strconv.ParseInt(sampleData[2], 10, 64) 830 | if err != nil { 831 | return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err) 832 | } 833 | 834 | // Unsample values if period and cpuHz are available. 835 | // - Delays are scaled to cycles and then to nanoseconds. 836 | // - Contentions are scaled to cycles. 837 | if period > 0 { 838 | if cpuHz > 0 { 839 | cpuGHz := float64(cpuHz) / 1e9 840 | v1 = int64(float64(v1) * float64(period) / cpuGHz) 841 | } 842 | v2 = v2 * period 843 | } 844 | 845 | value = []int64{v2, v1} 846 | addrs = parseHexAddresses(sampleData[3]) 847 | 848 | return value, addrs, nil 849 | } 850 | 851 | // parseThread parses a Threadz profile and returns a new Profile. 852 | func parseThread(b []byte) (*Profile, error) { 853 | r := bytes.NewBuffer(b) 854 | 855 | var line string 856 | var err error 857 | for { 858 | // Skip past comments and empty lines seeking a real header. 859 | line, err = r.ReadString('\n') 860 | if err != nil { 861 | return nil, err 862 | } 863 | if !isSpaceOrComment(line) { 864 | break 865 | } 866 | } 867 | 868 | if m := threadzStartRE.FindStringSubmatch(line); m != nil { 869 | // Advance over initial comments until first stack trace. 870 | for { 871 | line, err = r.ReadString('\n') 872 | if err != nil { 873 | if err != io.EOF { 874 | return nil, err 875 | } 876 | 877 | if line == "" { 878 | break 879 | } 880 | } 881 | if sectionTrigger(line) != unrecognizedSection || line[0] == '-' { 882 | break 883 | } 884 | } 885 | } else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { 886 | return nil, errUnrecognized 887 | } 888 | 889 | p := &Profile{ 890 | SampleType: []*ValueType{{Type: "thread", Unit: "count"}}, 891 | PeriodType: &ValueType{Type: "thread", Unit: "count"}, 892 | Period: 1, 893 | } 894 | 895 | locs := make(map[uint64]*Location) 896 | // Recognize each thread and populate profile samples. 897 | for sectionTrigger(line) == unrecognizedSection { 898 | if strings.HasPrefix(line, "---- no stack trace for") { 899 | line = "" 900 | break 901 | } 902 | if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 { 903 | return nil, errUnrecognized 904 | } 905 | 906 | var addrs []uint64 907 | line, addrs, err = parseThreadSample(r) 908 | if err != nil { 909 | return nil, errUnrecognized 910 | } 911 | if len(addrs) == 0 { 912 | // We got a --same as previous threads--. Bump counters. 913 | if len(p.Sample) > 0 { 914 | s := p.Sample[len(p.Sample)-1] 915 | s.Value[0]++ 916 | } 917 | continue 918 | } 919 | 920 | var sloc []*Location 921 | for i, addr := range addrs { 922 | // Addresses from stack traces point to the next instruction after 923 | // each call. Adjust by -1 to land somewhere on the actual call 924 | // (except for the leaf, which is not a call). 925 | if i > 0 { 926 | addr-- 927 | } 928 | loc := locs[addr] 929 | if locs[addr] == nil { 930 | loc = &Location{ 931 | Address: addr, 932 | } 933 | p.Location = append(p.Location, loc) 934 | locs[addr] = loc 935 | } 936 | sloc = append(sloc, loc) 937 | } 938 | 939 | p.Sample = append(p.Sample, &Sample{ 940 | Value: []int64{1}, 941 | Location: sloc, 942 | }) 943 | } 944 | 945 | if err = parseAdditionalSections(line, r, p); err != nil { 946 | return nil, err 947 | } 948 | 949 | return p, nil 950 | } 951 | 952 | // parseThreadSample parses a symbolized or unsymbolized stack trace. 953 | // Returns the first line after the traceback, the sample (or nil if 954 | // it hits a 'same-as-previous' marker) and an error. 955 | func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) { 956 | var l string 957 | sameAsPrevious := false 958 | for { 959 | if l, err = b.ReadString('\n'); err != nil { 960 | if err != io.EOF { 961 | return "", nil, err 962 | } 963 | if l == "" { 964 | break 965 | } 966 | } 967 | if l = strings.TrimSpace(l); l == "" { 968 | continue 969 | } 970 | 971 | if strings.HasPrefix(l, "---") { 972 | break 973 | } 974 | if strings.Contains(l, "same as previous thread") { 975 | sameAsPrevious = true 976 | continue 977 | } 978 | 979 | addrs = append(addrs, parseHexAddresses(l)...) 980 | } 981 | 982 | if sameAsPrevious { 983 | return l, nil, nil 984 | } 985 | return l, addrs, nil 986 | } 987 | 988 | // parseAdditionalSections parses any additional sections in the 989 | // profile, ignoring any unrecognized sections. 990 | func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) (err error) { 991 | for { 992 | if sectionTrigger(l) == memoryMapSection { 993 | break 994 | } 995 | // Ignore any unrecognized sections. 996 | if l, err := b.ReadString('\n'); err != nil { 997 | if err != io.EOF { 998 | return err 999 | } 1000 | if l == "" { 1001 | break 1002 | } 1003 | } 1004 | } 1005 | return p.ParseMemoryMap(b) 1006 | } 1007 | 1008 | // ParseMemoryMap parses a memory map in the format of 1009 | // /proc/self/maps, and overrides the mappings in the current profile. 1010 | // It renumbers the samples and locations in the profile correspondingly. 1011 | func (p *Profile) ParseMemoryMap(rd io.Reader) error { 1012 | b := bufio.NewReader(rd) 1013 | 1014 | var attrs []string 1015 | var r *strings.Replacer 1016 | const delimiter = "=" 1017 | for { 1018 | l, err := b.ReadString('\n') 1019 | if err != nil { 1020 | if err != io.EOF { 1021 | return err 1022 | } 1023 | if l == "" { 1024 | break 1025 | } 1026 | } 1027 | if l = strings.TrimSpace(l); l == "" { 1028 | continue 1029 | } 1030 | 1031 | if r != nil { 1032 | l = r.Replace(l) 1033 | } 1034 | m, err := parseMappingEntry(l) 1035 | if err != nil { 1036 | if err == errUnrecognized { 1037 | // Recognize assignments of the form: attr=value, and replace 1038 | // $attr with value on subsequent mappings. 1039 | if attr := strings.SplitN(l, delimiter, 2); len(attr) == 2 { 1040 | attrs = append(attrs, "$"+strings.TrimSpace(attr[0]), strings.TrimSpace(attr[1])) 1041 | r = strings.NewReplacer(attrs...) 1042 | } 1043 | // Ignore any unrecognized entries 1044 | continue 1045 | } 1046 | return err 1047 | } 1048 | if m == nil || (m.File == "" && len(p.Mapping) != 0) { 1049 | // In some cases the first entry may include the address range 1050 | // but not the name of the file. It should be followed by 1051 | // another entry with the name. 1052 | continue 1053 | } 1054 | if len(p.Mapping) == 1 && p.Mapping[0].File == "" { 1055 | // Update the name if this is the entry following that empty one. 1056 | p.Mapping[0].File = m.File 1057 | continue 1058 | } 1059 | p.Mapping = append(p.Mapping, m) 1060 | } 1061 | p.remapLocationIDs() 1062 | p.remapFunctionIDs() 1063 | p.remapMappingIDs() 1064 | return nil 1065 | } 1066 | 1067 | func parseMappingEntry(l string) (*Mapping, error) { 1068 | mapping := &Mapping{} 1069 | var err error 1070 | if me := procMapsRE.FindStringSubmatch(l); len(me) == 9 { 1071 | if !strings.Contains(me[3], "x") { 1072 | // Skip non-executable entries. 1073 | return nil, nil 1074 | } 1075 | if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { 1076 | return nil, errUnrecognized 1077 | } 1078 | if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { 1079 | return nil, errUnrecognized 1080 | } 1081 | if me[4] != "" { 1082 | if mapping.Offset, err = strconv.ParseUint(me[4], 16, 64); err != nil { 1083 | return nil, errUnrecognized 1084 | } 1085 | } 1086 | mapping.File = me[8] 1087 | return mapping, nil 1088 | } 1089 | 1090 | if me := briefMapsRE.FindStringSubmatch(l); len(me) == 6 { 1091 | if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil { 1092 | return nil, errUnrecognized 1093 | } 1094 | if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil { 1095 | return nil, errUnrecognized 1096 | } 1097 | mapping.File = me[3] 1098 | if me[5] != "" { 1099 | if mapping.Offset, err = strconv.ParseUint(me[5], 16, 64); err != nil { 1100 | return nil, errUnrecognized 1101 | } 1102 | } 1103 | return mapping, nil 1104 | } 1105 | 1106 | return nil, errUnrecognized 1107 | } 1108 | 1109 | type sectionType int 1110 | 1111 | const ( 1112 | unrecognizedSection sectionType = iota 1113 | memoryMapSection 1114 | ) 1115 | 1116 | var memoryMapTriggers = []string{ 1117 | "--- Memory map: ---", 1118 | "MAPPED_LIBRARIES:", 1119 | } 1120 | 1121 | func sectionTrigger(line string) sectionType { 1122 | for _, trigger := range memoryMapTriggers { 1123 | if strings.Contains(line, trigger) { 1124 | return memoryMapSection 1125 | } 1126 | } 1127 | return unrecognizedSection 1128 | } 1129 | 1130 | func (p *Profile) addLegacyFrameInfo() { 1131 | switch { 1132 | case isProfileType(p, heapzSampleTypes) || 1133 | isProfileType(p, heapzInUseSampleTypes) || 1134 | isProfileType(p, heapzAllocSampleTypes): 1135 | p.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr 1136 | case isProfileType(p, contentionzSampleTypes): 1137 | p.DropFrames, p.KeepFrames = lockRxStr, "" 1138 | default: 1139 | p.DropFrames, p.KeepFrames = cpuProfilerRxStr, "" 1140 | } 1141 | } 1142 | 1143 | var heapzSampleTypes = []string{"allocations", "size"} // early Go pprof profiles 1144 | var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"} 1145 | var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"} 1146 | var contentionzSampleTypes = []string{"contentions", "delay"} 1147 | 1148 | func isProfileType(p *Profile, t []string) bool { 1149 | st := p.SampleType 1150 | if len(st) != len(t) { 1151 | return false 1152 | } 1153 | 1154 | for i := range st { 1155 | if st[i].Type != t[i] { 1156 | return false 1157 | } 1158 | } 1159 | return true 1160 | } 1161 | 1162 | var allocRxStr = strings.Join([]string{ 1163 | // POSIX entry points. 1164 | `calloc`, 1165 | `cfree`, 1166 | `malloc`, 1167 | `free`, 1168 | `memalign`, 1169 | `do_memalign`, 1170 | `(__)?posix_memalign`, 1171 | `pvalloc`, 1172 | `valloc`, 1173 | `realloc`, 1174 | 1175 | // TC malloc. 1176 | `tcmalloc::.*`, 1177 | `tc_calloc`, 1178 | `tc_cfree`, 1179 | `tc_malloc`, 1180 | `tc_free`, 1181 | `tc_memalign`, 1182 | `tc_posix_memalign`, 1183 | `tc_pvalloc`, 1184 | `tc_valloc`, 1185 | `tc_realloc`, 1186 | `tc_new`, 1187 | `tc_delete`, 1188 | `tc_newarray`, 1189 | `tc_deletearray`, 1190 | `tc_new_nothrow`, 1191 | `tc_newarray_nothrow`, 1192 | 1193 | // Memory-allocation routines on OS X. 1194 | `malloc_zone_malloc`, 1195 | `malloc_zone_calloc`, 1196 | `malloc_zone_valloc`, 1197 | `malloc_zone_realloc`, 1198 | `malloc_zone_memalign`, 1199 | `malloc_zone_free`, 1200 | 1201 | // Go runtime 1202 | `runtime\..*`, 1203 | 1204 | // Other misc. memory allocation routines 1205 | `BaseArena::.*`, 1206 | `(::)?do_malloc_no_errno`, 1207 | `(::)?do_malloc_pages`, 1208 | `(::)?do_malloc`, 1209 | `DoSampledAllocation`, 1210 | `MallocedMemBlock::MallocedMemBlock`, 1211 | `_M_allocate`, 1212 | `__builtin_(vec_)?delete`, 1213 | `__builtin_(vec_)?new`, 1214 | `__gnu_cxx::new_allocator::allocate`, 1215 | `__libc_malloc`, 1216 | `__malloc_alloc_template::allocate`, 1217 | `allocate`, 1218 | `cpp_alloc`, 1219 | `operator new(\[\])?`, 1220 | `simple_alloc::allocate`, 1221 | }, `|`) 1222 | 1223 | var allocSkipRxStr = strings.Join([]string{ 1224 | // Preserve Go runtime frames that appear in the middle/bottom of 1225 | // the stack. 1226 | `runtime\.panic`, 1227 | }, `|`) 1228 | 1229 | var cpuProfilerRxStr = strings.Join([]string{ 1230 | `ProfileData::Add`, 1231 | `ProfileData::prof_handler`, 1232 | `CpuProfiler::prof_handler`, 1233 | `__pthread_sighandler`, 1234 | `__restore`, 1235 | }, `|`) 1236 | 1237 | var lockRxStr = strings.Join([]string{ 1238 | `RecordLockProfileData`, 1239 | `(base::)?RecordLockProfileData.*`, 1240 | `(base::)?SubmitMutexProfileData.*`, 1241 | `(base::)?SubmitSpinLockProfileData.*`, 1242 | `(Mutex::)?AwaitCommon.*`, 1243 | `(Mutex::)?Unlock.*`, 1244 | `(Mutex::)?UnlockSlow.*`, 1245 | `(Mutex::)?ReaderUnlock.*`, 1246 | `(MutexLock::)?~MutexLock.*`, 1247 | `(SpinLock::)?Unlock.*`, 1248 | `(SpinLock::)?SlowUnlock.*`, 1249 | `(SpinLockHolder::)?~SpinLockHolder.*`, 1250 | }, `|`) 1251 | --------------------------------------------------------------------------------