package profile
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"strings"
"time"
)
type Profile struct {
SampleType []*ValueType
DefaultSampleType string
Sample []*Sample
Mapping []*Mapping
Location []*Location
Function []*Function
Comments []string
DropFrames string
KeepFrames string
TimeNanos int64
DurationNanos int64
PeriodType *ValueType
Period int64
commentX []int64
dropFramesX int64
keepFramesX int64
stringTable []string
defaultSampleTypeX int64
}
type ValueType struct {
Type string
Unit string
typeX int64
unitX int64
}
type Sample struct {
Location []*Location
Value []int64
Label map [string ][]string
NumLabel map [string ][]int64
NumUnit map [string ][]string
locationIDX []uint64
labelX []Label
}
type Label struct {
keyX int64
strX int64
numX int64
}
type Mapping struct {
ID uint64
Start uint64
Limit uint64
Offset uint64
File string
BuildID string
HasFunctions bool
HasFilenames bool
HasLineNumbers bool
HasInlineFrames bool
fileX int64
buildIDX int64
}
type Location struct {
ID uint64
Mapping *Mapping
Address uint64
Line []Line
IsFolded bool
mappingIDX uint64
}
type Line struct {
Function *Function
Line int64
functionIDX uint64
}
type Function struct {
ID uint64
Name string
SystemName string
Filename string
StartLine int64
nameX int64
systemNameX int64
filenameX int64
}
func Parse (r io .Reader ) (*Profile , error ) {
orig , err := io .ReadAll (r )
if err != nil {
return nil , err
}
if len (orig ) >= 2 && orig [0 ] == 0x1f && orig [1 ] == 0x8b {
gz , err := gzip .NewReader (bytes .NewBuffer (orig ))
if err != nil {
return nil , fmt .Errorf ("decompressing profile: %v" , err )
}
data , err := io .ReadAll (gz )
if err != nil {
return nil , fmt .Errorf ("decompressing profile: %v" , err )
}
orig = data
}
p , err := parseUncompressed (orig )
if err != nil {
return nil , fmt .Errorf ("parsing profile: %w" , err )
}
if err := p .CheckValid (); err != nil {
return nil , fmt .Errorf ("malformed profile: %v" , err )
}
return p , nil
}
var errMalformed = fmt .Errorf ("malformed profile format" )
var ErrNoData = fmt .Errorf ("empty input file" )
func parseUncompressed(data []byte ) (*Profile , error ) {
if len (data ) == 0 {
return nil , ErrNoData
}
p := &Profile {}
if err := unmarshal (data , p ); err != nil {
return nil , err
}
if err := p .postDecode (); err != nil {
return nil , err
}
return p , nil
}
func (p *Profile ) Write (w io .Writer ) error {
p .preEncode ()
b := marshal (p )
zw := gzip .NewWriter (w )
defer zw .Close ()
_ , err := zw .Write (b )
return err
}
func (p *Profile ) CheckValid () error {
sampleLen := len (p .SampleType )
if sampleLen == 0 && len (p .Sample ) != 0 {
return fmt .Errorf ("missing sample type information" )
}
for _ , s := range p .Sample {
if len (s .Value ) != sampleLen {
return fmt .Errorf ("mismatch: sample has: %d values vs. %d types" , len (s .Value ), len (p .SampleType ))
}
}
mappings := make (map [uint64 ]*Mapping , len (p .Mapping ))
for _ , m := range p .Mapping {
if m .ID == 0 {
return fmt .Errorf ("found mapping with reserved ID=0" )
}
if mappings [m .ID ] != nil {
return fmt .Errorf ("multiple mappings with same id: %d" , m .ID )
}
mappings [m .ID ] = m
}
functions := make (map [uint64 ]*Function , len (p .Function ))
for _ , f := range p .Function {
if f .ID == 0 {
return fmt .Errorf ("found function with reserved ID=0" )
}
if functions [f .ID ] != nil {
return fmt .Errorf ("multiple functions with same id: %d" , f .ID )
}
functions [f .ID ] = f
}
locations := make (map [uint64 ]*Location , len (p .Location ))
for _ , l := range p .Location {
if l .ID == 0 {
return fmt .Errorf ("found location with reserved id=0" )
}
if locations [l .ID ] != nil {
return fmt .Errorf ("multiple locations with same id: %d" , l .ID )
}
locations [l .ID ] = l
if m := l .Mapping ; m != nil {
if m .ID == 0 || mappings [m .ID ] != m {
return fmt .Errorf ("inconsistent mapping %p: %d" , m , m .ID )
}
}
for _ , ln := range l .Line {
if f := ln .Function ; f != nil {
if f .ID == 0 || functions [f .ID ] != f {
return fmt .Errorf ("inconsistent function %p: %d" , f , f .ID )
}
}
}
}
return nil
}
func (p *Profile ) Aggregate (inlineFrame , function , filename , linenumber , address bool ) error {
for _ , m := range p .Mapping {
m .HasInlineFrames = m .HasInlineFrames && inlineFrame
m .HasFunctions = m .HasFunctions && function
m .HasFilenames = m .HasFilenames && filename
m .HasLineNumbers = m .HasLineNumbers && linenumber
}
if !function || !filename {
for _ , f := range p .Function {
if !function {
f .Name = ""
f .SystemName = ""
}
if !filename {
f .Filename = ""
}
}
}
if !inlineFrame || !address || !linenumber {
for _ , l := range p .Location {
if !inlineFrame && len (l .Line ) > 1 {
l .Line = l .Line [len (l .Line )-1 :]
}
if !linenumber {
for i := range l .Line {
l .Line [i ].Line = 0
}
}
if !address {
l .Address = 0
}
}
}
return p .CheckValid ()
}
func (p *Profile ) String () string {
ss := make ([]string , 0 , len (p .Sample )+len (p .Mapping )+len (p .Location ))
if pt := p .PeriodType ; pt != nil {
ss = append (ss , fmt .Sprintf ("PeriodType: %s %s" , pt .Type , pt .Unit ))
}
ss = append (ss , fmt .Sprintf ("Period: %d" , p .Period ))
if p .TimeNanos != 0 {
ss = append (ss , fmt .Sprintf ("Time: %v" , time .Unix (0 , p .TimeNanos )))
}
if p .DurationNanos != 0 {
ss = append (ss , fmt .Sprintf ("Duration: %v" , time .Duration (p .DurationNanos )))
}
ss = append (ss , "Samples:" )
var sh1 string
for _ , s := range p .SampleType {
sh1 = sh1 + fmt .Sprintf ("%s/%s " , s .Type , s .Unit )
}
ss = append (ss , strings .TrimSpace (sh1 ))
for _ , s := range p .Sample {
var sv string
for _ , v := range s .Value {
sv = fmt .Sprintf ("%s %10d" , sv , v )
}
sv = sv + ": "
for _ , l := range s .Location {
sv = sv + fmt .Sprintf ("%d " , l .ID )
}
ss = append (ss , sv )
const labelHeader = " "
if len (s .Label ) > 0 {
ls := labelHeader
for k , v := range s .Label {
ls = ls + fmt .Sprintf ("%s:%v " , k , v )
}
ss = append (ss , ls )
}
if len (s .NumLabel ) > 0 {
ls := labelHeader
for k , v := range s .NumLabel {
ls = ls + fmt .Sprintf ("%s:%v " , k , v )
}
ss = append (ss , ls )
}
}
ss = append (ss , "Locations" )
for _ , l := range p .Location {
locStr := fmt .Sprintf ("%6d: %#x " , l .ID , l .Address )
if m := l .Mapping ; m != nil {
locStr = locStr + fmt .Sprintf ("M=%d " , m .ID )
}
if len (l .Line ) == 0 {
ss = append (ss , locStr )
}
for li := range l .Line {
lnStr := "??"
if fn := l .Line [li ].Function ; fn != nil {
lnStr = fmt .Sprintf ("%s %s:%d s=%d" ,
fn .Name ,
fn .Filename ,
l .Line [li ].Line ,
fn .StartLine )
if fn .Name != fn .SystemName {
lnStr = lnStr + "(" + fn .SystemName + ")"
}
}
ss = append (ss , locStr +lnStr )
locStr = " "
}
}
ss = append (ss , "Mappings" )
for _ , m := range p .Mapping {
bits := ""
if m .HasFunctions {
bits += "[FN]"
}
if m .HasFilenames {
bits += "[FL]"
}
if m .HasLineNumbers {
bits += "[LN]"
}
if m .HasInlineFrames {
bits += "[IN]"
}
ss = append (ss , fmt .Sprintf ("%d: %#x/%#x/%#x %s %s %s" ,
m .ID ,
m .Start , m .Limit , m .Offset ,
m .File ,
m .BuildID ,
bits ))
}
return strings .Join (ss , "\n" ) + "\n"
}
func (p *Profile ) Merge (pb *Profile , r float64 ) error {
if err := p .Compatible (pb ); err != nil {
return err
}
pb = pb .Copy ()
if pb .Period > p .Period {
p .Period = pb .Period
}
p .DurationNanos += pb .DurationNanos
p .Mapping = append (p .Mapping , pb .Mapping ...)
for i , m := range p .Mapping {
m .ID = uint64 (i + 1 )
}
p .Location = append (p .Location , pb .Location ...)
for i , l := range p .Location {
l .ID = uint64 (i + 1 )
}
p .Function = append (p .Function , pb .Function ...)
for i , f := range p .Function {
f .ID = uint64 (i + 1 )
}
if r != 1.0 {
for _ , s := range pb .Sample {
for i , v := range s .Value {
s .Value [i ] = int64 ((float64 (v ) * r ))
}
}
}
p .Sample = append (p .Sample , pb .Sample ...)
return p .CheckValid ()
}
func (p *Profile ) Compatible (pb *Profile ) error {
if !compatibleValueTypes (p .PeriodType , pb .PeriodType ) {
return fmt .Errorf ("incompatible period types %v and %v" , p .PeriodType , pb .PeriodType )
}
if len (p .SampleType ) != len (pb .SampleType ) {
return fmt .Errorf ("incompatible sample types %v and %v" , p .SampleType , pb .SampleType )
}
for i := range p .SampleType {
if !compatibleValueTypes (p .SampleType [i ], pb .SampleType [i ]) {
return fmt .Errorf ("incompatible sample types %v and %v" , p .SampleType , pb .SampleType )
}
}
return nil
}
func (p *Profile ) HasFunctions () bool {
for _ , l := range p .Location {
if l .Mapping == nil || !l .Mapping .HasFunctions {
return false
}
}
return true
}
func (p *Profile ) HasFileLines () bool {
for _ , l := range p .Location {
if l .Mapping == nil || (!l .Mapping .HasFilenames || !l .Mapping .HasLineNumbers ) {
return false
}
}
return true
}
func compatibleValueTypes(v1 , v2 *ValueType ) bool {
if v1 == nil || v2 == nil {
return true
}
return v1 .Type == v2 .Type && v1 .Unit == v2 .Unit
}
func (p *Profile ) Copy () *Profile {
p .preEncode ()
b := marshal (p )
pp := &Profile {}
if err := unmarshal (b , pp ); err != nil {
panic (err )
}
if err := pp .postDecode (); err != nil {
panic (err )
}
return pp
}
type Demangler func (name []string ) (map [string ]string , error )
func (p *Profile ) Demangle (d Demangler ) error {
var names []string
for _ , fn := range p .Function {
names = append (names , fn .SystemName )
}
demangled , err := d (names )
if err != nil {
return err
}
for _ , fn := range p .Function {
if dd , ok := demangled [fn .SystemName ]; ok {
fn .Name = dd
}
}
return nil
}
func (p *Profile ) Empty () bool {
return len (p .Sample ) == 0
}
func (p *Profile ) Scale (ratio float64 ) {
if ratio == 1 {
return
}
ratios := make ([]float64 , len (p .SampleType ))
for i := range p .SampleType {
ratios [i ] = ratio
}
p .ScaleN (ratios )
}
func (p *Profile ) ScaleN (ratios []float64 ) error {
if len (p .SampleType ) != len (ratios ) {
return fmt .Errorf ("mismatched scale ratios, got %d, want %d" , len (ratios ), len (p .SampleType ))
}
allOnes := true
for _ , r := range ratios {
if r != 1 {
allOnes = false
break
}
}
if allOnes {
return nil
}
for _ , s := range p .Sample {
for i , v := range s .Value {
if ratios [i ] != 1 {
s .Value [i ] = int64 (float64 (v ) * ratios [i ])
}
}
}
return nil
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .