pkg/tools/profiling/api.go (171 lines of code) (raw):
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you 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 profiling
import (
"fmt"
"regexp"
"github.com/apache/skywalking-rover/pkg/logger"
"github.com/ianlancetaylor/demangle"
)
type ModuleType int8
var (
KernelProcSymbolFilePath = "kallsyms" // after host.GetHostProcInHost, should be "/proc/kallsyms"
log = logger.GetLogger("tools", "profiling")
)
const (
ModuleTypeExec ModuleType = iota
ModuleTypeSo
ModuleTypePerfMap
ModuleTypeVDSO
ModuleTypeUnknown
)
// Info of profiling process
type Info struct {
Modules []*Module
cacheAddrToSymbol map[uint64]string
}
type Module struct {
Ranges []*ModuleRange
Name string
Path string
Type ModuleType
SoOffset, SoAddr uint64
Symbols []*Symbol
}
type ModuleRange struct {
StartAddr, EndAddr, FileOffset uint64
}
// Symbol of executable file
type Symbol struct {
Name string
Location uint64
Size uint64
}
type StatFinder interface {
// IsSupport to stat the executable file for profiling
IsSupport(filePath string) bool
// AnalyzeSymbols in the file
AnalyzeSymbols(filePath string) ([]*Symbol, error)
// ToModule to init a new module
ToModule(pid int32, modName, modPath string, moduleRange []*ModuleRange) (*Module, error)
}
func NewInfo(modules map[string]*Module) *Info {
ls := make([]*Module, 0)
for _, m := range modules {
ls = append(ls, m)
}
return &Info{Modules: ls, cacheAddrToSymbol: make(map[uint64]string)}
}
// FindSymbols from address list, if could not found symbol name then append default symbol to array
func (i *Info) FindSymbols(addresses []uint64, defaultSymbol string) []string {
if len(addresses) == 0 {
return nil
}
result := make([]string, 0)
for _, addr := range addresses {
if addr <= 0 {
continue
}
s := i.FindSymbolName(addr)
if s == "" {
s = defaultSymbol
}
result = append(result, s)
}
return result
}
// FindSymbolName by address
func (i *Info) FindSymbolName(address uint64) string {
if d := i.cacheAddrToSymbol[address]; d != "" {
return d
}
log.Debugf("ready to find the symbol from address: %d", address)
foundModule := false
for _, mod := range i.Modules {
offset, c := mod.contains(address)
if !c {
continue
}
foundModule = true
if sym := mod.findAddr(offset); sym != nil {
name := processSymbolName(sym.Name)
i.cacheAddrToSymbol[address] = name
return name
}
}
if !foundModule {
log.Debugf("could not found any module to handle address: %d", address)
}
return ""
}
func (i *Info) FindSymbolAddress(name string) uint64 {
for _, m := range i.Modules {
for _, sym := range m.Symbols {
if sym.Name == name {
return sym.Location
}
}
}
return 0
}
func (i *Info) FindSymbolByRegex(rep string) (string, error) {
vals, err := i.FindMultipleSymbolByRegex(rep)
if err != nil {
return "", err
}
return vals[0], nil
}
func (i *Info) FindMultipleSymbolByRegex(rep string) ([]string, error) {
compile, err := regexp.Compile(rep)
if err != nil {
return nil, err
}
result := make([]string, 0)
for _, m := range i.Modules {
for _, sym := range m.Symbols {
if compile.MatchString(sym.Name) {
result = append(result, sym.Name)
}
}
}
if len(result) == 0 {
return nil, fmt.Errorf("cannot found any matches symbol: %s", rep)
}
return result, nil
}
func (m *Module) contains(addr uint64) (uint64, bool) {
for _, r := range m.Ranges {
if addr >= r.StartAddr && addr < r.EndAddr {
log.Debugf("found module %s could hanlde address: %d", m.Name, addr)
if m.Type == ModuleTypeSo || m.Type == ModuleTypeVDSO {
offset := addr - r.StartAddr + r.FileOffset
offset += m.SoAddr - m.SoOffset
log.Debugf("update address %d to offset %d", addr, offset)
return offset, true
}
return addr, true
}
}
return 0, false
}
func (m *Module) findAddr(offset uint64) *Symbol {
start := 0
end := len(m.Symbols) - 1
for start < end {
mid := start + (end-start)/2
result := int64(offset) - int64(m.Symbols[mid].Location)
if result < 0 {
end = mid
} else if result > 0 {
start = mid + 1
} else {
return m.Symbols[mid]
}
}
if start >= 1 && m.Symbols[start-1].Location < offset && offset < m.Symbols[start].Location {
return m.Symbols[start-1]
}
log.Debugf("could not found the address: %d in module %s", offset, m.Name)
return nil
}
func processSymbolName(name string) string {
if name == "" {
return ""
}
// fix process demangle symbol name, such as c++ language symbol
skip := 0
if name[0] == '.' || name[0] == '$' {
skip++
}
return demangle.Filter(name[skip:])
}