// Copyright 2017 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package profiler import ( "bytes" "regexp" "runtime" "strings" "github.com/google/pprof/profile" ) var shouldAssumeSymbolized = isSymbolizedGoVersion(runtime.Version()) type function interface { Name() string FileLine(pc uintptr) (string, int) } // funcForPC is a wrapper for runtime.FuncForPC. Defined as var for testing. var funcForPC = func(pc uintptr) function { return runtime.FuncForPC(pc) } // parseAndSymbolize parses a profile from a buffer, symbolizes it // if it's not yet symbolized, and writes the profile back as a // gzip-compressed marshaled protobuf. func parseAndSymbolize(data *bytes.Buffer) error { p, err := profile.ParseData(data.Bytes()) if err != nil { return err } // Do nothing if the profile is already symbolized. if symbolized(p) { return nil } // Clear the profile functions to avoid creating duplicates. p.Function = nil symbolize(p) data.Reset() return p.Write(data) } // isSymbolizedGoVersion returns true if Go version equals to or is // higher than Go 1.9. Starting Go 1.9 the profiles are symbolized // by runtime/pprof. func isSymbolizedGoVersion(goVersion string) bool { r, err := regexp.Compile(`go(1\.9|1\.[1-9][0-9]|[2-9]).*`) if err == nil && r.MatchString(goVersion) { return true } return false } // symbolized checks if all locations have symbolized function // information. func symbolized(p *profile.Profile) bool { for _, l := range p.Location { if len(l.Line) == 0 || l.Line[0].Function == nil { return false } } return true } func symbolize(p *profile.Profile) { fns := profileFunctionMap{} for _, l := range p.Location { pc := uintptr(l.Address) f := funcForPC(pc) if f == nil { continue } file, lineno := f.FileLine(pc) l.Line = []profile.Line{ { Function: fns.findOrAddFunction(f.Name(), file, p), Line: int64(lineno), }, } } // Trim runtime functions. Always hide runtime.goexit. Other runtime // functions are only hidden for heap profile when they appear at the beginning. isHeapProfile := p.PeriodType != nil && p.PeriodType.Type == "space" for _, s := range p.Sample { show := !isHeapProfile var i int for _, l := range s.Location { if len(l.Line) > 0 && l.Line[0].Function != nil { name := l.Line[0].Function.Name if name == "runtime.goexit" || !show && strings.HasPrefix(name, "runtime.") { continue } } show = true s.Location[i] = l i++ } // If all locations of a sample are trimmed, keep the root location. if i == 0 && len(s.Location) > 0 { s.Location[0] = s.Location[len(s.Location)-1] i = 1 } s.Location = s.Location[:i] } } type profileFunctionMap map[profile.Function]*profile.Function func (fns profileFunctionMap) findOrAddFunction(name, filename string, p *profile.Profile) *profile.Function { f := profile.Function{ Name: name, SystemName: name, Filename: filename, } if fp := fns[f]; fp != nil { return fp } fp := new(profile.Function) fns[f] = fp *fp = f fp.ID = uint64(len(p.Function) + 1) p.Function = append(p.Function, fp) return fp }