Skip to content
This repository has been archived by the owner on Oct 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #8 from tideways/dev
Browse files Browse the repository at this point in the history
Add --function flag to analyze-xhprof
  • Loading branch information
beberlei authored Jan 18, 2018
2 parents 3b1cf98 + 6f09a82 commit 0d8c56c
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 37 deletions.
6 changes: 4 additions & 2 deletions cmd/analyze-callgrind.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func analyzeCallgrind(cmd *cobra.Command, args []string) error {
profiles = append(profiles, profile)
}

avgProfile := xhprof.AvgProfiles(profiles)
profile := xhprof.AvgProfiles(profiles)

fieldInfo, ok := fieldsMap[field]
if !ok {
Expand All @@ -44,7 +44,9 @@ func analyzeCallgrind(cmd *cobra.Command, args []string) error {
}

minPercent = minPercent / 100.0
err := renderProfile(avgProfile, field, fieldInfo, minPercent)
main := profile.GetMain()
minValue := minPercent * main.GetFloat32Field(fieldInfo.Name)
err := renderProfile(profile, field, fieldInfo, minValue)
if err != nil {
return err
}
Expand Down
56 changes: 42 additions & 14 deletions cmd/analyze-xhprof.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"errors"
"fmt"

"github.com/tideways/toolkit/xhprof"
Expand All @@ -13,12 +14,14 @@ func init() {
xhprofCmd.Flags().StringVarP(&field, "field", "f", "excl_wt", "Field to view/sort (wt, excl_wt, cpu, excl_cpu, memory, excl_memory, io, excl_io)")
xhprofCmd.Flags().Float32VarP(&minPercent, "min", "m", 1, "Display items having minimum percentage (default 1%) of --field, with respect to main()")
xhprofCmd.Flags().StringVarP(&outFile, "out-file", "o", "", "If provided, the path to store the resulting profile (e.g. after averaging)")
xhprofCmd.Flags().StringVarP(&function, "function", "", "", "If provided, one table for parents, and one for children of this function will be displayed")
}

var (
field string
minPercent float32
outFile string
function string
)

var xhprofCmd = &cobra.Command{
Expand Down Expand Up @@ -51,22 +54,47 @@ func analyzeXhprof(cmd *cobra.Command, args []string) error {
}
}

profile, err := avgMap.Flatten()
if err != nil {
return err
}
profile := avgMap.Flatten()
minPercent = minPercent / 100.0
if function == "" {
fieldInfo, ok := fieldsMap[field]
if !ok {
fmt.Printf("Provided field (%s) is not valid, defaulting to excl_wt\n", field)
field = "excl_wt"
fieldInfo = fieldsMap[field]
}

fieldInfo, ok := fieldsMap[field]
if !ok {
fmt.Printf("Provided field (%s) is not valid, defaulting to excl_wt\n", field)
field = "excl_wt"
fieldInfo = fieldsMap[field]
}
main := profile.GetMain()
minValue := minPercent * main.GetFloat32Field(fieldInfo.Name)
err := renderProfile(profile, field, fieldInfo, minValue)
if err != nil {
return err
}
} else {
family := avgMap.ComputeNearestFamily(function)
parentsProfile := family.Parents.Flatten()
childrenProfile := family.Children.Flatten()

minPercent = minPercent / 100.0
err = renderProfile(profile, field, fieldInfo, minPercent)
if err != nil {
return err
field = "wt"
fieldInfo := fieldsMap[field]

functionCall := profile.GetCall(function)
if functionCall == nil {
return errors.New("Profile doesn't contain function")
}
minValue := minPercent * functionCall.GetFloat32Field(fieldInfo.Name)

fmt.Printf("Parents of %s:\n", function)
err := renderProfile(parentsProfile, field, fieldInfo, minValue)
if err != nil {
return err
}

fmt.Printf("Children of %s:\n", function)
err = renderProfile(childrenProfile, field, fieldInfo, minValue)
if err != nil {
return err
}
}

return nil
Expand Down
5 changes: 1 addition & 4 deletions cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,9 @@ var fieldsMap map[string]FieldInfo = map[string]FieldInfo{
},
}

func renderProfile(profile *xhprof.Profile, field string, fieldInfo FieldInfo, minPercent float32) error {
func renderProfile(profile *xhprof.Profile, field string, fieldInfo FieldInfo, minValue float32) error {
profile.SortBy(fieldInfo.Name)

main := profile.GetMain()
minValue := minPercent * main.GetFloat32Field(fieldInfo.Name)

var fields []FieldInfo
if strings.HasPrefix(field, "excl_") {
fields = []FieldInfo{fieldsMap[strings.TrimPrefix(field, "excl_")], fieldInfo}
Expand Down
2 changes: 1 addition & 1 deletion xhprof/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (f *File) GetProfile() (*Profile, error) {
return nil, err
}

return m.Flatten()
return m.Flatten(), nil
}

func (f *File) GetPairCallMap() (m *PairCallMap, err error) {
Expand Down
79 changes: 65 additions & 14 deletions xhprof/paircall.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package xhprof

import (
"errors"
"strings"
)

Expand Down Expand Up @@ -33,6 +32,21 @@ func (p *PairCall) Divide(d float32) *PairCall {
return p
}

type NearestFamily struct {
Children *PairCallMap
Parents *PairCallMap
ChildrenCount int
ParentsCount int
}

func NewNearestFamily() *NearestFamily {
f := new(NearestFamily)
f.Children = NewPairCallMap()
f.Parents = NewPairCallMap()

return f
}

type PairCallMap struct {
M map[string]*PairCall
}
Expand All @@ -44,20 +58,13 @@ func NewPairCallMap() *PairCallMap {
return m
}

func (m *PairCallMap) Flatten() (*Profile, error) {
func (m *PairCallMap) Flatten() *Profile {
var parent string
var child string

symbols := make(map[string]*Call)
for name, info := range m.M {
fns := strings.Split(name, "==>")
if len(fns) == 2 {
parent = fns[0]
child = fns[1]
} else {
parent = ""
child = fns[0]
}
parent, child = parsePairName(name)

call, ok := symbols[child]
if !ok {
Expand Down Expand Up @@ -87,12 +94,44 @@ func (m *PairCallMap) Flatten() (*Profile, error) {
profile.Calls = calls

main, ok := symbols["main()"]
if !ok || main == nil {
return nil, errors.New("Profile has no main()")
if ok {
profile.Main = main
}
profile.Main = main

return profile, nil
return profile
}

func (m *PairCallMap) ComputeNearestFamily(f string) *NearestFamily {
family := NewNearestFamily()

for name, info := range m.M {
parent, child := parsePairName(name)
if parent == f {
c, ok := family.Children.M[child]
if !ok {
c = new(PairCall)
family.Children.M[child] = c
}

c.WallTime += info.WallTime
c.Count += info.Count
family.ChildrenCount += info.Count
}

if child == f && parent != "" {
p, ok := family.Parents.M[parent]
if !ok {
p = new(PairCall)
family.Parents.M[parent] = p
}

p.WallTime += info.WallTime
p.Count += info.Count
family.ParentsCount += info.Count
}
}

return family
}

func AvgPairCallMaps(maps []*PairCallMap) *PairCallMap {
Expand Down Expand Up @@ -123,3 +162,15 @@ func AvgPairCallMaps(maps []*PairCallMap) *PairCallMap {

return res
}

func parsePairName(name string) (parent string, child string) {
fns := strings.Split(name, "==>")
if len(fns) == 2 {
parent = fns[0]
child = fns[1]
} else {
child = fns[0]
}

return
}
78 changes: 76 additions & 2 deletions xhprof/paircall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,31 @@ import (
"github.com/stretchr/testify/require"
)

func TestParsePairName(t *testing.T) {
cases := []struct {
Name string
Parent string
Child string
}{
{
Name: "main()",
Parent: "",
Child: "main()",
},
{
Name: "main()==>foo",
Parent: "main()",
Child: "foo",
},
}

for _, c := range cases {
parent, child := parsePairName(c.Name)
assert.Equal(t, c.Parent, parent)
assert.Equal(t, c.Child, child)
}
}

func TestFlatten(t *testing.T) {
expected := &Profile{
Calls: []*Call{
Expand Down Expand Up @@ -72,8 +97,7 @@ func TestFlatten(t *testing.T) {
},
}

profile, err := m.Flatten()
require.Nil(t, err)
profile := m.Flatten()
require.IsType(t, profile, expected)

profile.SortBy("WallTime")
Expand Down Expand Up @@ -157,3 +181,53 @@ func TestAvgPairCallMaps(t *testing.T) {
res := AvgPairCallMaps([]*PairCallMap{m1, m2, m3})
assert.EqualValues(t, expected, res)
}

func TestComputeNearestFamily(t *testing.T) {
expected := &NearestFamily{
Children: &PairCallMap{
M: map[string]*PairCall{
"bar": &PairCall{
WallTime: 200,
Count: 10,
},
},
},
Parents: &PairCallMap{
M: map[string]*PairCall{
"main()": &PairCall{
WallTime: 500,
Count: 2,
},
},
},
ChildrenCount: 10,
ParentsCount: 2,
}

m := &PairCallMap{
M: map[string]*PairCall{
"main()": &PairCall{
WallTime: 1000,
Count: 1,
CpuTime: 400,
Memory: 1500,
},
"main()==>foo": &PairCall{
WallTime: 500,
Count: 2,
CpuTime: 200,
Memory: 700,
},
"foo==>bar": &PairCall{
WallTime: 200,
Count: 10,
CpuTime: 100,
Memory: 300,
},
},
}

f := m.ComputeNearestFamily("foo")

assert.EqualValues(t, expected, f)
}
10 changes: 10 additions & 0 deletions xhprof/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ func (p *Profile) GetMain() *Call {
return p.Main
}

func (p *Profile) GetCall(name string) *Call {
for _, c := range p.Calls {
if c.Name == name {
return c
}
}

return nil
}

type ProfileByField struct {
Profile *Profile
Field string
Expand Down

0 comments on commit 0d8c56c

Please sign in to comment.