package profile
import (
"bytes"
"fmt"
"io"
"path/filepath"
"regexp"
"strconv"
"strings"
)
var (
attributeRx = regexp .MustCompile (`([\w ]+)=([\w ]+)` )
javaSampleRx = regexp .MustCompile (` *(\d+) +(\d+) +@ +([ x0-9a-f]*)` )
javaLocationRx = regexp .MustCompile (`^\s*0x([[:xdigit:]]+)\s+(.*)\s*$` )
javaLocationFileLineRx = regexp .MustCompile (`^(.*)\s+\((.+):(-?[[:digit:]]+)\)$` )
javaLocationPathRx = regexp .MustCompile (`^(.*)\s+\((.*)\)$` )
)
func javaCPUProfile(b []byte , period int64 , parse func (b []byte ) (uint64 , []byte )) (*Profile , error ) {
p := &Profile {
Period : period * 1000 ,
PeriodType : &ValueType {Type : "cpu" , Unit : "nanoseconds" },
SampleType : []*ValueType {{Type : "samples" , Unit : "count" }, {Type : "cpu" , Unit : "nanoseconds" }},
}
var err error
var locs map [uint64 ]*Location
if b , locs , err = parseCPUSamples (b , parse , false , p ); err != nil {
return nil , err
}
if err = parseJavaLocations (b , locs , p ); err != nil {
return nil , err
}
if err = p .Aggregate (true , true , true , true , false , false ); err != nil {
return nil , err
}
return p , nil
}
func parseJavaProfile(b []byte ) (*Profile , error ) {
h := bytes .SplitAfterN (b , []byte ("\n" ), 2 )
if len (h ) < 2 {
return nil , errUnrecognized
}
p := &Profile {
PeriodType : &ValueType {},
}
header := string (bytes .TrimSpace (h [0 ]))
var err error
var pType string
switch header {
case "--- heapz 1 ---" :
pType = "heap"
case "--- contentionz 1 ---" :
pType = "contention"
default :
return nil , errUnrecognized
}
if b , err = parseJavaHeader (pType , h [1 ], p ); err != nil {
return nil , err
}
var locs map [uint64 ]*Location
if b , locs , err = parseJavaSamples (pType , b , p ); err != nil {
return nil , err
}
if err = parseJavaLocations (b , locs , p ); err != nil {
return nil , err
}
if err = p .Aggregate (true , true , true , true , false , false ); err != nil {
return nil , err
}
return p , nil
}
func parseJavaHeader(pType string , b []byte , p *Profile ) ([]byte , error ) {
nextNewLine := bytes .IndexByte (b , byte ('\n' ))
for nextNewLine != -1 {
line := string (bytes .TrimSpace (b [0 :nextNewLine ]))
if line != "" {
h := attributeRx .FindStringSubmatch (line )
if h == nil {
return b , nil
}
attribute , value := strings .TrimSpace (h [1 ]), strings .TrimSpace (h [2 ])
var err error
switch pType + "/" + attribute {
case "heap/format" , "cpu/format" , "contention/format" :
if value != "java" {
return nil , errUnrecognized
}
case "heap/resolution" :
p .SampleType = []*ValueType {
{Type : "inuse_objects" , Unit : "count" },
{Type : "inuse_space" , Unit : value },
}
case "contention/resolution" :
p .SampleType = []*ValueType {
{Type : "contentions" , Unit : "count" },
{Type : "delay" , Unit : value },
}
case "contention/sampling period" :
p .PeriodType = &ValueType {
Type : "contentions" , Unit : "count" ,
}
if p .Period , err = strconv .ParseInt (value , 0 , 64 ); err != nil {
return nil , fmt .Errorf ("failed to parse attribute %s: %v" , line , err )
}
case "contention/ms since reset" :
millis , err := strconv .ParseInt (value , 0 , 64 )
if err != nil {
return nil , fmt .Errorf ("failed to parse attribute %s: %v" , line , err )
}
p .DurationNanos = millis * 1000 * 1000
default :
return nil , errUnrecognized
}
}
b = b [nextNewLine +1 :]
nextNewLine = bytes .IndexByte (b , byte ('\n' ))
}
return b , nil
}
func parseJavaSamples(pType string , b []byte , p *Profile ) ([]byte , map [uint64 ]*Location , error ) {
nextNewLine := bytes .IndexByte (b , byte ('\n' ))
locs := make (map [uint64 ]*Location )
for nextNewLine != -1 {
line := string (bytes .TrimSpace (b [0 :nextNewLine ]))
if line != "" {
sample := javaSampleRx .FindStringSubmatch (line )
if sample == nil {
return b , locs , nil
}
var err error
value1 , value2 , value3 := sample [2 ], sample [1 ], sample [3 ]
addrs , err := parseHexAddresses (value3 )
if err != nil {
return nil , nil , fmt .Errorf ("malformed sample: %s: %v" , line , err )
}
var sloc []*Location
for _ , addr := range addrs {
loc := locs [addr ]
if locs [addr ] == nil {
loc = &Location {
Address : addr ,
}
p .Location = append (p .Location , loc )
locs [addr ] = loc
}
sloc = append (sloc , loc )
}
s := &Sample {
Value : make ([]int64 , 2 ),
Location : sloc ,
}
if s .Value [0 ], err = strconv .ParseInt (value1 , 0 , 64 ); err != nil {
return nil , nil , fmt .Errorf ("parsing sample %s: %v" , line , err )
}
if s .Value [1 ], err = strconv .ParseInt (value2 , 0 , 64 ); err != nil {
return nil , nil , fmt .Errorf ("parsing sample %s: %v" , line , err )
}
switch pType {
case "heap" :
const javaHeapzSamplingRate = 524288
if s .Value [0 ] == 0 {
return nil , nil , fmt .Errorf ("parsing sample %s: second value must be non-zero" , line )
}
s .NumLabel = map [string ][]int64 {"bytes" : {s .Value [1 ] / s .Value [0 ]}}
s .Value [0 ], s .Value [1 ] = scaleHeapSample (s .Value [0 ], s .Value [1 ], javaHeapzSamplingRate )
case "contention" :
if period := p .Period ; period != 0 {
s .Value [0 ] = s .Value [0 ] * p .Period
s .Value [1 ] = s .Value [1 ] * p .Period
}
}
p .Sample = append (p .Sample , s )
}
b = b [nextNewLine +1 :]
nextNewLine = bytes .IndexByte (b , byte ('\n' ))
}
return b , locs , nil
}
func parseJavaLocations(b []byte , locs map [uint64 ]*Location , p *Profile ) error {
r := bytes .NewBuffer (b )
fns := make (map [string ]*Function )
for {
line , err := r .ReadString ('\n' )
if err != nil {
if err != io .EOF {
return err
}
if line == "" {
break
}
}
if line = strings .TrimSpace (line ); line == "" {
continue
}
jloc := javaLocationRx .FindStringSubmatch (line )
if len (jloc ) != 3 {
continue
}
addr , err := strconv .ParseUint (jloc [1 ], 16 , 64 )
if err != nil {
return fmt .Errorf ("parsing sample %s: %v" , line , err )
}
loc := locs [addr ]
if loc == nil {
continue
}
var lineFunc , lineFile string
var lineNo int64
if fileLine := javaLocationFileLineRx .FindStringSubmatch (jloc [2 ]); len (fileLine ) == 4 {
lineFunc , lineFile = fileLine [1 ], fileLine [2 ]
if n , err := strconv .ParseInt (fileLine [3 ], 10 , 64 ); err == nil && n > 0 {
lineNo = n
}
} else if filePath := javaLocationPathRx .FindStringSubmatch (jloc [2 ]); len (filePath ) == 3 {
lineFunc , lineFile = filePath [1 ], filepath .Base (filePath [2 ])
} else if strings .Contains (jloc [2 ], "generated stub/JIT" ) {
lineFunc = "STUB"
} else {
lineFunc = jloc [2 ]
}
fn := fns [lineFunc ]
if fn == nil {
fn = &Function {
Name : lineFunc ,
SystemName : lineFunc ,
Filename : lineFile ,
}
fns [lineFunc ] = fn
p .Function = append (p .Function , fn )
}
loc .Line = []Line {
{
Function : fn ,
Line : lineNo ,
},
}
loc .Address = 0
}
p .remapLocationIDs ()
p .remapFunctionIDs ()
p .remapMappingIDs ()
return nil
}
The pages are generated with Golds v0.8.2 . (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 .