X-Git-Url: http://git.osdn.net/view?p=bytom%2Fvapor.git;a=blobdiff_plain;f=toolbar%2Fmeasure%2Ftimer.go;fp=toolbar%2Fmeasure%2Ftimer.go;h=801a4a2165386c3b2b98c740f20be9ebe6433d82;hp=0000000000000000000000000000000000000000;hb=36ed0fa1af1b068729a5bd88f663fedee5854295;hpb=f0a522057a65acf27966e5bbc48b5d6a7392587e diff --git a/toolbar/measure/timer.go b/toolbar/measure/timer.go new file mode 100644 index 00000000..801a4a21 --- /dev/null +++ b/toolbar/measure/timer.go @@ -0,0 +1,129 @@ +package measure + +import ( + "fmt" + "strings" + "time" +) + +const ( + summaryPrefix = "|--" +) + +// Timer is created for analysis of the function performance +type Timer struct { + name string + start *time.Time + total time.Duration + + subtimerMap map[string]*Timer +} + +// NewTimer create a new timer, only use for root timer only +func NewTimer(name string) *Timer { + now := time.Now() + return &Timer{name: name, start: &now, subtimerMap: map[string]*Timer{}} +} + +// StartTimer start track time for sub func +func (t *Timer) StartTimer(stacks []string) error { + stacks, err := t.locateStack(stacks) + if err != nil { + return nil + } + + return t.startSubtimer(stacks) +} + +// String implement the print interface +func (t *Timer) String() string { + return t.summary(0, t.total) +} + +// EndTimer always run on end of the func +func (t *Timer) EndTimer(stacks []string) error { + stacks, err := t.locateStack(stacks) + if err != nil { + return err + } + + return t.endSubtimer(stacks) +} + +// IsEnd check wheather the ticker is close +func (t *Timer) IsEnd() bool { + return t.start == nil +} + +func (t *Timer) startSubtimer(stacks []string) error { + if len(stacks) == 0 { + if !t.IsEnd() { + return fmt.Errorf("try to start an unclose timer") + } + + now := time.Now() + t.start = &now + return nil + } + + nextStack := stacks[len(stacks)-1] + if _, ok := t.subtimerMap[nextStack]; !ok { + t.subtimerMap[nextStack] = &Timer{name: nextStack, subtimerMap: map[string]*Timer{}} + } + + return t.subtimerMap[nextStack].startSubtimer(stacks[:len(stacks)-1]) +} + +func (t *Timer) endSubtimer(stacks []string) error { + if len(stacks) == 0 { + if t.IsEnd() { + return fmt.Errorf("timer didn't start") + } + + t.total += time.Now().Sub(*t.start) + t.start = nil + return nil + } + + subtimer, ok := t.subtimerMap[stacks[len(stacks)-1]] + if !ok { + return fmt.Errorf("endSubtimer didn't find sub timer") + } + + return subtimer.endSubtimer(stacks[:len(stacks)-1]) +} + +// locateStack is using to exclude dust stacks +func (t *Timer) locateStack(stacks []string) ([]string, error) { + for i := len(stacks) - 1; i >= 0; i-- { + if stacks[i] == t.name { + return stacks[:i], nil + } + } + + return nil, fmt.Errorf("locateStack didn't match the expect stack") +} + +// summary will convert the time spend graph to tree string +func (t *Timer) summary(depth int, parentDuration time.Duration) string { + result := strings.Repeat(" ", depth) + summaryPrefix + result += t.name + ": " + if !t.IsEnd() { + return result + "\n" + } + + result += fmt.Sprintf("%s (%.2f)\n", t.total.String(), float64(t.total.Nanoseconds())/float64(parentDuration.Nanoseconds())*100) + + // handle the case that skip middle level time measure case + nextDepth, total := depth+1, t.total + if t.total == 0 { + result = "" + nextDepth, total = depth, parentDuration + } + + for _, sub := range t.subtimerMap { + result += sub.summary(nextDepth, total) + } + + return result +}