flagPeek := f.isFormat("peek")
flagWebList := f.isFormat("weblist")
flagList := f.isFormat("list")
+ flagCallgrind := f.isFormat("callgrind")
- if flagDis || flagWebList {
+ if flagDis || flagWebList || flagCallgrind {
// Collect all samples at address granularity for assembly
// listing.
f.flagNodeCount = newInt(0)
switch {
case f.isFormat("proto"), f.isFormat("raw"):
// No aggregation for raw profiles.
- case f.isFormat("callgrind"):
- // Aggregate to file/line for callgrind.
- fallthrough
case *f.flagLines:
return prof.Aggregate(true, true, true, true, false)
case *f.flagFiles:
return prof.Aggregate(true, false, true, false, false)
case *f.flagFunctions:
return prof.Aggregate(true, true, false, false, false)
- case f.isFormat("weblist"), f.isFormat("disasm"):
+ case f.isFormat("weblist"), f.isFormat("disasm"), f.isFormat("callgrind"):
return prof.Aggregate(false, true, true, true, true)
}
return nil
g.preprocess(rpt)
+ 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 *nodeInfo
for _, n := range g.ns {
- fmt.Fprintln(w, "fl="+callgrindName(files, n.info.file))
- fmt.Fprintln(w, "fn="+callgrindName(names, n.info.name))
+ 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, _ := ScaleValue(n.flat, o.SampleUnit, o.OutputUnit)
- fmt.Fprintf(w, "%d %d\n", n.info.lineno, int(sv))
+ fmt.Fprintf(w, "%s %d %d\n", addr, n.info.lineno, int(sv))
// Print outgoing edges.
for _, out := range sortedEdges(n.out) {
c, _ := ScaleValue(out.weight, o.SampleUnit, o.OutputUnit)
- count := fmt.Sprintf("%d", int(c))
callee := out.dest
fmt.Fprintln(w, "cfl="+callgrindName(files, callee.info.file))
fmt.Fprintln(w, "cfn="+callgrindName(names, callee.info.name))
- fmt.Fprintln(w, "calls="+count, callee.info.lineno)
- fmt.Fprintln(w, n.info.lineno, count)
+ fmt.Fprintf(w, "calls=%d %s %d\n", int(c), 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", int(c))
}
- fmt.Fprintln(w)
+
+ prevInfo = &n.info
}
return nil
return fmt.Sprintf("(%d) %s", id, name)
}
+// callgrindAddress implements the callgrind subposition compression scheme if
+// possible. If prevInfo != nil, it contains the previous address. The current
+// address can be given relative to the previous address, with an explicit +/-
+// to indicate it is relative, or * for the same address.
+func callgrindAddress(prevInfo *nodeInfo, curr uint64) string {
+ abs := fmt.Sprintf("%#x", curr)
+ if prevInfo == nil {
+ return abs
+ }
+
+ prev := prevInfo.address
+ if prev == curr {
+ return "*"
+ }
+
+ diff := int64(curr - prev)
+ relative := fmt.Sprintf("%+d", diff)
+
+ // Only bother to use the relative address if it is actually shorter.
+ if len(relative) < len(abs) {
+ return relative
+ }
+
+ return abs
+}
+
// printTree prints a tree-based report in text form.
func printTree(w io.Writer, rpt *Report) error {
const separator = "----------------------------------------------------------+-------------"
var objfile string
if m := l.Mapping; m != nil {
- objfile = filepath.Base(m.File)
+ objfile = m.File
}
if len(l.Line) == 0 {
}
if name = strings.TrimSpace(name); name == "" && info.objfile != "" {
- name = "[" + info.objfile + "]"
+ name = "[" + filepath.Base(info.objfile) + "]"
}
return name
}