package profile
import (
)
var (
countStartRE = regexp.MustCompile(`\A(\w+) profile: total \d+\n\z`)
countRE = regexp.MustCompile(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`)
heapHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`)
heapSampleRE = regexp.MustCompile(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`)
contentionSampleRE = regexp.MustCompile(`(\d+) *(\d+) @([ x0-9a-f]*)`)
hexNumberRE = regexp.MustCompile(`0x[0-9a-f]+`)
growthHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`)
fragmentationHeaderRE = regexp.MustCompile(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`)
threadzStartRE = regexp.MustCompile(`--- threadz \d+ ---`)
threadStartRE = regexp.MustCompile(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`)
procMapsRE = regexp.MustCompile(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`)
briefMapsRE = regexp.MustCompile(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`)
LegacyHeapAllocated bool
)
func ( string) bool {
:= strings.TrimSpace()
return len() == 0 || [0] == '#'
}
func ( []byte) (*Profile, error) {
:= bytes.NewBuffer()
var string
var error
for {
, = .ReadString('\n')
if != nil {
return nil,
}
if !isSpaceOrComment() {
break
}
}
:= countStartRE.FindStringSubmatch()
if == nil {
return nil, errUnrecognized
}
:= [1]
:= &Profile{
PeriodType: &ValueType{Type: , Unit: "count"},
Period: 1,
SampleType: []*ValueType{{Type: , Unit: "count"}},
}
:= make(map[uint64]*Location)
for {
, = .ReadString('\n')
if != nil {
if == io.EOF {
break
}
return nil,
}
if isSpaceOrComment() {
continue
}
if strings.HasPrefix(, "---") {
break
}
:= countRE.FindStringSubmatch()
if == nil {
return nil, errMalformed
}
, := strconv.ParseInt([1], 0, 64)
if != nil {
return nil, errMalformed
}
:= strings.Fields([2])
:= make([]*Location, 0, len())
for , := range {
, := strconv.ParseUint(, 0, 64)
if != nil {
return nil, errMalformed
}
--
:= []
if == nil {
= &Location{
Address: ,
}
[] =
.Location = append(.Location, )
}
= append(, )
}
.Sample = append(.Sample, &Sample{
Location: ,
Value: []int64{},
})
}
if = parseAdditionalSections(strings.TrimSpace(), , ); != nil {
return nil,
}
return , nil
}
func ( *Profile) () {
:= make(map[*Location]bool, len(.Location))
var []*Location
for , := range .Sample {
for , := range .Location {
if [] {
continue
}
.ID = uint64(len() + 1)
= append(, )
[] = true
}
}
.Location =
}
func ( *Profile) () {
:= make(map[*Function]bool, len(.Function))
var []*Function
for , := range .Location {
for , := range .Line {
:= .Function
if == nil || [] {
continue
}
.ID = uint64(len() + 1)
= append(, )
[] = true
}
}
.Function =
}
func ( *Profile) () {
if len(.Mapping) == 0 {
return
}
if := .Mapping[0]; strings.HasPrefix(.File, "/anon_hugepage") {
if len(.Mapping) > 1 && .Limit == .Mapping[1].Start {
.Mapping = .Mapping[1:]
}
}
const = 0x400000
if := .Mapping[0]; .Start-.Offset == {
.Start =
.Offset = 0
}
for , := range .Location {
if := .Address; != 0 {
for , := range .Mapping {
if .Start <= && < .Limit {
.Mapping =
break
}
}
}
}
for , := range .Mapping {
.ID = uint64( + 1)
}
}
var cpuInts = []func([]byte) (uint64, []byte){
get32l,
get32b,
get64l,
get64b,
}
func ( []byte) (uint64, []byte) {
if len() < 4 {
return 0, nil
}
return uint64([0]) | uint64([1])<<8 | uint64([2])<<16 | uint64([3])<<24, [4:]
}
func ( []byte) (uint64, []byte) {
if len() < 4 {
return 0, nil
}
return uint64([3]) | uint64([2])<<8 | uint64([1])<<16 | uint64([0])<<24, [4:]
}
func ( []byte) (uint64, []byte) {
if len() < 8 {
return 0, nil
}
return uint64([0]) | uint64([1])<<8 | uint64([2])<<16 | uint64([3])<<24 | uint64([4])<<32 | uint64([5])<<40 | uint64([6])<<48 | uint64([7])<<56, [8:]
}
func ( []byte) (uint64, []byte) {
if len() < 8 {
return 0, nil
}
return uint64([7]) | uint64([6])<<8 | uint64([5])<<16 | uint64([4])<<24 | uint64([3])<<32 | uint64([2])<<40 | uint64([1])<<48 | uint64([0])<<56, [8:]
}
func ( []byte) (*Profile, error) {
:= bytes.NewBuffer()
:= &Profile{
PeriodType: &ValueType{Type: "trace", Unit: "count"},
Period: 1,
SampleType: []*ValueType{
{Type: "trace", Unit: "count"},
},
}
var []string
var []*Location
:= make(map[uint64]*Location)
for {
, := .ReadString('\n')
if != nil {
if != io.EOF {
return nil,
}
if == "" {
break
}
}
if sectionTrigger() == memoryMapSection {
break
}
if , := extractHexAddresses(); len() > 0 {
for , := range {
--
:= []
if [] == nil {
= &Location{
Address: ,
}
.Location = append(.Location, )
[] =
}
= append(, )
}
= append(, ...)
} else {
if len() > 0 || len() > 0 {
addTracebackSample(, , )
, = nil, nil
}
}
}
if len() > 0 || len() > 0 {
addTracebackSample(, , )
}
if := .ParseMemoryMap(); != nil {
return nil,
}
return , nil
}
func ( []*Location, []string, *Profile) {
.Sample = append(.Sample,
&Sample{
Value: []int64{1},
Location: ,
Label: map[string][]string{"source": },
})
}
func ( []byte) (*Profile, error) {
var func([]byte) (uint64, []byte)
var , , , , uint64
for _, = range cpuInts {
var []byte
, = ()
, = ()
, = ()
, = ()
, = ()
if != nil && == 0 && == 3 && == 0 && > 0 && == 0 {
=
return cpuProfile(, int64(), )
}
}
return nil, errUnrecognized
}
func ( []byte, int64, func( []byte) (uint64, []byte)) (*Profile, error) {
:= &Profile{
Period: * 1000,
PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},
SampleType: []*ValueType{
{Type: "samples", Unit: "count"},
{Type: "cpu", Unit: "nanoseconds"},
},
}
var error
if , _, = parseCPUSamples(, , true, ); != nil {
return nil,
}
if len(.Sample) > 1 && len(.Sample[0].Location) > 1 {
:= true
:= .Sample[0].Location[1].Address
for , := range .Sample {
if len(.Location) < 2 || != .Location[1].Address {
= false
break
}
}
if {
for , := range .Sample {
.Location = append(.Location[:1], .Location[2:]...)
}
}
}
if := .ParseMemoryMap(bytes.NewBuffer()); != nil {
return nil,
}
return , nil
}
func ( []byte, func( []byte) (uint64, []byte), bool, *Profile) ([]byte, map[uint64]*Location, error) {
:= make(map[uint64]*Location)
for len() > 0 {
var , uint64
, = ()
, = ()
if == nil || > uint64(len()/4) {
return nil, nil, errUnrecognized
}
var []*Location
:= make([]uint64, )
for := 0; < int(); ++ {
[], = ()
}
if == 0 && == 1 && [0] == 0 {
break
}
for , := range {
if && > 0 {
--
}
:= []
if == nil {
= &Location{
Address: ,
}
[] =
.Location = append(.Location, )
}
= append(, )
}
.Sample = append(.Sample,
&Sample{
Value: []int64{int64(), int64() * .Period},
Location: ,
})
}
return , , nil
}
func ( []byte) ( *Profile, error) {
:= bytes.NewBuffer()
, := .ReadString('\n')
if != nil {
return nil, errUnrecognized
}
:= ""
if := heapHeaderRE.FindStringSubmatch(); != nil {
= &Profile{
SampleType: []*ValueType{
{Type: "objects", Unit: "count"},
{Type: "space", Unit: "bytes"},
},
PeriodType: &ValueType{Type: "objects", Unit: "bytes"},
}
var int64
if len([6]) > 0 {
if , = strconv.ParseInt([6], 10, 64); != nil {
return nil, errUnrecognized
}
}
switch [5] {
case "heapz_v2", "heap_v2":
, .Period = "v2",
case "heapprofile":
, .Period = "", 1
case "heap":
, .Period = "v2", /2
default:
return nil, errUnrecognized
}
} else if = growthHeaderRE.FindStringSubmatch(); != nil {
= &Profile{
SampleType: []*ValueType{
{Type: "objects", Unit: "count"},
{Type: "space", Unit: "bytes"},
},
PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"},
Period: 1,
}
} else if = fragmentationHeaderRE.FindStringSubmatch(); != nil {
= &Profile{
SampleType: []*ValueType{
{Type: "objects", Unit: "count"},
{Type: "space", Unit: "bytes"},
},
PeriodType: &ValueType{Type: "allocations", Unit: "count"},
Period: 1,
}
} else {
return nil, errUnrecognized
}
if LegacyHeapAllocated {
for , := range .SampleType {
.Type = "alloc_" + .Type
}
} else {
for , := range .SampleType {
.Type = "inuse_" + .Type
}
}
:= make(map[uint64]*Location)
for {
, = .ReadString('\n')
if != nil {
if != io.EOF {
return nil,
}
if == "" {
break
}
}
if isSpaceOrComment() {
continue
}
= strings.TrimSpace()
if sectionTrigger() != unrecognizedSection {
break
}
, , , := parseHeapSample(, .Period, )
if != nil {
return nil,
}
var []*Location
for , := range {
--
:= []
if [] == nil {
= &Location{
Address: ,
}
.Location = append(.Location, )
[] =
}
= append(, )
}
.Sample = append(.Sample, &Sample{
Value: ,
Location: ,
NumLabel: map[string][]int64{"bytes": {}},
})
}
if = parseAdditionalSections(, , ); != nil {
return nil,
}
return , nil
}
func ( string, int64, string) ( []int64, int64, []uint64, error) {
:= heapSampleRE.FindStringSubmatch()
if len() != 6 {
return , , , fmt.Errorf("unexpected number of sample values: got %d, want 6", len())
}
:= 1
if LegacyHeapAllocated {
= 3
}
var , int64
if , = strconv.ParseInt([], 10, 64); != nil {
return , , , fmt.Errorf("malformed sample: %s: %v", , )
}
if , = strconv.ParseInt([+1], 10, 64); != nil {
return , , , fmt.Errorf("malformed sample: %s: %v", , )
}
if == 0 {
if != 0 {
return , , , fmt.Errorf("allocation count was 0 but allocation bytes was %d", )
}
} else {
= /
if == "v2" {
, = scaleHeapSample(, , )
}
}
= []int64{, }
= parseHexAddresses([5])
return , , , nil
}
func ( string) ([]string, []uint64) {
:= hexNumberRE.FindAllString(, -1)
var []uint64
for , := range {
if , := strconv.ParseUint(, 0, 64); == nil {
= append(, )
} else {
panic("failed to parse hex value:" + )
}
}
return ,
}
func ( string) []uint64 {
, := extractHexAddresses()
return
}
func (, , int64) (int64, int64) {
if == 0 || == 0 {
return 0, 0
}
if <= 1 {
return ,
}
:= float64() / float64()
:= 1 / (1 - math.Exp(-/float64()))
return int64(float64() * ), int64(float64() * )
}
func ( []byte) (*Profile, error) {
:= bytes.NewBuffer()
var string
var error
for {
, = .ReadString('\n')
if != nil {
return nil,
}
if !isSpaceOrComment() {
break
}
}
if strings.HasPrefix(, "--- contentionz ") {
return parseCppContention()
} else if strings.HasPrefix(, "--- mutex:") {
return parseCppContention()
} else if strings.HasPrefix(, "--- contention:") {
return parseCppContention()
}
return nil, errUnrecognized
}
func ( *bytes.Buffer) (*Profile, error) {
:= &Profile{
PeriodType: &ValueType{Type: "contentions", Unit: "count"},
Period: 1,
SampleType: []*ValueType{
{Type: "contentions", Unit: "count"},
{Type: "delay", Unit: "nanoseconds"},
},
}
var int64
var string
var error
const = "="
for {
, = .ReadString('\n')
if != nil {
if != io.EOF {
return nil,
}
if == "" {
break
}
}
if isSpaceOrComment() {
continue
}
if = strings.TrimSpace(); == "" {
continue
}
if strings.HasPrefix(, "---") {
break
}
, , := strings.Cut(, )
if ! {
break
}
, = strings.TrimSpace(), strings.TrimSpace()
var error
switch {
case "cycles/second":
if , = strconv.ParseInt(, 0, 64); != nil {
return nil, errUnrecognized
}
case "sampling period":
if .Period, = strconv.ParseInt(, 0, 64); != nil {
return nil, errUnrecognized
}
case "ms since reset":
, := strconv.ParseInt(, 0, 64)
if != nil {
return nil, errUnrecognized
}
.DurationNanos = * 1000 * 1000
case "format":
return nil, errUnrecognized
case "resolution":
return nil, errUnrecognized
case "discarded samples":
default:
return nil, errUnrecognized
}
}
:= make(map[uint64]*Location)
for {
if !isSpaceOrComment() {
if = strings.TrimSpace(); strings.HasPrefix(, "---") {
break
}
, , := parseContentionSample(, .Period, )
if != nil {
return nil,
}
var []*Location
for , := range {
--
:= []
if [] == nil {
= &Location{
Address: ,
}
.Location = append(.Location, )
[] =
}
= append(, )
}
.Sample = append(.Sample, &Sample{
Value: ,
Location: ,
})
}
if , = .ReadString('\n'); != nil {
if != io.EOF {
return nil,
}
if == "" {
break
}
}
}
if = parseAdditionalSections(, , ); != nil {
return nil,
}
return , nil
}
func ( string, , int64) ( []int64, []uint64, error) {
:= contentionSampleRE.FindStringSubmatch()
if == nil {
return , , errUnrecognized
}
, := strconv.ParseInt([1], 10, 64)
if != nil {
return , , fmt.Errorf("malformed sample: %s: %v", , )
}
, := strconv.ParseInt([2], 10, 64)
if != nil {
return , , fmt.Errorf("malformed sample: %s: %v", , )
}
if > 0 {
if > 0 {
:= float64() / 1e9
= int64(float64() * float64() / )
}
= *
}
= []int64{, }
= parseHexAddresses([3])
return , , nil
}
func ( []byte) (*Profile, error) {
:= bytes.NewBuffer()
var string
var error
for {
, = .ReadString('\n')
if != nil {
return nil,
}
if !isSpaceOrComment() {
break
}
}
if := threadzStartRE.FindStringSubmatch(); != nil {
for {
, = .ReadString('\n')
if != nil {
if != io.EOF {
return nil,
}
if == "" {
break
}
}
if sectionTrigger() != unrecognizedSection || [0] == '-' {
break
}
}
} else if := threadStartRE.FindStringSubmatch(); len() != 4 {
return nil, errUnrecognized
}
:= &Profile{
SampleType: []*ValueType{{Type: "thread", Unit: "count"}},
PeriodType: &ValueType{Type: "thread", Unit: "count"},
Period: 1,
}
:= make(map[uint64]*Location)
for sectionTrigger() == unrecognizedSection {
if strings.HasPrefix(, "---- no stack trace for") {
= ""
break
}
if := threadStartRE.FindStringSubmatch(); len() != 4 {
return nil, errUnrecognized
}
var []uint64
, , = parseThreadSample()
if != nil {
return nil, errUnrecognized
}
if len() == 0 {
if len(.Sample) > 0 {
:= .Sample[len(.Sample)-1]
.Value[0]++
}
continue
}
var []*Location
for , := range {
--
:= []
if [] == nil {
= &Location{
Address: ,
}
.Location = append(.Location, )
[] =
}
= append(, )
}
.Sample = append(.Sample, &Sample{
Value: []int64{1},
Location: ,
})
}
if = parseAdditionalSections(, , ); != nil {
return nil,
}
return , nil
}
func ( *bytes.Buffer) ( string, []uint64, error) {
var string
:= false
for {
if , = .ReadString('\n'); != nil {
if != io.EOF {
return "", nil,
}
if == "" {
break
}
}
if = strings.TrimSpace(); == "" {
continue
}
if strings.HasPrefix(, "---") {
break
}
if strings.Contains(, "same as previous thread") {
= true
continue
}
= append(, parseHexAddresses()...)
}
if {
return , nil, nil
}
return , , nil
}
func ( string, *bytes.Buffer, *Profile) ( error) {
for {
if sectionTrigger() == memoryMapSection {
break
}
if , := .ReadString('\n'); != nil {
if != io.EOF {
return
}
if == "" {
break
}
}
}
return .ParseMemoryMap()
}
func ( *Profile) ( io.Reader) error {
:= bufio.NewReader()
var []string
var *strings.Replacer
const = "="
for {
, := .ReadString('\n')
if != nil {
if != io.EOF {
return
}
if == "" {
break
}
}
if = strings.TrimSpace(); == "" {
continue
}
if != nil {
= .Replace()
}
, := parseMappingEntry()
if != nil {
if == errUnrecognized {
if , , := strings.Cut(, ); {
= append(, "$"+strings.TrimSpace(), strings.TrimSpace())
= strings.NewReplacer(...)
}
continue
}
return
}
if == nil || (.File == "" && len(.Mapping) != 0) {
continue
}
if len(.Mapping) == 1 && .Mapping[0].File == "" {
.Mapping[0].File = .File
continue
}
.Mapping = append(.Mapping, )
}
.remapLocationIDs()
.remapFunctionIDs()
.remapMappingIDs()
return nil
}
func ( string) (*Mapping, error) {
:= &Mapping{}
var error
if := procMapsRE.FindStringSubmatch(); len() == 9 {
if !strings.Contains([3], "x") {
return nil, nil
}
if .Start, = strconv.ParseUint([1], 16, 64); != nil {
return nil, errUnrecognized
}
if .Limit, = strconv.ParseUint([2], 16, 64); != nil {
return nil, errUnrecognized
}
if [4] != "" {
if .Offset, = strconv.ParseUint([4], 16, 64); != nil {
return nil, errUnrecognized
}
}
.File = [8]
return , nil
}
if := briefMapsRE.FindStringSubmatch(); len() == 6 {
if .Start, = strconv.ParseUint([1], 16, 64); != nil {
return nil, errUnrecognized
}
if .Limit, = strconv.ParseUint([2], 16, 64); != nil {
return nil, errUnrecognized
}
.File = [3]
if [5] != "" {
if .Offset, = strconv.ParseUint([5], 16, 64); != nil {
return nil, errUnrecognized
}
}
return , nil
}
return nil, errUnrecognized
}
type sectionType int
const (
unrecognizedSection sectionType = iota
memoryMapSection
)
var memoryMapTriggers = []string{
"--- Memory map: ---",
"MAPPED_LIBRARIES:",
}
func ( string) sectionType {
for , := range memoryMapTriggers {
if strings.Contains(, ) {
return memoryMapSection
}
}
return unrecognizedSection
}
func ( *Profile) () {
switch {
case isProfileType(, heapzSampleTypes) ||
isProfileType(, heapzInUseSampleTypes) ||
isProfileType(, heapzAllocSampleTypes):
.DropFrames, .KeepFrames = allocRxStr, allocSkipRxStr
case isProfileType(, contentionzSampleTypes):
.DropFrames, .KeepFrames = lockRxStr, ""
default:
.DropFrames, .KeepFrames = cpuProfilerRxStr, ""
}
}
var heapzSampleTypes = []string{"allocations", "size"}
var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"}
var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"}
var contentionzSampleTypes = []string{"contentions", "delay"}
func ( *Profile, []string) bool {
:= .SampleType
if len() != len() {
return false
}
for := range {
if [].Type != [] {
return false
}
}
return true
}
var allocRxStr = strings.Join([]string{
`calloc`,
`cfree`,
`malloc`,
`free`,
`memalign`,
`do_memalign`,
`(__)?posix_memalign`,
`pvalloc`,
`valloc`,
`realloc`,
`tcmalloc::.*`,
`tc_calloc`,
`tc_cfree`,
`tc_malloc`,
`tc_free`,
`tc_memalign`,
`tc_posix_memalign`,
`tc_pvalloc`,
`tc_valloc`,
`tc_realloc`,
`tc_new`,
`tc_delete`,
`tc_newarray`,
`tc_deletearray`,
`tc_new_nothrow`,
`tc_newarray_nothrow`,
`malloc_zone_malloc`,
`malloc_zone_calloc`,
`malloc_zone_valloc`,
`malloc_zone_realloc`,
`malloc_zone_memalign`,
`malloc_zone_free`,
`runtime\..*`,
`BaseArena::.*`,
`(::)?do_malloc_no_errno`,
`(::)?do_malloc_pages`,
`(::)?do_malloc`,
`DoSampledAllocation`,
`MallocedMemBlock::MallocedMemBlock`,
`_M_allocate`,
`__builtin_(vec_)?delete`,
`__builtin_(vec_)?new`,
`__gnu_cxx::new_allocator::allocate`,
`__libc_malloc`,
`__malloc_alloc_template::allocate`,
`allocate`,
`cpp_alloc`,
`operator new(\[\])?`,
`simple_alloc::allocate`,
}, `|`)
var allocSkipRxStr = strings.Join([]string{
`runtime\.panic`,
`runtime\.reflectcall`,
`runtime\.call[0-9]*`,
}, `|`)
var cpuProfilerRxStr = strings.Join([]string{
`ProfileData::Add`,
`ProfileData::prof_handler`,
`CpuProfiler::prof_handler`,
`__pthread_sighandler`,
`__restore`,
}, `|`)
var lockRxStr = strings.Join([]string{
`RecordLockProfileData`,
`(base::)?RecordLockProfileData.*`,
`(base::)?SubmitMutexProfileData.*`,
`(base::)?SubmitSpinLockProfileData.*`,
`(Mutex::)?AwaitCommon.*`,
`(Mutex::)?Unlock.*`,
`(Mutex::)?UnlockSlow.*`,
`(Mutex::)?ReaderUnlock.*`,
`(MutexLock::)?~MutexLock.*`,
`(SpinLock::)?Unlock.*`,
`(SpinLock::)?SlowUnlock.*`,
`(SpinLockHolder::)?~SpinLockHolder.*`,
}, `|`)