Geen omschrijving

report.go 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  1. // Copyright 2014 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package report summarizes a performance profile into a
  15. // human-readable report.
  16. package report
  17. import (
  18. "fmt"
  19. "io"
  20. "math"
  21. "os"
  22. "path/filepath"
  23. "regexp"
  24. "sort"
  25. "strconv"
  26. "strings"
  27. "time"
  28. "github.com/google/pprof/internal/graph"
  29. "github.com/google/pprof/internal/measurement"
  30. "github.com/google/pprof/internal/plugin"
  31. "github.com/google/pprof/profile"
  32. )
  33. // Generate generates a report as directed by the Report.
  34. func Generate(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
  35. o := rpt.options
  36. switch o.OutputFormat {
  37. case Dot:
  38. return printDOT(w, rpt)
  39. case Tree:
  40. return printTree(w, rpt)
  41. case Text:
  42. return printText(w, rpt)
  43. case Traces:
  44. return printTraces(w, rpt)
  45. case Raw:
  46. fmt.Fprint(w, rpt.prof.String())
  47. return nil
  48. case Tags:
  49. return printTags(w, rpt)
  50. case Proto:
  51. return rpt.prof.Write(w)
  52. case TopProto:
  53. return printTopProto(w, rpt)
  54. case Dis:
  55. return printAssembly(w, rpt, obj)
  56. case List:
  57. return printSource(w, rpt)
  58. case WebList:
  59. return printWebSource(w, rpt, obj)
  60. case Callgrind:
  61. return printCallgrind(w, rpt)
  62. }
  63. return fmt.Errorf("unexpected output format")
  64. }
  65. // newTrimmedGraph creates a graph for this report, trimmed according
  66. // to the report options.
  67. func (rpt *Report) newTrimmedGraph() (g *graph.Graph, origCount, droppedNodes, droppedEdges int) {
  68. o := rpt.options
  69. // Build a graph and refine it. On each refinement step we must rebuild the graph from the samples,
  70. // as the graph itself doesn't contain enough information to preserve full precision.
  71. // First step: Build complete graph to identify low frequency nodes, based on their cum weight.
  72. g = rpt.newGraph(nil)
  73. totalValue, _ := g.Nodes.Sum()
  74. nodeCutoff := abs64(int64(float64(totalValue) * o.NodeFraction))
  75. edgeCutoff := abs64(int64(float64(totalValue) * o.EdgeFraction))
  76. // Filter out nodes with cum value below nodeCutoff.
  77. if nodeCutoff > 0 {
  78. if nodesKept := g.DiscardLowFrequencyNodes(nodeCutoff); len(g.Nodes) != len(nodesKept) {
  79. droppedNodes = len(g.Nodes) - len(nodesKept)
  80. g = rpt.newGraph(nodesKept)
  81. }
  82. }
  83. origCount = len(g.Nodes)
  84. // Second step: Limit the total number of nodes. Apply specialized heuristics to improve
  85. // visualization when generating dot output.
  86. visualMode := o.OutputFormat == Dot
  87. g.SortNodes(o.CumSort, visualMode)
  88. if nodeCount := o.NodeCount; nodeCount > 0 {
  89. // Remove low frequency tags and edges as they affect selection.
  90. g.TrimLowFrequencyTags(nodeCutoff)
  91. g.TrimLowFrequencyEdges(edgeCutoff)
  92. if nodesKept := g.SelectTopNodes(nodeCount, visualMode); len(nodesKept) != len(g.Nodes) {
  93. g = rpt.newGraph(nodesKept)
  94. g.SortNodes(o.CumSort, visualMode)
  95. }
  96. }
  97. // Final step: Filter out low frequency tags and edges, and remove redundant edges that clutter
  98. // the graph.
  99. g.TrimLowFrequencyTags(nodeCutoff)
  100. droppedEdges = g.TrimLowFrequencyEdges(edgeCutoff)
  101. if visualMode {
  102. g.RemoveRedundantEdges()
  103. }
  104. return
  105. }
  106. func (rpt *Report) selectOutputUnit(g *graph.Graph) {
  107. o := rpt.options
  108. // Select best unit for profile output.
  109. // Find the appropriate units for the smallest non-zero sample
  110. if o.OutputUnit != "minimum" || len(g.Nodes) == 0 {
  111. return
  112. }
  113. var minValue int64
  114. for _, n := range g.Nodes {
  115. nodeMin := abs64(n.Flat)
  116. if nodeMin == 0 {
  117. nodeMin = abs64(n.Cum)
  118. }
  119. if nodeMin > 0 && (minValue == 0 || nodeMin < minValue) {
  120. minValue = nodeMin
  121. }
  122. }
  123. maxValue := rpt.total
  124. if minValue == 0 {
  125. minValue = maxValue
  126. }
  127. if r := o.Ratio; r > 0 && r != 1 {
  128. minValue = int64(float64(minValue) * r)
  129. maxValue = int64(float64(maxValue) * r)
  130. }
  131. _, minUnit := measurement.Scale(minValue, o.SampleUnit, "minimum")
  132. _, maxUnit := measurement.Scale(maxValue, o.SampleUnit, "minimum")
  133. unit := minUnit
  134. if minUnit != maxUnit && minValue*100 < maxValue && o.OutputFormat != Callgrind {
  135. // Minimum and maximum values have different units. Scale
  136. // minimum by 100 to use larger units, allowing minimum value to
  137. // be scaled down to 0.01, except for callgrind reports since
  138. // they can only represent integer values.
  139. _, unit = measurement.Scale(100*minValue, o.SampleUnit, "minimum")
  140. }
  141. if unit != "" {
  142. o.OutputUnit = unit
  143. } else {
  144. o.OutputUnit = o.SampleUnit
  145. }
  146. }
  147. // newGraph creates a new graph for this report. If nodes is non-nil,
  148. // only nodes whose info matches are included. Otherwise, all nodes
  149. // are included, without trimming.
  150. func (rpt *Report) newGraph(nodes graph.NodeSet) *graph.Graph {
  151. o := rpt.options
  152. // Clean up file paths using heuristics.
  153. prof := rpt.prof
  154. for _, f := range prof.Function {
  155. f.Filename = trimPath(f.Filename)
  156. }
  157. // Remove numeric tags not recognized by pprof.
  158. for _, s := range prof.Sample {
  159. numLabels := make(map[string][]int64, len(s.NumLabel))
  160. for k, v := range s.NumLabel {
  161. if k == "bytes" {
  162. numLabels[k] = append(numLabels[k], v...)
  163. }
  164. }
  165. s.NumLabel = numLabels
  166. }
  167. gopt := &graph.Options{
  168. SampleValue: o.SampleValue,
  169. FormatTag: formatTag,
  170. CallTree: o.CallTree && o.OutputFormat == Dot,
  171. DropNegative: o.DropNegative,
  172. KeptNodes: nodes,
  173. }
  174. // Only keep binary names for disassembly-based reports, otherwise
  175. // remove it to allow merging of functions across binaries.
  176. switch o.OutputFormat {
  177. case Raw, List, WebList, Dis:
  178. gopt.ObjNames = true
  179. }
  180. return graph.New(rpt.prof, gopt)
  181. }
  182. func formatTag(v int64, key string) string {
  183. return measurement.Label(v, key)
  184. }
  185. func printTopProto(w io.Writer, rpt *Report) error {
  186. p := rpt.prof
  187. o := rpt.options
  188. g, _, _, _ := rpt.newTrimmedGraph()
  189. rpt.selectOutputUnit(g)
  190. out := profile.Profile{
  191. SampleType: []*profile.ValueType{
  192. {Type: "cum", Unit: o.OutputUnit},
  193. {Type: "flat", Unit: o.OutputUnit},
  194. },
  195. TimeNanos: p.TimeNanos,
  196. DurationNanos: p.DurationNanos,
  197. PeriodType: p.PeriodType,
  198. Period: p.Period,
  199. }
  200. var flatSum int64
  201. for i, n := range g.Nodes {
  202. name, flat, cum := n.Info.PrintableName(), n.Flat, n.Cum
  203. flatSum += flat
  204. f := &profile.Function{
  205. ID: uint64(i + 1),
  206. Name: name,
  207. SystemName: name,
  208. }
  209. l := &profile.Location{
  210. ID: uint64(i + 1),
  211. Line: []profile.Line{
  212. {
  213. Function: f,
  214. },
  215. },
  216. }
  217. fv, _ := measurement.Scale(flat, o.SampleUnit, o.OutputUnit)
  218. cv, _ := measurement.Scale(cum, o.SampleUnit, o.OutputUnit)
  219. s := &profile.Sample{
  220. Location: []*profile.Location{l},
  221. Value: []int64{int64(cv), int64(fv)},
  222. }
  223. out.Function = append(out.Function, f)
  224. out.Location = append(out.Location, l)
  225. out.Sample = append(out.Sample, s)
  226. }
  227. return out.Write(w)
  228. }
  229. // printAssembly prints an annotated assembly listing.
  230. func printAssembly(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
  231. o := rpt.options
  232. prof := rpt.prof
  233. g := rpt.newGraph(nil)
  234. // If the regexp source can be parsed as an address, also match
  235. // functions that land on that address.
  236. var address *uint64
  237. if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
  238. address = &hex
  239. }
  240. fmt.Fprintln(w, "Total:", rpt.formatValue(rpt.total))
  241. symbols := symbolsFromBinaries(prof, g, o.Symbol, address, obj)
  242. symNodes := nodesPerSymbol(g.Nodes, symbols)
  243. // Sort function names for printing.
  244. var syms objSymbols
  245. for s := range symNodes {
  246. syms = append(syms, s)
  247. }
  248. sort.Sort(syms)
  249. // Correlate the symbols from the binary with the profile samples.
  250. for _, s := range syms {
  251. sns := symNodes[s]
  252. // Gather samples for this symbol.
  253. flatSum, cumSum := sns.Sum()
  254. // Get the function assembly.
  255. insns, err := obj.Disasm(s.sym.File, s.sym.Start, s.sym.End)
  256. if err != nil {
  257. return err
  258. }
  259. ns := annotateAssembly(insns, sns, s.base)
  260. fmt.Fprintf(w, "ROUTINE ======================== %s\n", s.sym.Name[0])
  261. for _, name := range s.sym.Name[1:] {
  262. fmt.Fprintf(w, " AKA ======================== %s\n", name)
  263. }
  264. fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
  265. rpt.formatValue(flatSum), rpt.formatValue(cumSum),
  266. percentage(cumSum, rpt.total))
  267. for _, n := range ns {
  268. fmt.Fprintf(w, "%10s %10s %10x: %s\n", valueOrDot(n.Flat, rpt), valueOrDot(n.Cum, rpt), n.Info.Address, n.Info.Name)
  269. }
  270. }
  271. return nil
  272. }
  273. // symbolsFromBinaries examines the binaries listed on the profile
  274. // that have associated samples, and identifies symbols matching rx.
  275. func symbolsFromBinaries(prof *profile.Profile, g *graph.Graph, rx *regexp.Regexp, address *uint64, obj plugin.ObjTool) []*objSymbol {
  276. hasSamples := make(map[string]bool)
  277. // Only examine mappings that have samples that match the
  278. // regexp. This is an optimization to speed up pprof.
  279. for _, n := range g.Nodes {
  280. if name := n.Info.PrintableName(); rx.MatchString(name) && n.Info.Objfile != "" {
  281. hasSamples[n.Info.Objfile] = true
  282. }
  283. }
  284. // Walk all mappings looking for matching functions with samples.
  285. var objSyms []*objSymbol
  286. for _, m := range prof.Mapping {
  287. if !hasSamples[filepath.Base(m.File)] {
  288. if address == nil || !(m.Start <= *address && *address <= m.Limit) {
  289. continue
  290. }
  291. }
  292. f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
  293. if err != nil {
  294. fmt.Printf("%v\n", err)
  295. continue
  296. }
  297. // Find symbols in this binary matching the user regexp.
  298. var addr uint64
  299. if address != nil {
  300. addr = *address
  301. }
  302. msyms, err := f.Symbols(rx, addr)
  303. base := f.Base()
  304. f.Close()
  305. if err != nil {
  306. continue
  307. }
  308. for _, ms := range msyms {
  309. objSyms = append(objSyms,
  310. &objSymbol{
  311. sym: ms,
  312. base: base,
  313. },
  314. )
  315. }
  316. }
  317. return objSyms
  318. }
  319. // objSym represents a symbol identified from a binary. It includes
  320. // the SymbolInfo from the disasm package and the base that must be
  321. // added to correspond to sample addresses
  322. type objSymbol struct {
  323. sym *plugin.Sym
  324. base uint64
  325. }
  326. // objSymbols is a wrapper type to enable sorting of []*objSymbol.
  327. type objSymbols []*objSymbol
  328. func (o objSymbols) Len() int {
  329. return len(o)
  330. }
  331. func (o objSymbols) Less(i, j int) bool {
  332. if namei, namej := o[i].sym.Name[0], o[j].sym.Name[0]; namei != namej {
  333. return namei < namej
  334. }
  335. return o[i].sym.Start < o[j].sym.Start
  336. }
  337. func (o objSymbols) Swap(i, j int) {
  338. o[i], o[j] = o[j], o[i]
  339. }
  340. // nodesPerSymbol classifies nodes into a group of symbols.
  341. func nodesPerSymbol(ns graph.Nodes, symbols []*objSymbol) map[*objSymbol]graph.Nodes {
  342. symNodes := make(map[*objSymbol]graph.Nodes)
  343. for _, s := range symbols {
  344. // Gather samples for this symbol.
  345. for _, n := range ns {
  346. address := n.Info.Address - s.base
  347. if address >= s.sym.Start && address < s.sym.End {
  348. symNodes[s] = append(symNodes[s], n)
  349. }
  350. }
  351. }
  352. return symNodes
  353. }
  354. // annotateAssembly annotates a set of assembly instructions with a
  355. // set of samples. It returns a set of nodes to display. base is an
  356. // offset to adjust the sample addresses.
  357. func annotateAssembly(insns []plugin.Inst, samples graph.Nodes, base uint64) graph.Nodes {
  358. // Add end marker to simplify printing loop.
  359. insns = append(insns, plugin.Inst{^uint64(0), "", "", 0})
  360. // Ensure samples are sorted by address.
  361. samples.Sort(graph.AddressOrder)
  362. var s int
  363. var asm graph.Nodes
  364. for ix, in := range insns[:len(insns)-1] {
  365. n := graph.Node{
  366. Info: graph.NodeInfo{
  367. Address: in.Addr,
  368. Name: in.Text,
  369. File: trimPath(in.File),
  370. Lineno: in.Line,
  371. },
  372. }
  373. // Sum all the samples until the next instruction (to account
  374. // for samples attributed to the middle of an instruction).
  375. for next := insns[ix+1].Addr; s < len(samples) && samples[s].Info.Address-base < next; s++ {
  376. n.Flat += samples[s].Flat
  377. n.Cum += samples[s].Cum
  378. if samples[s].Info.File != "" {
  379. n.Info.File = trimPath(samples[s].Info.File)
  380. n.Info.Lineno = samples[s].Info.Lineno
  381. }
  382. }
  383. asm = append(asm, &n)
  384. }
  385. return asm
  386. }
  387. // valueOrDot formats a value according to a report, intercepting zero
  388. // values.
  389. func valueOrDot(value int64, rpt *Report) string {
  390. if value == 0 {
  391. return "."
  392. }
  393. return rpt.formatValue(value)
  394. }
  395. // canAccessFile determines if the filename can be opened for reading.
  396. func canAccessFile(path string) bool {
  397. if fi, err := os.Stat(path); err == nil {
  398. return fi.Mode().Perm()&0400 != 0
  399. }
  400. return false
  401. }
  402. // printTags collects all tags referenced in the profile and prints
  403. // them in a sorted table.
  404. func printTags(w io.Writer, rpt *Report) error {
  405. p := rpt.prof
  406. // Hashtable to keep accumulate tags as key,value,count.
  407. tagMap := make(map[string]map[string]int64)
  408. for _, s := range p.Sample {
  409. for key, vals := range s.Label {
  410. for _, val := range vals {
  411. if valueMap, ok := tagMap[key]; ok {
  412. valueMap[val] = valueMap[val] + s.Value[0]
  413. continue
  414. }
  415. valueMap := make(map[string]int64)
  416. valueMap[val] = s.Value[0]
  417. tagMap[key] = valueMap
  418. }
  419. }
  420. for key, vals := range s.NumLabel {
  421. for _, nval := range vals {
  422. val := measurement.Label(nval, key)
  423. if valueMap, ok := tagMap[key]; ok {
  424. valueMap[val] = valueMap[val] + s.Value[0]
  425. continue
  426. }
  427. valueMap := make(map[string]int64)
  428. valueMap[val] = s.Value[0]
  429. tagMap[key] = valueMap
  430. }
  431. }
  432. }
  433. tagKeys := make([]*graph.Tag, 0, len(tagMap))
  434. for key := range tagMap {
  435. tagKeys = append(tagKeys, &graph.Tag{Name: key})
  436. }
  437. for _, tagKey := range graph.SortTags(tagKeys, true) {
  438. var total int64
  439. key := tagKey.Name
  440. tags := make([]*graph.Tag, 0, len(tagMap[key]))
  441. for t, c := range tagMap[key] {
  442. total += c
  443. tags = append(tags, &graph.Tag{Name: t, Flat: c})
  444. }
  445. fmt.Fprintf(w, "%s: Total %d\n", key, total)
  446. for _, t := range graph.SortTags(tags, true) {
  447. if total > 0 {
  448. fmt.Fprintf(w, " %8d (%s): %s\n", t.Flat,
  449. percentage(t.Flat, total), t.Name)
  450. } else {
  451. fmt.Fprintf(w, " %8d: %s\n", t.Flat, t.Name)
  452. }
  453. }
  454. fmt.Fprintln(w)
  455. }
  456. return nil
  457. }
  458. // printText prints a flat text report for a profile.
  459. func printText(w io.Writer, rpt *Report) error {
  460. g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
  461. rpt.selectOutputUnit(g)
  462. fmt.Fprintln(w, strings.Join(reportLabels(rpt, g, origCount, droppedNodes, 0, false), "\n"))
  463. fmt.Fprintf(w, "%10s %5s%% %5s%% %10s %5s%%\n",
  464. "flat", "flat", "sum", "cum", "cum")
  465. var flatSum int64
  466. for _, n := range g.Nodes {
  467. name, flat, cum := n.Info.PrintableName(), n.Flat, n.Cum
  468. var inline, noinline bool
  469. for _, e := range n.In {
  470. if e.Inline {
  471. inline = true
  472. } else {
  473. noinline = true
  474. }
  475. }
  476. if inline {
  477. if noinline {
  478. name = name + " (partial-inline)"
  479. } else {
  480. name = name + " (inline)"
  481. }
  482. }
  483. flatSum += flat
  484. fmt.Fprintf(w, "%10s %s %s %10s %s %s\n",
  485. rpt.formatValue(flat),
  486. percentage(flat, rpt.total),
  487. percentage(flatSum, rpt.total),
  488. rpt.formatValue(cum),
  489. percentage(cum, rpt.total),
  490. name)
  491. }
  492. return nil
  493. }
  494. // printTraces prints all traces from a profile.
  495. func printTraces(w io.Writer, rpt *Report) error {
  496. fmt.Fprintln(w, strings.Join(ProfileLabels(rpt), "\n"))
  497. prof := rpt.prof
  498. o := rpt.options
  499. const separator = "-----------+-------------------------------------------------------"
  500. _, locations := graph.CreateNodes(prof, false, nil)
  501. for _, sample := range prof.Sample {
  502. var stack graph.Nodes
  503. for _, loc := range sample.Location {
  504. id := loc.ID
  505. stack = append(stack, locations[id]...)
  506. }
  507. if len(stack) == 0 {
  508. continue
  509. }
  510. fmt.Fprintln(w, separator)
  511. // Print any text labels for the sample.
  512. var labels []string
  513. for s, vs := range sample.Label {
  514. labels = append(labels, fmt.Sprintf("%10s: %s\n", s, strings.Join(vs, " ")))
  515. }
  516. sort.Strings(labels)
  517. fmt.Fprint(w, strings.Join(labels, ""))
  518. // Print call stack.
  519. fmt.Fprintf(w, "%10s %s\n",
  520. rpt.formatValue(o.SampleValue(sample.Value)),
  521. stack[0].Info.PrintableName())
  522. for _, s := range stack[1:] {
  523. fmt.Fprintf(w, "%10s %s\n", "", s.Info.PrintableName())
  524. }
  525. }
  526. fmt.Fprintln(w, separator)
  527. return nil
  528. }
  529. // printCallgrind prints a graph for a profile on callgrind format.
  530. func printCallgrind(w io.Writer, rpt *Report) error {
  531. o := rpt.options
  532. rpt.options.NodeFraction = 0
  533. rpt.options.EdgeFraction = 0
  534. rpt.options.NodeCount = 0
  535. g, _, _, _ := rpt.newTrimmedGraph()
  536. rpt.selectOutputUnit(g)
  537. fmt.Fprintln(w, "events:", o.SampleType+"("+o.OutputUnit+")")
  538. files := make(map[string]int)
  539. names := make(map[string]int)
  540. for _, n := range g.Nodes {
  541. fmt.Fprintln(w, "fl="+callgrindName(files, n.Info.File))
  542. fmt.Fprintln(w, "fn="+callgrindName(names, n.Info.Name))
  543. sv, _ := measurement.Scale(n.Flat, o.SampleUnit, o.OutputUnit)
  544. fmt.Fprintf(w, "%d %d\n", n.Info.Lineno, int64(sv))
  545. // Print outgoing edges.
  546. for _, out := range n.Out.Sort() {
  547. c, _ := measurement.Scale(out.Weight, o.SampleUnit, o.OutputUnit)
  548. callee := out.Dest
  549. fmt.Fprintln(w, "cfl="+callgrindName(files, callee.Info.File))
  550. fmt.Fprintln(w, "cfn="+callgrindName(names, callee.Info.Name))
  551. // pprof doesn't have a flat weight for a call, leave as 0.
  552. fmt.Fprintln(w, "calls=0", callee.Info.Lineno)
  553. fmt.Fprintln(w, n.Info.Lineno, int64(c))
  554. }
  555. fmt.Fprintln(w)
  556. }
  557. return nil
  558. }
  559. // callgrindName implements the callgrind naming compression scheme.
  560. // For names not previously seen returns "(N) name", where N is a
  561. // unique index. For names previously seen returns "(N)" where N is
  562. // the index returned the first time.
  563. func callgrindName(names map[string]int, name string) string {
  564. if name == "" {
  565. return ""
  566. }
  567. if id, ok := names[name]; ok {
  568. return fmt.Sprintf("(%d)", id)
  569. }
  570. id := len(names) + 1
  571. names[name] = id
  572. return fmt.Sprintf("(%d) %s", id, name)
  573. }
  574. // printTree prints a tree-based report in text form.
  575. func printTree(w io.Writer, rpt *Report) error {
  576. const separator = "----------------------------------------------------------+-------------"
  577. const legend = " flat flat% sum% cum cum% calls calls% + context "
  578. g, origCount, droppedNodes, _ := rpt.newTrimmedGraph()
  579. rpt.selectOutputUnit(g)
  580. fmt.Fprintln(w, strings.Join(reportLabels(rpt, g, origCount, droppedNodes, 0, false), "\n"))
  581. fmt.Fprintln(w, separator)
  582. fmt.Fprintln(w, legend)
  583. var flatSum int64
  584. rx := rpt.options.Symbol
  585. for _, n := range g.Nodes {
  586. name, flat, cum := n.Info.PrintableName(), n.Flat, n.Cum
  587. // Skip any entries that do not match the regexp (for the "peek" command).
  588. if rx != nil && !rx.MatchString(name) {
  589. continue
  590. }
  591. fmt.Fprintln(w, separator)
  592. // Print incoming edges.
  593. inEdges := n.In.Sort()
  594. for _, in := range inEdges {
  595. var inline string
  596. if in.Inline {
  597. inline = " (inline)"
  598. }
  599. fmt.Fprintf(w, "%50s %s | %s%s\n", rpt.formatValue(in.Weight),
  600. percentage(in.Weight, cum), in.Src.Info.PrintableName(), inline)
  601. }
  602. // Print current node.
  603. flatSum += flat
  604. fmt.Fprintf(w, "%10s %s %s %10s %s | %s\n",
  605. rpt.formatValue(flat),
  606. percentage(flat, rpt.total),
  607. percentage(flatSum, rpt.total),
  608. rpt.formatValue(cum),
  609. percentage(cum, rpt.total),
  610. name)
  611. // Print outgoing edges.
  612. outEdges := n.Out.Sort()
  613. for _, out := range outEdges {
  614. var inline string
  615. if out.Inline {
  616. inline = " (inline)"
  617. }
  618. fmt.Fprintf(w, "%50s %s | %s%s\n", rpt.formatValue(out.Weight),
  619. percentage(out.Weight, cum), out.Dest.Info.PrintableName(), inline)
  620. }
  621. }
  622. if len(g.Nodes) > 0 {
  623. fmt.Fprintln(w, separator)
  624. }
  625. return nil
  626. }
  627. // printDOT prints an annotated callgraph in DOT format.
  628. func printDOT(w io.Writer, rpt *Report) error {
  629. g, origCount, droppedNodes, droppedEdges := rpt.newTrimmedGraph()
  630. rpt.selectOutputUnit(g)
  631. labels := reportLabels(rpt, g, origCount, droppedNodes, droppedEdges, true)
  632. c := &graph.DotConfig{
  633. Title: rpt.options.Title,
  634. Labels: labels,
  635. FormatValue: rpt.formatValue,
  636. Total: rpt.total,
  637. }
  638. graph.ComposeDot(w, g, &graph.DotAttributes{}, c)
  639. return nil
  640. }
  641. // percentage computes the percentage of total of a value, and encodes
  642. // it as a string. At least two digits of precision are printed.
  643. func percentage(value, total int64) string {
  644. var ratio float64
  645. if total != 0 {
  646. ratio = math.Abs(float64(value)/float64(total)) * 100
  647. }
  648. switch {
  649. case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05:
  650. return " 100%"
  651. case math.Abs(ratio) >= 1.0:
  652. return fmt.Sprintf("%5.2f%%", ratio)
  653. default:
  654. return fmt.Sprintf("%5.2g%%", ratio)
  655. }
  656. }
  657. // ProfileLabels returns printable labels for a profile.
  658. func ProfileLabels(rpt *Report) []string {
  659. label := []string{}
  660. prof := rpt.prof
  661. o := rpt.options
  662. if len(prof.Mapping) > 0 {
  663. if prof.Mapping[0].File != "" {
  664. label = append(label, "File: "+filepath.Base(prof.Mapping[0].File))
  665. }
  666. if prof.Mapping[0].BuildID != "" {
  667. label = append(label, "Build ID: "+prof.Mapping[0].BuildID)
  668. }
  669. }
  670. label = append(label, prof.Comments...)
  671. if o.SampleType != "" {
  672. label = append(label, "Type: "+o.SampleType)
  673. }
  674. if prof.TimeNanos != 0 {
  675. const layout = "Jan 2, 2006 at 3:04pm (MST)"
  676. label = append(label, "Time: "+time.Unix(0, prof.TimeNanos).Format(layout))
  677. }
  678. if prof.DurationNanos != 0 {
  679. duration := measurement.Label(prof.DurationNanos, "nanoseconds")
  680. totalNanos, totalUnit := measurement.Scale(rpt.total, o.SampleUnit, "nanoseconds")
  681. var ratio string
  682. if totalUnit == "ns" && totalNanos != 0 {
  683. ratio = "(" + percentage(int64(totalNanos), prof.DurationNanos) + ")"
  684. }
  685. label = append(label, fmt.Sprintf("Duration: %s, Total samples = %s %s", duration, rpt.formatValue(rpt.total), ratio))
  686. }
  687. return label
  688. }
  689. // reportLabels returns printable labels for a report. Includes
  690. // profileLabels.
  691. func reportLabels(rpt *Report, g *graph.Graph, origCount, droppedNodes, droppedEdges int, fullHeaders bool) []string {
  692. nodeFraction := rpt.options.NodeFraction
  693. edgeFraction := rpt.options.EdgeFraction
  694. nodeCount := rpt.options.NodeCount
  695. var label []string
  696. if len(rpt.options.ProfileLabels) > 0 {
  697. for _, l := range rpt.options.ProfileLabels {
  698. label = append(label, l)
  699. }
  700. } else if fullHeaders || !rpt.options.CompactLabels {
  701. label = ProfileLabels(rpt)
  702. }
  703. var flatSum int64
  704. for _, n := range g.Nodes {
  705. flatSum = flatSum + n.Flat
  706. }
  707. label = append(label, fmt.Sprintf("Showing nodes accounting for %s, %s of %s total", rpt.formatValue(flatSum), strings.TrimSpace(percentage(flatSum, rpt.total)), rpt.formatValue(rpt.total)))
  708. if rpt.total != 0 {
  709. if droppedNodes > 0 {
  710. label = append(label, genLabel(droppedNodes, "node", "cum",
  711. rpt.formatValue(abs64(int64(float64(rpt.total)*nodeFraction)))))
  712. }
  713. if droppedEdges > 0 {
  714. label = append(label, genLabel(droppedEdges, "edge", "freq",
  715. rpt.formatValue(abs64(int64(float64(rpt.total)*edgeFraction)))))
  716. }
  717. if nodeCount > 0 && nodeCount < origCount {
  718. label = append(label, fmt.Sprintf("Showing top %d nodes out of %d (cum >= %s)",
  719. nodeCount, origCount,
  720. rpt.formatValue(g.Nodes[len(g.Nodes)-1].Cum)))
  721. }
  722. }
  723. return label
  724. }
  725. func genLabel(d int, n, l, f string) string {
  726. if d > 1 {
  727. n = n + "s"
  728. }
  729. return fmt.Sprintf("Dropped %d %s (%s <= %s)", d, n, l, f)
  730. }
  731. // Output formats.
  732. const (
  733. Proto = iota
  734. Dot
  735. Tags
  736. Tree
  737. Text
  738. Traces
  739. Raw
  740. Dis
  741. List
  742. WebList
  743. Callgrind
  744. TopProto
  745. )
  746. // Options are the formatting and filtering options used to generate a
  747. // profile.
  748. type Options struct {
  749. OutputFormat int
  750. CumSort bool
  751. CallTree bool
  752. DropNegative bool
  753. PositivePercentages bool
  754. CompactLabels bool
  755. Ratio float64
  756. Title string
  757. ProfileLabels []string
  758. NodeCount int
  759. NodeFraction float64
  760. EdgeFraction float64
  761. SampleValue func(s []int64) int64
  762. SampleType string
  763. SampleUnit string // Unit for the sample data from the profile.
  764. OutputUnit string // Units for data formatting in report.
  765. Symbol *regexp.Regexp // Symbols to include on disassembly report.
  766. }
  767. // New builds a new report indexing the sample values interpreting the
  768. // samples with the provided function.
  769. func New(prof *profile.Profile, o *Options) *Report {
  770. format := func(v int64) string {
  771. if r := o.Ratio; r > 0 && r != 1 {
  772. fv := float64(v) * r
  773. v = int64(fv)
  774. }
  775. return measurement.ScaledLabel(v, o.SampleUnit, o.OutputUnit)
  776. }
  777. return &Report{prof, computeTotal(prof, o.SampleValue, !o.PositivePercentages),
  778. o, format}
  779. }
  780. // NewDefault builds a new report indexing the last sample value
  781. // available.
  782. func NewDefault(prof *profile.Profile, options Options) *Report {
  783. index := len(prof.SampleType) - 1
  784. o := &options
  785. if o.Title == "" && len(prof.Mapping) > 0 {
  786. o.Title = filepath.Base(prof.Mapping[0].File)
  787. }
  788. o.SampleType = prof.SampleType[index].Type
  789. o.SampleUnit = strings.ToLower(prof.SampleType[index].Unit)
  790. o.SampleValue = func(v []int64) int64 {
  791. return v[index]
  792. }
  793. return New(prof, o)
  794. }
  795. // computeTotal computes the sum of all sample values. This will be
  796. // used to compute percentages. If includeNegative is set, use use
  797. // absolute values to provide a meaningful percentage for both
  798. // negative and positive values. Otherwise only use positive values,
  799. // which is useful when comparing profiles from different jobs.
  800. func computeTotal(prof *profile.Profile, value func(v []int64) int64, includeNegative bool) int64 {
  801. var ret int64
  802. for _, sample := range prof.Sample {
  803. if v := value(sample.Value); v > 0 {
  804. ret += v
  805. } else if includeNegative {
  806. ret -= v
  807. }
  808. }
  809. return ret
  810. }
  811. // Report contains the data and associated routines to extract a
  812. // report from a profile.
  813. type Report struct {
  814. prof *profile.Profile
  815. total int64
  816. options *Options
  817. formatValue func(int64) string
  818. }
  819. func (rpt *Report) formatTags(s *profile.Sample) (string, bool) {
  820. var labels []string
  821. for key, vals := range s.Label {
  822. for _, v := range vals {
  823. labels = append(labels, key+":"+v)
  824. }
  825. }
  826. for key, nvals := range s.NumLabel {
  827. for _, v := range nvals {
  828. labels = append(labels, measurement.Label(v, key))
  829. }
  830. }
  831. if len(labels) == 0 {
  832. return "", false
  833. }
  834. sort.Strings(labels)
  835. return strings.Join(labels, `\n`), true
  836. }
  837. func abs64(i int64) int64 {
  838. if i < 0 {
  839. return -i
  840. }
  841. return i
  842. }