func (b *builder) tagGroupLabel(g []*Tag) (label string, flat, cum int64) { formatTag := b.config.FormatTag if formatTag == nil { formatTag = measurement.Label } if len(g) == 1 { t := g[0] return formatTag(t.Value, t.Unit), t.FlatValue(), t.CumValue() } min := g[0] max := g[0] df, f := min.FlatDiv, min.Flat dc, c := min.CumDiv, min.Cum for _, t := range g[1:] { if v, _ := measurement.Scale(t.Value, t.Unit, min.Unit); int64(v) < min.Value { min = t } if v, _ := measurement.Scale(t.Value, t.Unit, max.Unit); int64(v) > max.Value { max = t } f += t.Flat df += t.FlatDiv c += t.Cum dc += t.CumDiv } if df != 0 { f = f / df } if dc != 0 { c = c / dc } return formatTag(min.Value, min.Unit) + ".." + formatTag(max.Value, max.Unit), f, c }
// printCallgrind prints a graph for a profile on callgrind format. func printCallgrind(w io.Writer, rpt *Report) error { o := rpt.options rpt.options.NodeFraction = 0 rpt.options.EdgeFraction = 0 rpt.options.NodeCount = 0 g, _, _, _ := rpt.newTrimmedGraph() rpt.selectOutputUnit(g) fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")") files := make(map[string]int) names := make(map[string]int) for _, n := range g.Nodes { fmt.Fprintln(w, "fl="+callgrindName(files, n.Info.File)) fmt.Fprintln(w, "fn="+callgrindName(names, n.Info.Name)) sv, _ := measurement.Scale(n.Flat, o.SampleUnit, o.OutputUnit) fmt.Fprintf(w, "%d %d\n", n.Info.Lineno, int64(sv)) // Print outgoing edges. for _, out := range n.Out.Sort() { c, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit) callee := out.Dest fmt.Fprintln(w, "cfl="+callgrindName(files, callee.Info.File)) fmt.Fprintln(w, "cfn="+callgrindName(names, callee.Info.Name)) // pprof doesn't have a flat weight for a call, leave as 0. fmt.Fprintln(w, "calls=0", callee.Info.Lineno) fmt.Fprintln(w, n.Info.Lineno, int64(c)) } fmt.Fprintln(w) } return nil }
// printCallgrind prints a graph for a profile on callgrind format. func printCallgrind(w io.Writer, rpt *Report) error { o := rpt.options rpt.options.NodeFraction = 0 rpt.options.EdgeFraction = 0 rpt.options.NodeCount = 0 g, _, _, _ := rpt.newTrimmedGraph() rpt.selectOutputUnit(g) nodeNames := getDisambiguatedNames(g) fmt.Fprintln(w, "positions: instr line") fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")") objfiles := make(map[string]int) files := make(map[string]int) names := make(map[string]int) // prevInfo points to the previous NodeInfo. // It is used to group cost lines together as much as possible. var prevInfo *graph.NodeInfo for _, n := range g.Nodes { if prevInfo == nil || n.Info.Objfile != prevInfo.Objfile || n.Info.File != prevInfo.File || n.Info.Name != prevInfo.Name { fmt.Fprintln(w) fmt.Fprintln(w, "ob="+callgrindName(objfiles, n.Info.Objfile)) fmt.Fprintln(w, "fl="+callgrindName(files, n.Info.File)) fmt.Fprintln(w, "fn="+callgrindName(names, n.Info.Name)) } addr := callgrindAddress(prevInfo, n.Info.Address) sv, _ := measurement.Scale(n.FlatValue(), o.SampleUnit, o.OutputUnit) fmt.Fprintf(w, "%s %d %d\n", addr, n.Info.Lineno, int64(sv)) // Print outgoing edges. for _, out := range n.Out.Sort() { c, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit) callee := out.Dest fmt.Fprintln(w, "cfl="+callgrindName(files, callee.Info.File)) fmt.Fprintln(w, "cfn="+callgrindName(names, nodeNames[callee])) // pprof doesn't have a flat weight for a call, leave as 0. fmt.Fprintf(w, "calls=0 %s %d\n", callgrindAddress(prevInfo, callee.Info.Address), callee.Info.Lineno) // TODO: This address may be in the middle of a call // instruction. It would be best to find the beginning // of the instruction, but the tools seem to handle // this OK. fmt.Fprintf(w, "* * %d\n", int64(c)) } prevInfo = &n.Info } return nil }
func printTopProto(w io.Writer, rpt *Report) error { p := rpt.prof o := rpt.options g, _, _, _ := rpt.newTrimmedGraph() rpt.selectOutputUnit(g) out := profile.Profile{ SampleType: []*profile.ValueType{ {Type: "cum", Unit: o.OutputUnit}, {Type: "flat", Unit: o.OutputUnit}, }, TimeNanos: p.TimeNanos, DurationNanos: p.DurationNanos, PeriodType: p.PeriodType, Period: p.Period, } var flatSum int64 for i, n := range g.Nodes { name, flat, cum := n.Info.PrintableName(), n.Flat, n.Cum flatSum += flat f := &profile.Function{ ID: uint64(i + 1), Name: name, SystemName: name, } l := &profile.Location{ ID: uint64(i + 1), Line: []profile.Line{ { Function: f, }, }, } fv, _ := measurement.Scale(flat, o.SampleUnit, o.OutputUnit) cv, _ := measurement.Scale(cum, o.SampleUnit, o.OutputUnit) s := &profile.Sample{ Location: []*profile.Location{l}, Value: []int64{int64(cv), int64(fv)}, } out.Function = append(out.Function, f) out.Location = append(out.Location, l) out.Sample = append(out.Sample, s) } return out.Write(w) }
// ProfileLabels returns printable labels for a profile. func ProfileLabels(rpt *Report) []string { label := []string{} prof := rpt.prof o := rpt.options if len(prof.Mapping) > 0 { if prof.Mapping[0].File != "" { label = append(label, "File: "+filepath.Base(prof.Mapping[0].File)) } if prof.Mapping[0].BuildID != "" { label = append(label, "Build ID: "+prof.Mapping[0].BuildID) } } label = append(label, prof.Comments...) if o.SampleType != "" { label = append(label, "Type: "+o.SampleType) } if prof.TimeNanos != 0 { const layout = "Jan 2, 2006 at 3:04pm (MST)" label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout)) } if prof.DurationNanos != 0 { duration := measurement.Label(prof.DurationNanos, "nanoseconds") totalNanos, totalUnit := measurement.Scale(rpt.total, o.SampleUnit, "nanoseconds") var ratio string if totalUnit == "ns" && totalNanos != 0 { ratio = "(" + percentage(int64(totalNanos), prof.DurationNanos) + ")" } label = append(label, fmt.Sprintf("Duration: %s, Total samples = %s %s", duration, rpt.formatValue(rpt.total), ratio)) } return label }
func tagDistance(t, u *Tag) float64 { v, _ := measurement.Scale(u.Value, u.Unit, t.Unit) if v < float64(t.Value) { return float64(t.Value) - v } return v - float64(t.Value) }
func (rpt *Report) selectOutputUnit(g *graph.Graph) { o := rpt.options // Select best unit for profile output. // Find the appropriate units for the smallest non-zero sample if o.OutputUnit != "minimum" || len(g.Nodes) == 0 { return } var minValue int64 for _, n := range g.Nodes { nodeMin := abs64(n.Flat) if nodeMin == 0 { nodeMin = abs64(n.Cum) } if nodeMin > 0 && (minValue == 0 || nodeMin < minValue) { minValue = nodeMin } } maxValue := rpt.total if minValue == 0 { minValue = maxValue } if r := o.Ratio; r > 0 && r != 1 { minValue = int64(float64(minValue) * r) maxValue = int64(float64(maxValue) * r) } _, minUnit := measurement.Scale(minValue, o.SampleUnit, "minimum") _, maxUnit := measurement.Scale(maxValue, o.SampleUnit, "minimum") unit := minUnit if minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind { // Minimum and maximum values have different units. Scale // minimum by 100 to use larger units, allowing minimum value to // be scaled down to 0.01, except for callgrind reports since // they can only represent integer values. _, unit = measurement.Scale(100*minValue, o.SampleUnit, "minimum") } if unit != "" { o.OutputUnit = unit } else { o.OutputUnit = o.SampleUnit } }
func tagGroupLabel(g []*Tag) (label string, flat, cum int64) { if len(g) == 1 { t := g[0] return measurement.Label(t.Value, t.Unit), t.Flat, t.Cum } min := g[0] max := g[0] f := min.Flat c := min.Cum for _, t := range g[1:] { if v, _ := measurement.Scale(t.Value, t.Unit, min.Unit); int64(v) < min.Value { min = t } if v, _ := measurement.Scale(t.Value, t.Unit, max.Unit); int64(v) > max.Value { max = t } f += t.Flat c += t.Cum } return measurement.Label(min.Value, min.Unit) + ".." + measurement.Label(max.Value, max.Unit), f, c }
// parseTagFilterRange returns a function to checks if a value is // contained on the range described by a string. It can recognize // strings of the form: // "32kb" -- matches values == 32kb // ":64kb" -- matches values <= 64kb // "4mb:" -- matches values >= 4mb // "12kb:64mb" -- matches values between 12kb and 64mb (both included). func parseTagFilterRange(filter string) func(int64, string) bool { ranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2) if len(ranges) == 0 { return nil // No ranges were identified } v, err := strconv.ParseInt(ranges[0][1], 10, 64) if err != nil { panic(fmt.Errorf("Failed to parse int %s: %v", ranges[0][1], err)) } scaledValue, unit := measurement.Scale(v, ranges[0][2], ranges[0][2]) if len(ranges) == 1 { switch match := ranges[0][0]; filter { case match: return func(v int64, u string) bool { sv, su := measurement.Scale(v, u, unit) return su == unit && sv == scaledValue } case match + ":": return func(v int64, u string) bool { sv, su := measurement.Scale(v, u, unit) return su == unit && sv >= scaledValue } case ":" + match: return func(v int64, u string) bool { sv, su := measurement.Scale(v, u, unit) return su == unit && sv <= scaledValue } } return nil } if filter != ranges[0][0]+":"+ranges[1][0] { return nil } if v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil { panic(fmt.Errorf("Failed to parse int %s: %v", ranges[1][1], err)) } scaledValue2, unit2 := measurement.Scale(v, ranges[1][2], unit) if unit != unit2 { return nil } return func(v int64, u string) bool { sv, su := measurement.Scale(v, u, unit) return su == unit && sv >= scaledValue && sv <= scaledValue2 } }