Quellcode durchsuchen

first commit of OSS pprof

Raul Silvera vor 9 Jahren
Commit
d174bbe741
100 geänderte Dateien mit 12961 neuen und 0 gelöschten Zeilen
  1. 7
    0
      AUTHORS
  2. 30
    0
      COMPILE.sh
  3. 27
    0
      CONTRIBUTING
  4. 14
    0
      CONTRIBUTORS
  5. 202
    0
      LICENSE
  6. 75
    0
      README.md
  7. 30
    0
      TEST.sh
  8. 14
    0
      doc/developer/pprof.dev.md
  9. 147
    0
      doc/developer/profile.proto.md
  10. 209
    0
      doc/pprof.md
  11. 280
    0
      src/driver/driver.go
  12. 179
    0
      src/internal/binutils/addr2liner.go
  13. 122
    0
      src/internal/binutils/addr2liner_nm.go
  14. 242
    0
      src/internal/binutils/binutils.go
  15. 111
    0
      src/internal/binutils/binutils_test.go
  16. 156
    0
      src/internal/binutils/disasm.go
  17. 133
    0
      src/internal/binutils/disasm_test.go
  18. 85
    0
      src/internal/binutils/testdata/wrapper/addr2line
  19. 62
    0
      src/internal/binutils/testdata/wrapper/nm
  20. 59
    0
      src/internal/binutils/testdata/wrapper/objdump
  21. 271
    0
      src/internal/driver/cli.go
  22. 580
    0
      src/internal/driver/commands.go
  23. 276
    0
      src/internal/driver/driver.go
  24. 168
    0
      src/internal/driver/driver_focus.go
  25. 1004
    0
      src/internal/driver/driver_test.go
  26. 367
    0
      src/internal/driver/fetch.go
  27. 148
    0
      src/internal/driver/fetch_test.go
  28. 372
    0
      src/internal/driver/interactive.go
  29. 305
    0
      src/internal/driver/interactive_test.go
  30. 148
    0
      src/internal/driver/options.go
  31. 54
    0
      src/internal/driver/tempfile.go
  32. BIN
      src/internal/driver/testdata/cppbench.cpu
  33. 55
    0
      src/internal/driver/testdata/cppbench.svg
  34. 17
    0
      src/internal/driver/testdata/file1000.src
  35. 17
    0
      src/internal/driver/testdata/file2000.src
  36. 17
    0
      src/internal/driver/testdata/file3000.src
  37. BIN
      src/internal/driver/testdata/go.crc32.cpu
  38. 10
    0
      src/internal/driver/testdata/pprof.contention.cum.files.dot
  39. 9
    0
      src/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore
  40. 77
    0
      src/internal/driver/testdata/pprof.cpu.callgrind
  41. 5
    0
      src/internal/driver/testdata/pprof.cpu.cum.lines.text.hide
  42. 5
    0
      src/internal/driver/testdata/pprof.cpu.cum.lines.text.show
  43. 5
    0
      src/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide
  44. 14
    0
      src/internal/driver/testdata/pprof.cpu.flat.addresses.disasm
  45. 109
    0
      src/internal/driver/testdata/pprof.cpu.flat.addresses.weblist
  46. 20
    0
      src/internal/driver/testdata/pprof.cpu.flat.functions.dot
  47. 8
    0
      src/internal/driver/testdata/pprof.cpu.flat.functions.text
  48. 13
    0
      src/internal/driver/testdata/pprof.cpu.peek
  49. 54
    0
      src/internal/driver/testdata/pprof.cpu.svg
  50. 13
    0
      src/internal/driver/testdata/pprof.cpu.tags
  51. 6
    0
      src/internal/driver/testdata/pprof.cpu.tags.focus.ignore
  52. 32
    0
      src/internal/driver/testdata/pprof.cpu.traces
  53. 17
    0
      src/internal/driver/testdata/pprof.cpusmall.flat.addresses.tree
  54. 53
    0
      src/internal/driver/testdata/pprof.heap.callgrind
  55. 19
    0
      src/internal/driver/testdata/pprof.heap.cum.lines.tree.focus
  56. 19
    0
      src/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus
  57. 2
    0
      src/internal/driver/testdata/pprof.heap.flat.files.seconds.text
  58. 5
    0
      src/internal/driver/testdata/pprof.heap.flat.files.text
  59. 8
    0
      src/internal/driver/testdata/pprof.heap.flat.inuse_objects.text
  60. 13
    0
      src/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus
  61. 15
    0
      src/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore
  62. 21
    0
      src/internal/driver/testdata/pprof.heap.flat.lines.dot.focus
  63. 8
    0
      src/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text
  64. 18
    0
      src/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus
  65. 11
    0
      src/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide
  66. 8
    0
      src/internal/driver/testdata/pprof.unknown.flat.functions.text
  67. 85
    0
      src/internal/driver/testdata/wrapper/addr2line
  68. 29
    0
      src/internal/driver/testdata/wrapper/dot
  69. 62
    0
      src/internal/driver/testdata/wrapper/nm
  70. 59
    0
      src/internal/driver/testdata/wrapper/objdump
  71. 248
    0
      src/internal/elfexec/elfexec.go
  72. 87
    0
      src/internal/elfexec/elfexec_test.go
  73. 469
    0
      src/internal/graph/dotgraph.go
  74. 276
    0
      src/internal/graph/dotgraph_test.go
  75. 859
    0
      src/internal/graph/graph.go
  76. 7
    0
      src/internal/graph/testdata/compose1.dot
  77. 7
    0
      src/internal/graph/testdata/compose2.dot
  78. 11
    0
      src/internal/graph/testdata/compose3.dot
  79. 4
    0
      src/internal/graph/testdata/compose4.dot
  80. 11
    0
      src/internal/graph/testdata/compose5.dot
  81. 299
    0
      src/internal/measurement/measurement.go
  82. 186
    0
      src/internal/plugin/plugin.go
  83. 102
    0
      src/internal/proftest/proftest.go
  84. 947
    0
      src/internal/report/report.go
  85. 210
    0
      src/internal/report/report_test.go
  86. 457
    0
      src/internal/report/source.go
  87. 87
    0
      src/internal/report/source_html.go
  88. 17
    0
      src/internal/report/testdata/source.dot
  89. 49
    0
      src/internal/report/testdata/source.rpt
  90. 19
    0
      src/internal/report/testdata/source1
  91. 19
    0
      src/internal/report/testdata/source2
  92. 320
    0
      src/internal/symbolizer/symbolizer.go
  93. 192
    0
      src/internal/symbolizer/symbolizer_test.go
  94. 161
    0
      src/internal/symbolz/symbolz.go
  95. 100
    0
      src/internal/symbolz/symbolz_test.go
  96. 30
    0
      src/pprof/pprof.go
  97. 493
    0
      src/profile/encode.go
  98. 171
    0
      src/profile/filter.go
  99. 308
    0
      src/profile/legacy_java_profile.go
  100. 0
    0
      src/profile/legacy_profile.go

+ 7
- 0
AUTHORS Datei anzeigen

1
+# This is the official list of pprof authors for copyright purposes.
2
+# This file is distinct from the CONTRIBUTORS files.
3
+# See the latter for an explanation.
4
+# Names should be added to this file as:
5
+# Name or Organization <email address>
6
+# The email address is not required for organizations.
7
+Google Inc.

+ 30
- 0
COMPILE.sh Datei anzeigen

1
+#!/bin/bash
2
+
3
+# Copyright 2014 Google Inc. All Rights Reserved.
4
+# 
5
+# Licensed under the Apache License, Version 2.0 (the "License");
6
+# you may not use this file except in compliance with the License.
7
+# You may obtain a copy of the License at
8
+# 
9
+#     http://www.apache.org/licenses/LICENSE-2.0
10
+# 
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS,
13
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+# See the License for the specific language governing permissions and
15
+# limitations under the License.
16
+
17
+ROOT=${PWD}
18
+
19
+echo Building pprof at ${ROOT}
20
+GOPATH=${ROOT}:${ROOT}/third_party go build pprof 
21
+RETCODE=$?
22
+
23
+if [ ${RETCODE} -eq 0 ]
24
+then
25
+   echo Build was successful. pprof available at ${ROOT}/pprof
26
+else
27
+   echo Build failed.
28
+   exit 1
29
+fi
30
+

+ 27
- 0
CONTRIBUTING Datei anzeigen

1
+Want to contribute? Great! First, read this page (including the small print at the end).
2
+
3
+### Before you contribute
4
+Before we can use your code, you must sign the
5
+[Google Individual Contributor License Agreement]
6
+(https://cla.developers.google.com/about/google-individual)
7
+(CLA), which you can do online. The CLA is necessary mainly because you own the
8
+copyright to your changes, even after your contribution becomes part of our
9
+codebase, so we need your permission to use and distribute your code. We also
10
+need to be sure of various other things—for instance that you'll tell us if you
11
+know that your code infringes on other people's patents. You don't have to sign
12
+the CLA until after you've submitted your code for review and a member has
13
+approved it, but you must do it before we can put your code into our codebase.
14
+Before you start working on a larger contribution, you should get in touch with
15
+us first through the issue tracker with your idea so that we can help out and
16
+possibly guide you. Coordinating up front makes it much easier to avoid
17
+frustration later on.
18
+
19
+### Code reviews
20
+All submissions, including submissions by project members, require review. We
21
+use Github pull requests for this purpose.
22
+
23
+### The small print
24
+Contributions made by corporations are covered by a different agreement than
25
+the one above, the
26
+[Software Grant and Corporate Contributor License Agreement]
27
+(https://cla.developers.google.com/about/google-corporate).

+ 14
- 0
CONTRIBUTORS Datei anzeigen

1
+# People who have agreed to one of the CLAs and can contribute patches.
2
+# The AUTHORS file lists the copyright holders; this file
3
+# lists people.  For example, Google employees are listed here
4
+# but not in AUTHORS, because Google holds the copyright.
5
+#
6
+# https://developers.google.com/open-source/cla/individual
7
+# https://developers.google.com/open-source/cla/corporate
8
+#
9
+# Names should be added to this file as:
10
+#     Name <email address>
11
+Raul Silvera <rsilvera@google.com>
12
+Tipp Moseley <tipp@google.com>
13
+Hyoun Kyu Cho <netforce@google.com>
14
+

+ 202
- 0
LICENSE Datei anzeigen

1
+
2
+                                 Apache License
3
+                           Version 2.0, January 2004
4
+                        http://www.apache.org/licenses/
5
+
6
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+   1. Definitions.
9
+
10
+      "License" shall mean the terms and conditions for use, reproduction,
11
+      and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+      "Licensor" shall mean the copyright owner or entity authorized by
14
+      the copyright owner that is granting the License.
15
+
16
+      "Legal Entity" shall mean the union of the acting entity and all
17
+      other entities that control, are controlled by, or are under common
18
+      control with that entity. For the purposes of this definition,
19
+      "control" means (i) the power, direct or indirect, to cause the
20
+      direction or management of such entity, whether by contract or
21
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+      outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+      "You" (or "Your") shall mean an individual or Legal Entity
25
+      exercising permissions granted by this License.
26
+
27
+      "Source" form shall mean the preferred form for making modifications,
28
+      including but not limited to software source code, documentation
29
+      source, and configuration files.
30
+
31
+      "Object" form shall mean any form resulting from mechanical
32
+      transformation or translation of a Source form, including but
33
+      not limited to compiled object code, generated documentation,
34
+      and conversions to other media types.
35
+
36
+      "Work" shall mean the work of authorship, whether in Source or
37
+      Object form, made available under the License, as indicated by a
38
+      copyright notice that is included in or attached to the work
39
+      (an example is provided in the Appendix below).
40
+
41
+      "Derivative Works" shall mean any work, whether in Source or Object
42
+      form, that is based on (or derived from) the Work and for which the
43
+      editorial revisions, annotations, elaborations, or other modifications
44
+      represent, as a whole, an original work of authorship. For the purposes
45
+      of this License, Derivative Works shall not include works that remain
46
+      separable from, or merely link (or bind by name) to the interfaces of,
47
+      the Work and Derivative Works thereof.
48
+
49
+      "Contribution" shall mean any work of authorship, including
50
+      the original version of the Work and any modifications or additions
51
+      to that Work or Derivative Works thereof, that is intentionally
52
+      submitted to Licensor for inclusion in the Work by the copyright owner
53
+      or by an individual or Legal Entity authorized to submit on behalf of
54
+      the copyright owner. For the purposes of this definition, "submitted"
55
+      means any form of electronic, verbal, or written communication sent
56
+      to the Licensor or its representatives, including but not limited to
57
+      communication on electronic mailing lists, source code control systems,
58
+      and issue tracking systems that are managed by, or on behalf of, the
59
+      Licensor for the purpose of discussing and improving the Work, but
60
+      excluding communication that is conspicuously marked or otherwise
61
+      designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+      "Contributor" shall mean Licensor and any individual or Legal Entity
64
+      on behalf of whom a Contribution has been received by Licensor and
65
+      subsequently incorporated within the Work.
66
+
67
+   2. Grant of Copyright License. Subject to the terms and conditions of
68
+      this License, each Contributor hereby grants to You a perpetual,
69
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+      copyright license to reproduce, prepare Derivative Works of,
71
+      publicly display, publicly perform, sublicense, and distribute the
72
+      Work and such Derivative Works in Source or Object form.
73
+
74
+   3. Grant of Patent License. Subject to the terms and conditions of
75
+      this License, each Contributor hereby grants to You a perpetual,
76
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+      (except as stated in this section) patent license to make, have made,
78
+      use, offer to sell, sell, import, and otherwise transfer the Work,
79
+      where such license applies only to those patent claims licensable
80
+      by such Contributor that are necessarily infringed by their
81
+      Contribution(s) alone or by combination of their Contribution(s)
82
+      with the Work to which such Contribution(s) was submitted. If You
83
+      institute patent litigation against any entity (including a
84
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+      or a Contribution incorporated within the Work constitutes direct
86
+      or contributory patent infringement, then any patent licenses
87
+      granted to You under this License for that Work shall terminate
88
+      as of the date such litigation is filed.
89
+
90
+   4. Redistribution. You may reproduce and distribute copies of the
91
+      Work or Derivative Works thereof in any medium, with or without
92
+      modifications, and in Source or Object form, provided that You
93
+      meet the following conditions:
94
+
95
+      (a) You must give any other recipients of the Work or
96
+          Derivative Works a copy of this License; and
97
+
98
+      (b) You must cause any modified files to carry prominent notices
99
+          stating that You changed the files; and
100
+
101
+      (c) You must retain, in the Source form of any Derivative Works
102
+          that You distribute, all copyright, patent, trademark, and
103
+          attribution notices from the Source form of the Work,
104
+          excluding those notices that do not pertain to any part of
105
+          the Derivative Works; and
106
+
107
+      (d) If the Work includes a "NOTICE" text file as part of its
108
+          distribution, then any Derivative Works that You distribute must
109
+          include a readable copy of the attribution notices contained
110
+          within such NOTICE file, excluding those notices that do not
111
+          pertain to any part of the Derivative Works, in at least one
112
+          of the following places: within a NOTICE text file distributed
113
+          as part of the Derivative Works; within the Source form or
114
+          documentation, if provided along with the Derivative Works; or,
115
+          within a display generated by the Derivative Works, if and
116
+          wherever such third-party notices normally appear. The contents
117
+          of the NOTICE file are for informational purposes only and
118
+          do not modify the License. You may add Your own attribution
119
+          notices within Derivative Works that You distribute, alongside
120
+          or as an addendum to the NOTICE text from the Work, provided
121
+          that such additional attribution notices cannot be construed
122
+          as modifying the License.
123
+
124
+      You may add Your own copyright statement to Your modifications and
125
+      may provide additional or different license terms and conditions
126
+      for use, reproduction, or distribution of Your modifications, or
127
+      for any such Derivative Works as a whole, provided Your use,
128
+      reproduction, and distribution of the Work otherwise complies with
129
+      the conditions stated in this License.
130
+
131
+   5. Submission of Contributions. Unless You explicitly state otherwise,
132
+      any Contribution intentionally submitted for inclusion in the Work
133
+      by You to the Licensor shall be under the terms and conditions of
134
+      this License, without any additional terms or conditions.
135
+      Notwithstanding the above, nothing herein shall supersede or modify
136
+      the terms of any separate license agreement you may have executed
137
+      with Licensor regarding such Contributions.
138
+
139
+   6. Trademarks. This License does not grant permission to use the trade
140
+      names, trademarks, service marks, or product names of the Licensor,
141
+      except as required for reasonable and customary use in describing the
142
+      origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+   7. Disclaimer of Warranty. Unless required by applicable law or
145
+      agreed to in writing, Licensor provides the Work (and each
146
+      Contributor provides its Contributions) on an "AS IS" BASIS,
147
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+      implied, including, without limitation, any warranties or conditions
149
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+      PARTICULAR PURPOSE. You are solely responsible for determining the
151
+      appropriateness of using or redistributing the Work and assume any
152
+      risks associated with Your exercise of permissions under this License.
153
+
154
+   8. Limitation of Liability. In no event and under no legal theory,
155
+      whether in tort (including negligence), contract, or otherwise,
156
+      unless required by applicable law (such as deliberate and grossly
157
+      negligent acts) or agreed to in writing, shall any Contributor be
158
+      liable to You for damages, including any direct, indirect, special,
159
+      incidental, or consequential damages of any character arising as a
160
+      result of this License or out of the use or inability to use the
161
+      Work (including but not limited to damages for loss of goodwill,
162
+      work stoppage, computer failure or malfunction, or any and all
163
+      other commercial damages or losses), even if such Contributor
164
+      has been advised of the possibility of such damages.
165
+
166
+   9. Accepting Warranty or Additional Liability. While redistributing
167
+      the Work or Derivative Works thereof, You may choose to offer,
168
+      and charge a fee for, acceptance of support, warranty, indemnity,
169
+      or other liability obligations and/or rights consistent with this
170
+      License. However, in accepting such obligations, You may act only
171
+      on Your own behalf and on Your sole responsibility, not on behalf
172
+      of any other Contributor, and only if You agree to indemnify,
173
+      defend, and hold each Contributor harmless for any liability
174
+      incurred by, or claims asserted against, such Contributor by reason
175
+      of your accepting any such warranty or additional liability.
176
+
177
+   END OF TERMS AND CONDITIONS
178
+
179
+   APPENDIX: How to apply the Apache License to your work.
180
+
181
+      To apply the Apache License to your work, attach the following
182
+      boilerplate notice, with the fields enclosed by brackets "[]"
183
+      replaced with your own identifying information. (Don't include
184
+      the brackets!)  The text should be enclosed in the appropriate
185
+      comment syntax for the file format. We also recommend that a
186
+      file or class name and description of purpose be included on the
187
+      same "printed page" as the copyright notice for easier
188
+      identification within third-party archives.
189
+
190
+   Copyright [yyyy] [name of copyright owner]
191
+
192
+   Licensed under the Apache License, Version 2.0 (the "License");
193
+   you may not use this file except in compliance with the License.
194
+   You may obtain a copy of the License at
195
+
196
+       http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+   Unless required by applicable law or agreed to in writing, software
199
+   distributed under the License is distributed on an "AS IS" BASIS,
200
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+   See the License for the specific language governing permissions and
202
+   limitations under the License.

+ 75
- 0
README.md Datei anzeigen

1
+# Introduction
2
+
3
+pprof is a tool for visualization and analysis of profiling data.
4
+
5
+pprof reads a collection of profiling samples in profile.proto format and
6
+generates reports to visualize and help analyze the data. It can generate both
7
+text and graphical reports (through the use of the dot visualization package).
8
+
9
+profile.proto is a protocol buffer that describes a set of callstacks
10
+and symbolization information. A common usage is to represent a set of
11
+sampled callstacks from statistical profiling. The format is
12
+described on the src/proto/profile.proto file. For details on protocol
13
+buffers, see https://developers.google.com/protocol-buffers
14
+
15
+Profiles can be read from a local file, or over http. Multiple
16
+profiles of the same type can be aggregated or compared.
17
+
18
+If the profile samples contain machine addresses, pprof can symbolize
19
+them through the use of the native binutils tools (addr2line and nm).
20
+
21
+**This is not an official Google product.**
22
+
23
+# Building pprof
24
+
25
+Prerequisites:
26
+
27
+- Go development kit: https://golang.org/dl/
28
+  Known to work with Go 1.5
29
+- Graphviz: http://www.graphviz.org/
30
+  Optional, used to generate graphic visualizations of profiles
31
+
32
+To build it, run the COMPILE.sh script. The TEST.sh script runs unit tests.
33
+
34
+# Basic usage
35
+
36
+pprof can read a profile from a file or directly from a server via http.
37
+Specify the profile input(s) in the command line, and use options to
38
+indicate how to format the report.
39
+
40
+## Generate a text report of the profile, sorted by hotness:
41
+
42
+```
43
+% pprof -top [main_binary] profile.pb.gz
44
+Where
45
+    main_binary:  Local path to the main program binary, to enable symbolization
46
+    profile.pb.gz: Local path to the profile in a compressed protobuf, or
47
+                   URL to the http service that serves a profile.
48
+```
49
+
50
+## Generate a graph in an SVG file, and open it with a web browser:
51
+
52
+```
53
+pprof -web [main_binary] profile.pb.gz
54
+```
55
+
56
+## Run pprof on interactive mode:
57
+
58
+If no output formatting option is specified, pprof runs on interactive mode,
59
+where reads the profile and accepts interactive commands for visualization and
60
+refinement of the profile.
61
+
62
+```
63
+pprof [main_binary] profile.pb.gz
64
+
65
+This will open a simple shell that takes pprof commands to generate reports.
66
+Type 'help' for available commands/options.
67
+```
68
+
69
+## Further documentation
70
+
71
+See doc/pprof.md for more detailed end-user documentation.
72
+
73
+See doc/developer/pprof.dev.md for developer documentation.
74
+
75
+See doc/developer/profile.proto.md for a description of the profile.proto format.

+ 30
- 0
TEST.sh Datei anzeigen

1
+#!/bin/bash
2
+
3
+# Copyright 2014 Google Inc. All Rights Reserved.
4
+# 
5
+# Licensed under the Apache License, Version 2.0 (the "License");
6
+# you may not use this file except in compliance with the License.
7
+# You may obtain a copy of the License at
8
+# 
9
+#     http://www.apache.org/licenses/LICENSE-2.0
10
+# 
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS,
13
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+# See the License for the specific language governing permissions and
15
+# limitations under the License.
16
+
17
+ROOT=${PWD}
18
+INTERNAL="internal/binutils internal/driver internal/elfexec internal/graph internal/report internal/symbolizer internal/symbolz"
19
+PACKAGES="profile ${INTERNAL}"
20
+
21
+echo Testing pprof packages at ${ROOT}
22
+GOPATH=${ROOT}:${ROOT}/third_party go test $PACKAGES
23
+RETCODE=$?
24
+
25
+if [ ${RETCODE} -eq 0 ]
26
+then
27
+   echo Tests were successful.
28
+else
29
+   echo Tests failed.
30
+fi

+ 14
- 0
doc/developer/pprof.dev.md Datei anzeigen

1
+This is pprof's developer documentation. It discusses how to maintain and extend
2
+pprof. It has yet to be written.
3
+
4
+# How is pprof code structured?
5
+
6
+Internal vs external packages.
7
+
8
+# External interface
9
+
10
+## Plugins
11
+
12
+## Legacy formats
13
+
14
+#  Overview of internal packages

+ 147
- 0
doc/developer/profile.proto.md Datei anzeigen

1
+This is a description of the profile.proto format.
2
+
3
+# Overview
4
+
5
+Profile.proto is a data representation for profile data. It is independent of
6
+the type of data being collected and the sampling process used to collect that
7
+data. On disk, it is represented as a gzip-compressed protocol buffer, described
8
+at src/proto/profile.proto
9
+
10
+A profile in this context refers to a collection of samples, each one
11
+representing measurements performed at a certain point in the life of a job. A
12
+sample associates a set of measurement values with a list of locations, commonly
13
+representing the program call stack when the sample was taken.
14
+
15
+Tools such as pprof analyze these samples and display this information in
16
+multiple forms, such as identifying hottest locations, building graphical call
17
+graphs or trees, etc.
18
+
19
+# General structure of a profile
20
+
21
+A profile is represented on a Profile message, which contain the following
22
+fields:
23
+
24
+* *sample*: A profile sample, with the values measured and the associated call
25
+  stack as a list of location ids. Samples with identical call stacks can be
26
+  merged by adding their respective values, element by element.
27
+* *location*: A unique place in the program, commonly mapped to a single
28
+  instruction address. It has a unique nonzero id, to be referenced from the
29
+  samples. It contains source information in the form of lines, and a mapping id
30
+  that points to a binary.
31
+* *function*: A program function as defined in the program source. It has a
32
+  unique nonzero id, referenced from the location lines. It contains a
33
+  human-readable name for the function (eg a C++ demangled name), a system name
34
+  (eg a C++ mangled name), the name of the corresponding source file, and other
35
+  function attributes.
36
+* *mapping*: A binary that is part of the program during the profile
37
+  collection. It has a unique nonzero id, referenced from the locations. It
38
+  includes details on how the binary was mapped during program execution. By
39
+  convention the main program binary is the first mapping, followed by any
40
+  shared libraries.
41
+* *string_table*: All strings in the profile are represented as indices into
42
+  this repeating field. The first string is empty, so index == 0 always
43
+  represents the empty string.
44
+
45
+# Measurement values
46
+
47
+Measurement values are represented as 64-bit integers. The profile contains an
48
+explicit description of each value represented, using a ValueType message, with
49
+two fields:
50
+
51
+* *Type*: A human-readable description of the type semantics. For example “cpu”
52
+  to represent CPU time, “wall” or “time” for wallclock time, or “memory” for
53
+  bytes allocated.
54
+* *Unit*: A human-readable name of the unit represented by the 64-bit integer
55
+  values. For example, it could be “nanoseconds” or “milliseconds” for a time
56
+  value, or “bytes” or “megabytes” for a memory size. If this is just
57
+  representing a number of events, the recommended unit name is “count”.
58
+
59
+A profile can represent multiple measurements per sample, but all samples must
60
+have the same number and type of measurements. The actual values are stored in
61
+the Sample.value fields, each one described by the corresponding
62
+Profile.sample_type field.
63
+
64
+Some profiles have a uniform period that describe the granularity of the data
65
+collection. For example, a CPU profile may have a period of 100ms, or a memory
66
+allocation profile may have a period of 512kb. Profiles can optionally describe
67
+such a value on the Profile.period and Profile.period_type fields. The profile
68
+period is meant for human consumption and does not affect the interpretation of
69
+the profiling data.
70
+
71
+By convention, the first value on all profiles is the number of samples
72
+collected at this call stack, with unit “count”. Because the profile does not
73
+describe the sampling process beyond the optional period, it must include
74
+unsampled values for all measurements. For example, a CPU profile could have
75
+value[0] == samples, and value[1] == time in milliseconds.
76
+
77
+## Locations, functions and mappings
78
+
79
+Each sample lists the id of each location where the sample was collected, in
80
+bottom-up order. Each location has an explicit unique nonzero integer id,
81
+independent of its position in the profile, and holds additional information to
82
+identify the corresponding source.
83
+
84
+The profile source is expected to perform any adjustment required to the
85
+locations in order to point to the calls in the stack. For example, if the
86
+profile source extracts the call stack by walking back over the program stack,
87
+it must adjust the instruction addresses to point to the actual call
88
+instruction, instead of the instruction that each call will return to.
89
+
90
+Sources usually generate profiles that fall into these two categories:
91
+
92
+* *Unsymbolized profiles*: These only contain instruction addresses, and are to
93
+  be symbolized by a separate tool. It is critical for each location to point to
94
+  a valid mapping, which will provide the information required for
95
+  symbolization. These are used for profiles of compiled languages, such as C++
96
+  and Go.
97
+
98
+* *Symbolized profiles*: These contain all the symbol information available for
99
+  the profile. Mappings and instruction addresses are optional for symbolized
100
+  locations. These are used for profiles of interpreted or jitted languages,
101
+  such as Java or Python.  Also, the profile format allows the generation of
102
+  mixed profiles, with symbolized and unsymbolized locations.
103
+
104
+The symbol information is represented in the repeating lines field of the
105
+Location message. A location has multiple lines if it reflects multiple program
106
+sources, for example if representing inlined call stacks. Lines reference
107
+functions by their unique nonzero id, and the source line number within the
108
+source file listed by the function. A function contains the source attributes
109
+for a function, including its name, source file, etc. Functions include both a
110
+user and a system form of the name, for example to include C++ demangled and
111
+mangled names. For profiles where only a single name exists, both should be set
112
+to the same string.
113
+
114
+Mappings are also referenced from locations by their unique nonzero id, and
115
+include all information needed to symbolize addresses within the mapping. It
116
+includes similar information to the Linux /proc/self/maps file. Locations
117
+associated to a mapping should have addresses that land between the mapping
118
+start and limit. Also, if available, mappings should include a build id to
119
+uniquely identify the version of the binary being used.
120
+
121
+## Labels
122
+
123
+Samples optionally contain labels, which are annotations to discriminate samples
124
+with identical locations. For example, a label can be used on a malloc profile
125
+to indicate allocation size, so two samples on the same call stack with sizes
126
+2MB and 4MB do not get merged into a single sample with two allocations and a
127
+size of 6MB.
128
+
129
+Labels can be string-based or numeric. They are represented by the Label
130
+message, with a key identifying the label and either a string or numeric
131
+value. For numeric labels, by convention the key represents the measurement unit
132
+of the numeric value. So for the previous example, the samples would have labels
133
+{“bytes”, 2097152} and {“bytes”, 4194304}.
134
+
135
+## Keep and drop expressions
136
+
137
+Some profile sources may have knowledge of locations that are uninteresting or
138
+irrelevant. However, if symbolization is needed in order to identify these
139
+locations, the profile source may not be able to remove them when the profile is
140
+generated. The profile format provides a mechanism to identify these frames by
141
+name, through regular expressions.
142
+
143
+These expressions must match the function name in its entirety. Frames that
144
+match Profile.drop\_frames will be dropped from the profile, along with any
145
+frames below it. Frames that match Profile.keep\_frames will be kept, even if
146
+they match drop\_frames.
147
+

+ 209
- 0
doc/pprof.md Datei anzeigen

1
+# pprof
2
+
3
+pprof is a tool for visualization and analysis of profiling data.
4
+
5
+pprof reads a collection of profiling samples in profile.proto format and
6
+generates reports to visualize and help analyze the data. It can generate both
7
+text and graphical reports (through the use of the dot visualization package).
8
+
9
+profile.proto is a protocol buffer that describes a set of callstacks
10
+and symbolization information. A common usage is to represent a set of
11
+sampled callstacks from statistical profiling. The format is
12
+described on the src/proto/profile.proto file. For details on protocol
13
+buffers, see https://developers.google.com/protocol-buffers
14
+
15
+Profiles can be read from a local file, or over http. Multiple
16
+profiles of the same type can be aggregated or compared.
17
+
18
+If the profile samples contain machine addresses, pprof can symbolize
19
+them through the use of the native binutils tools (addr2line and nm).
20
+
21
+# pprof profiles
22
+
23
+pprof operates on data in the profile.proto format. Each profile is a collection
24
+of samples, where each sample is associated to a point in a location hierarchy,
25
+one or more numeric values, and a set of labels. Often these profiles represents
26
+data collected through statistical sampling of a program, so each sample
27
+describes a program call stack and a number or weight of samples collected at a
28
+location. pprof is agnostic to the profile semantics, so other uses are
29
+possible. The interpretation of the reports generated by pprof depends on the
30
+semantics defined by the source of the profile.
31
+
32
+# General usage
33
+
34
+The objective of pprof is to generate a report for a profile. The report is
35
+generated from a location hierarchy, which is reconstructed from the profile
36
+samples. Each location contains two values: *flat* is the value of the location
37
+itself, while *cum* is the value of the location plus all its
38
+descendants. Samples that include a location multiple times (eg for recursive
39
+functions) are counted only once per location.
40
+
41
+The basic usage of pprof is
42
+
43
+    pprof <format> [options] source
44
+
45
+Where *format* selects the nature of the report, and *options* configure the
46
+contents of the report. Each option has a value, which can be boolean, numeric,
47
+or strings. While only one format can be specified, most options can be selected
48
+independently of each other.
49
+
50
+Some common pprof options are:
51
+
52
+* **-flat [default]:** Sort entries based on their flat weight, on text reports.
53
+* **-cum:** Sort entries based on cumulative weight, on text reports.
54
+* **-functions [default]:** Accumulate samples at the function level; profile
55
+  locations that describe the same function will be merged into a report entry.
56
+* **-lines:** Accumulate samples at the source line level; profile locations that
57
+  describe the same function will be merged into a report entry.
58
+* **-addresses:** Accumulate samples at the instruction address; profile locations
59
+  that describe the same function address will be merged into a report entry.
60
+* **-nodecount= _int_:** Maximum number of entries in the report. pprof will only print
61
+  this many entries and will use heuristics to select which entries to trim.
62
+* **-focus= _regex_:** Only include samples that include a report entry matching
63
+  *regex*.
64
+* **-ignore= _regex_:** Do not include samples that include a report entry matching
65
+  *regex*.
66
+* **-show= _regex_:** Only show entries that match *regex*.
67
+* **-hide= _regex_:** Do not show entries that match *regex*.
68
+
69
+Each sample in a profile may include multiple values, representing different
70
+entities associated to the sample. pprof reports include a single sample value,
71
+which by convention is the last one specified in the report. The `sample_index=`
72
+option selects which value to use, and can be set to a number (from 0 to the
73
+number of values - 1) or the name of the sample value.
74
+
75
+Sample values are numeric values associated to a unit. If pprof can recognize
76
+these units, it will attempt to scale the values to a suitable unit for
77
+visualization. The `unite=` option will force the use of a specific unit. For
78
+example, `sample_index=sec` will force any time values to be reported in
79
+seconds. pprof recognizes most common time and memory size units.
80
+
81
+## Text reports
82
+
83
+pprof text reports show the location hierarchy in text format.
84
+
85
+* **-text:** Prints the location entries, one per line, including the flat and cum
86
+  values.
87
+* **-tree:** Prints each location entry with its predecessors and successors. 
88
+* **-peek= _regex_:** Print the location entry with all its predecessors and
89
+  successors, without trimming any entries.
90
+* **-traces:** Prints each sample with a location per line.
91
+
92
+## Graphical reports
93
+
94
+pprof can generate graphical reports on the DOT format, and convert them to
95
+multiple formats using the graphviz package.
96
+
97
+These reports represent the location hierarchy as a graph, with a report entry
98
+represented as a node. Solid edges represent a direct connection between
99
+entries, while dotted edges represent a connection where some intermediate nodes
100
+have been removed. Nodes are removed using heuristics to limit the size of
101
+the graph, controlled by the *nodecount* option.
102
+
103
+The size of each node represents the flat weight of the node, and the width of
104
+each edge represents the cumulative weight of all samples going through
105
+it. Nodes are colored according to their cumulative weight, highlighting the
106
+paths with the highest cum weight.
107
+
108
+* **-dot:** Generates a report in .dot format. All other formats are generated from
109
+  this one.
110
+* **-svg:** Generates a report in SVG format.
111
+* **-web:** Generates a report in SVG format on a temp file, and starts a web
112
+  browser to view it.
113
+* **-png, -jpg, -gif, -pdf:** Generates a report in these formats,
114
+
115
+## Annotated code
116
+
117
+pprof can also generate reports of annotated source with samples associated to
118
+them. For these, the source or binaries must be locally available, and the
119
+profile must contain data with the appropriate level of detail.
120
+
121
+pprof will look for source files on its current working directory and all its
122
+ancestors. pprof will look for binaries on the directories specified in the
123
+`$PPROF_BINARY_PATH` environment variable, by default `$HOME/pprof/binaries`. It
124
+will look binaries up by name, and if the profile includes linker build ids, it
125
+will also search for them in a directory named as the build id.
126
+
127
+pprof uses the binutils tools to examine and disassemble the binaries. By
128
+default it will search for those tools in the current path, but it can also
129
+search for them in a directory pointed to by the environment variable
130
+`$PPROF_TOOLS`.
131
+
132
+* **-disasm= _regex_:** Generates an annotated source listing for functions matching
133
+  regex, with flat/cum weights for each source line.
134
+* **-list= _regex_:** Generates an annotated disassembly listing for functions
135
+  matching *regex*.
136
+* **-weblist= _regex_:** Generates a source/assembly combined annotated listing for
137
+  functions matching *regex*, and starts a web browser to display it.
138
+
139
+# Fetching profiles
140
+
141
+pprof can read profiles from a file or directly from a URL over http. Its native
142
+format is a gzipped profile.proto file, but it can also accept some legacy
143
+formats generated by [gperftools](https://github.com/gperftools/gperftools).
144
+
145
+When fetching from a URL handler, pprof accepts options to indicate how much to
146
+wait for the profile.
147
+
148
+* **-seconds= _int_:** Makes pprof request for a profile with the specified duration
149
+  in seconds. Only makes sense for profiles based on elapsed time, such as CPU
150
+  profiles.
151
+* **-timeout= _int_:** Makes pprof wait for the specified timeout when retrieving a
152
+  profile over http. If not specified, pprof will use heuristics to determine a
153
+  reasonable timeout.
154
+
155
+If multiple profiles are specified, pprof will fetch them all and merge
156
+them. This is useful to combine profiles from multiple processes of a
157
+distributed job. The profiles may be from different programs but must be
158
+compatible (for example, CPU profiles cannot be combined with heap profiles).
159
+
160
+pprof can subtract a profile from another in order to compare them. For that,
161
+use the **-base= _profile_** option, where *profile* is the filename or URL for the
162
+profile to be subtracted. This may result on some report entries having negative
163
+values.
164
+
165
+## Symbolization
166
+
167
+pprof can add symbol information to a profile that was collected only with
168
+address information. This is useful for profiles for compiled languages, where
169
+it may not be easy or even possible for the profile source to include function
170
+names or source coordinates.
171
+
172
+pprof can extract the symbol information locally by examining the binaries using
173
+the binutils tools, or it can ask running jobs that provide a symbolization
174
+interface.
175
+
176
+pprof will attempt symbolizing profiles by default, and its `-symbolize` option
177
+provides some control over symbolization:
178
+
179
+* **-symbolize=none:** Disables any symbolization from pprof.
180
+
181
+* **-symbolize=local:** Only attempts symbolizing the profile from local
182
+  binaries using the binutils tools.
183
+
184
+* **-symbolize=remote:** Only attempts to symbolize running jobs by contacting
185
+  their symbolization handler.
186
+
187
+For local symbolization, pprof will look for the binaries on the paths specified
188
+by the profile, and then it will search for them on the path specified by the
189
+environment variable `$PPROF_BINARY_PATH`. Also, the name of the main binary can
190
+be passed directly to pprof as its first parameter, to override the name or
191
+location of the main binary of the profile, like this:
192
+
193
+    pprof /path/to/binary profile.pb.gz
194
+
195
+By default pprof will attempt to demangle and simplify C++ names, to provide
196
+readable names for C++ symbols. It will aggressively discard template and
197
+function parameters. This can be controlled with the `-symbolize=demangle`
198
+option. Note that for remote symbolization mangled names may not be provided by
199
+the symbolization handler.
200
+
201
+* **--symbolize=demangle=none:** Do not perform any demangling. Show mangled
202
+  names if available.
203
+
204
+* **-symbolize=demangle=full:** Demangle, but do not perform any
205
+  simplification. Show full demangled names if available.
206
+
207
+* **-symbolize=demangle=templates:** Demangle, and trim function parameters, but
208
+  not template parameters.
209
+

+ 280
- 0
src/driver/driver.go Datei anzeigen

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
+
15
+// Package driver provides an external entry point to the pprof driver.
16
+package driver
17
+
18
+import (
19
+	internaldriver "internal/driver"
20
+	"internal/plugin"
21
+	"io"
22
+	"regexp"
23
+	"time"
24
+
25
+	"profile"
26
+)
27
+
28
+// PProf acquires a profile, and symbolizes it using a profile
29
+// manager. Then it generates a report formatted according to the
30
+// options selected through the flags package.
31
+func PProf(o *Options) error {
32
+	return internaldriver.PProf(o.InternalOptions())
33
+}
34
+
35
+func (o *Options) InternalOptions() *plugin.Options {
36
+	var obj plugin.ObjTool
37
+	if o.Obj != nil {
38
+		obj = &internalObjTool{o.Obj}
39
+	}
40
+	var sym plugin.Symbolizer
41
+	if o.Sym != nil {
42
+		sym = &internalSymbolizer{o.Sym}
43
+	}
44
+	return &plugin.Options{
45
+		o.Writer,
46
+		o.Flagset,
47
+		o.Fetch,
48
+		sym,
49
+		obj,
50
+		o.UI,
51
+	}
52
+}
53
+
54
+// Options groups all the optional plugins into pprof.
55
+type Options struct {
56
+	Writer  Writer
57
+	Flagset FlagSet
58
+	Fetch   Fetcher
59
+	Sym     Symbolizer
60
+	Obj     ObjTool
61
+	UI      UI
62
+}
63
+
64
+// Writer provides a mechanism to write data under a certain name,
65
+// typically a filename.
66
+type Writer interface {
67
+	Open(name string) (io.WriteCloser, error)
68
+}
69
+
70
+// A FlagSet creates and parses command-line flags.
71
+// It is similar to the standard flag.FlagSet.
72
+type FlagSet interface {
73
+	// Bool, Int, Float64, and String define new flags,
74
+	// like the functions of the same name in package flag.
75
+	Bool(name string, def bool, usage string) *bool
76
+	Int(name string, def int, usage string) *int
77
+	Float64(name string, def float64, usage string) *float64
78
+	String(name string, def string, usage string) *string
79
+
80
+	// BoolVar, IntVar, Float64Var, and StringVar define new flags referencing
81
+	// a given pointer, like the functions of the same name in package flag.
82
+	BoolVar(pointer *bool, name string, def bool, usage string)
83
+	IntVar(pointer *int, name string, def int, usage string)
84
+	Float64Var(pointer *float64, name string, def float64, usage string)
85
+	StringVar(pointer *string, name string, def string, usage string)
86
+
87
+	// StringList is similar to String but allows multiple values for a
88
+	// single flag
89
+	StringList(name string, def string, usage string) *[]*string
90
+
91
+	// ExtraUsage returns any additional text that should be
92
+	// printed after the standard usage message.
93
+	// The typical use of ExtraUsage is to show any custom flags
94
+	// defined by the specific pprof plugins being used.
95
+	ExtraUsage() string
96
+
97
+	// Parse initializes the flags with their values for this run
98
+	// and returns the non-flag command line arguments.
99
+	// If an unknown flag is encountered or there are no arguments,
100
+	// Parse should call usage and return nil.
101
+	Parse(usage func()) []string
102
+}
103
+
104
+// A Fetcher reads and returns the profile named by src, using
105
+// the specified duration and timeout. It returns the fetched
106
+// profile and a string indicating a URL from where the profile
107
+// was fetched, which may be different than src.
108
+type Fetcher interface {
109
+	Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error)
110
+}
111
+
112
+// A Symbolizer introduces symbol information into a profile.
113
+type Symbolizer interface {
114
+	Symbolize(mode string, srcs MappingSources, prof *profile.Profile) error
115
+}
116
+
117
+// MappingSources map each profile.Mapping to the source of the profile.
118
+// The key is either Mapping.File or Mapping.BuildId.
119
+type MappingSources map[string][]struct {
120
+	Source string // URL of the source the mapping was collected from
121
+	Start  uint64 // delta applied to addresses from this source (to represent Merge adjustments)
122
+}
123
+
124
+// An ObjTool inspects shared libraries and executable files.
125
+type ObjTool interface {
126
+	// Open opens the named object file.  If the object is a shared
127
+	// library, start/limit/offset are the addresses where it is mapped
128
+	// into memory in the address space being inspected.
129
+	Open(file string, start, limit, offset uint64) (ObjFile, error)
130
+
131
+	// Disasm disassembles the named object file, starting at
132
+	// the start address and stopping at (before) the end address.
133
+	Disasm(file string, start, end uint64) ([]Inst, error)
134
+}
135
+
136
+// An Inst is a single instruction in an assembly listing.
137
+type Inst struct {
138
+	Addr uint64 // virtual address of instruction
139
+	Text string // instruction text
140
+	File string // source file
141
+	Line int    // source line
142
+}
143
+
144
+// An ObjFile is a single object file: a shared library or executable.
145
+type ObjFile interface {
146
+	// Name returns the underlying file name, if available.
147
+	Name() string
148
+
149
+	// Base returns the base address to use when looking up symbols in the file.
150
+	Base() uint64
151
+
152
+	// BuildID returns the GNU build ID of the file, or an empty string.
153
+	BuildID() string
154
+
155
+	// SourceLine reports the source line information for a given
156
+	// address in the file. Due to inlining, the source line information
157
+	// is in general a list of positions representing a call stack,
158
+	// with the leaf function first.
159
+	SourceLine(addr uint64) ([]Frame, error)
160
+
161
+	// Symbols returns a list of symbols in the object file.
162
+	// If r is not nil, Symbols restricts the list to symbols
163
+	// with names matching the regular expression.
164
+	// If addr is not zero, Symbols restricts the list to symbols
165
+	// containing that address.
166
+	Symbols(r *regexp.Regexp, addr uint64) ([]*Sym, error)
167
+
168
+	// Close closes the file, releasing associated resources.
169
+	Close() error
170
+}
171
+
172
+// A Frame describes a single line in a source file.
173
+type Frame struct {
174
+	Func string // name of function
175
+	File string // source file name
176
+	Line int    // line in file
177
+}
178
+
179
+// A Sym describes a single symbol in an object file.
180
+type Sym struct {
181
+	Name  []string // names of symbol (many if symbol was dedup'ed)
182
+	File  string   // object file containing symbol
183
+	Start uint64   // start virtual address
184
+	End   uint64   // virtual address of last byte in sym (Start+size-1)
185
+}
186
+
187
+// A UI manages user interactions.
188
+type UI interface {
189
+	// Read returns a line of text (a command) read from the user.
190
+	// prompt is printed before reading the command.
191
+	ReadLine(prompt string) (string, error)
192
+
193
+	// Print shows a message to the user.
194
+	// It formats the text as fmt.Print would and adds a final \n if not already present.
195
+	// For line-based UI, Print writes to standard error.
196
+	// (Standard output is reserved for report data.)
197
+	Print(...interface{})
198
+
199
+	// PrintErr shows an error message to the user.
200
+	// It formats the text as fmt.Print would and adds a final \n if not already present.
201
+	// For line-based UI, PrintErr writes to standard error.
202
+	PrintErr(...interface{})
203
+
204
+	// IsTerminal returns whether the UI is known to be tied to an
205
+	// interactive terminal (as opposed to being redirected to a file).
206
+	IsTerminal() bool
207
+
208
+	// SetAutoComplete instructs the UI to call complete(cmd) to obtain
209
+	// the auto-completion of cmd, if the UI supports auto-completion at all.
210
+	SetAutoComplete(complete func(string) string)
211
+}
212
+
213
+// internalObjTool is a wrapper to map from the pprof external
214
+// interface to the internal interface.
215
+type internalObjTool struct {
216
+	ObjTool
217
+}
218
+
219
+func (o *internalObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
220
+	f, err := o.ObjTool.Open(file, start, limit, offset)
221
+	if err != nil {
222
+		return nil, err
223
+	}
224
+	return &internalObjFile{f}, err
225
+}
226
+
227
+type internalObjFile struct {
228
+	ObjFile
229
+}
230
+
231
+func (f *internalObjFile) SourceLine(frame uint64) ([]plugin.Frame, error) {
232
+	frames, err := f.ObjFile.SourceLine(frame)
233
+	if err != nil {
234
+		return nil, err
235
+	}
236
+	var pluginFrames []plugin.Frame
237
+	for _, f := range frames {
238
+		pluginFrames = append(pluginFrames, plugin.Frame(f))
239
+	}
240
+	return pluginFrames, nil
241
+}
242
+
243
+func (f *internalObjFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
244
+	syms, err := f.ObjFile.Symbols(r, addr)
245
+	if err != nil {
246
+		return nil, err
247
+	}
248
+	var pluginSyms []*plugin.Sym
249
+	for _, s := range syms {
250
+		ps := plugin.Sym(*s)
251
+		pluginSyms = append(pluginSyms, &ps)
252
+	}
253
+	return pluginSyms, nil
254
+}
255
+
256
+func (o *internalObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
257
+	insts, err := o.ObjTool.Disasm(file, start, end)
258
+	if err != nil {
259
+		return nil, err
260
+	}
261
+	var pluginInst []plugin.Inst
262
+	for _, inst := range insts {
263
+		pluginInst = append(pluginInst, plugin.Inst(inst))
264
+	}
265
+	return pluginInst, nil
266
+}
267
+
268
+// internalSymbolizer is a wrapper to map from the pprof external
269
+// interface to the internal interface.
270
+type internalSymbolizer struct {
271
+	Symbolizer
272
+}
273
+
274
+func (s *internalSymbolizer) Symbolize(mode string, srcs plugin.MappingSources, prof *profile.Profile) error {
275
+	isrcs := plugin.MappingSources{}
276
+	for m, s := range srcs {
277
+		isrcs[m] = s
278
+	}
279
+	return s.Symbolize(mode, isrcs, prof)
280
+}

+ 179
- 0
src/internal/binutils/addr2liner.go Datei anzeigen

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
+
15
+package binutils
16
+
17
+import (
18
+	"bufio"
19
+	"fmt"
20
+	"io"
21
+	"os/exec"
22
+	"strconv"
23
+	"strings"
24
+
25
+	"internal/plugin"
26
+)
27
+
28
+const (
29
+	defaultAddr2line = "addr2line"
30
+
31
+	// addr2line may produce multiple lines of output. We
32
+	// use this sentinel to identify the end of the output.
33
+	sentinel = ^uint64(0)
34
+)
35
+
36
+// Addr2Liner is a connection to an addr2line command for obtaining
37
+// address and line number information from a binary.
38
+type addr2Liner struct {
39
+	filename string
40
+	cmd      *exec.Cmd
41
+	in       io.WriteCloser
42
+	out      *bufio.Reader
43
+	err      error
44
+
45
+	base uint64
46
+}
47
+
48
+// newAddr2liner starts the given addr2liner command reporting
49
+// information about the given executable file. If file is a shared
50
+// library, base should be the address at which is was mapped in the
51
+// program under consideration.
52
+func newAddr2Liner(cmd, file string, base uint64) (*addr2Liner, error) {
53
+	if cmd == "" {
54
+		cmd = defaultAddr2line
55
+	}
56
+
57
+	a := &addr2Liner{
58
+		filename: file,
59
+		base:     base,
60
+		cmd:      exec.Command(cmd, "-aif", "-e", file),
61
+	}
62
+
63
+	var err error
64
+	if a.in, err = a.cmd.StdinPipe(); err != nil {
65
+		return nil, err
66
+	}
67
+
68
+	outPipe, err := a.cmd.StdoutPipe()
69
+	if err != nil {
70
+		return nil, err
71
+	}
72
+
73
+	a.out = bufio.NewReader(outPipe)
74
+	if err := a.cmd.Start(); err != nil {
75
+		return nil, err
76
+	}
77
+	return a, nil
78
+}
79
+
80
+// close releases any resources used by the addr2liner object.
81
+func (d *addr2Liner) close() {
82
+	d.in.Close()
83
+	d.cmd.Wait()
84
+}
85
+
86
+func (d *addr2Liner) readString() (s string) {
87
+	if d.err != nil {
88
+		return ""
89
+	}
90
+	if s, d.err = d.out.ReadString('\n'); d.err != nil {
91
+		return ""
92
+	}
93
+	return strings.TrimSpace(s)
94
+}
95
+
96
+// readFrame parses the addr2line output for a single address. It
97
+// returns a populated plugin.Frame and whether it has reached the end of the
98
+// data.
99
+func (d *addr2Liner) readFrame() (plugin.Frame, bool) {
100
+	funcname := d.readString()
101
+
102
+	if strings.HasPrefix(funcname, "0x") {
103
+		// If addr2line returns a hex address we can assume it is the
104
+		// sentinel.  Read and ignore next two lines of output from
105
+		// addr2line
106
+		d.readString()
107
+		d.readString()
108
+		return plugin.Frame{}, true
109
+	}
110
+
111
+	fileline := d.readString()
112
+	if d.err != nil {
113
+		return plugin.Frame{}, true
114
+	}
115
+
116
+	linenumber := 0
117
+
118
+	if funcname == "??" {
119
+		funcname = ""
120
+	}
121
+
122
+	if fileline == "??:0" {
123
+		fileline = ""
124
+	} else {
125
+		if i := strings.LastIndex(fileline, ":"); i >= 0 {
126
+			// Remove discriminator, if present
127
+			if disc := strings.Index(fileline, " (discriminator"); disc > 0 {
128
+				fileline = fileline[:disc]
129
+			}
130
+			// If we cannot parse a number after the last ":", keep it as
131
+			// part of the filename.
132
+			if line, err := strconv.Atoi(fileline[i+1:]); err == nil {
133
+				linenumber = line
134
+				fileline = fileline[:i]
135
+			}
136
+		}
137
+	}
138
+
139
+	return plugin.Frame{funcname, fileline, linenumber}, false
140
+}
141
+
142
+// addrInfo returns the stack frame information for a specific program
143
+// address. It returns nil if the address could not be identified.
144
+func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {
145
+	if d.err != nil {
146
+		return nil, d.err
147
+	}
148
+
149
+	if _, d.err = fmt.Fprintf(d.in, "%x\n", addr-d.base); d.err != nil {
150
+		return nil, d.err
151
+	}
152
+
153
+	if _, d.err = fmt.Fprintf(d.in, "%x\n", sentinel); d.err != nil {
154
+		return nil, d.err
155
+	}
156
+
157
+	resp := d.readString()
158
+	if d.err != nil {
159
+		return nil, d.err
160
+	}
161
+
162
+	if !strings.HasPrefix(resp, "0x") {
163
+		d.err = fmt.Errorf("unexpected addr2line output: %s", resp)
164
+		return nil, d.err
165
+	}
166
+
167
+	var stack []plugin.Frame
168
+	for {
169
+		frame, end := d.readFrame()
170
+		if end {
171
+			break
172
+		}
173
+
174
+		if frame != (plugin.Frame{}) {
175
+			stack = append(stack, frame)
176
+		}
177
+	}
178
+	return stack, d.err
179
+}

+ 122
- 0
src/internal/binutils/addr2liner_nm.go Datei anzeigen

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
+
15
+package binutils
16
+
17
+import (
18
+	"bufio"
19
+	"bytes"
20
+	"io"
21
+	"os/exec"
22
+	"strconv"
23
+	"strings"
24
+
25
+	"internal/plugin"
26
+)
27
+
28
+const (
29
+	defaultNM = "nm"
30
+)
31
+
32
+// addr2LinerNM is a connection to an nm command for obtaining address
33
+// information from a binary.
34
+type addr2LinerNM struct {
35
+	m []symbolInfo // Sorted list of addresses from binary.
36
+}
37
+
38
+type symbolInfo struct {
39
+	address uint64
40
+	name    string
41
+}
42
+
43
+//  newAddr2LinerNM starts the given nm command reporting information about the
44
+// given executable file. If file is a shared library, base should be
45
+// the address at which is was mapped in the program under
46
+// consideration.
47
+func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) {
48
+	if cmd == "" {
49
+		cmd = defaultNM
50
+	}
51
+
52
+	a := &addr2LinerNM{
53
+		m: []symbolInfo{},
54
+	}
55
+
56
+	var b bytes.Buffer
57
+	c := exec.Command(cmd, "-n", file)
58
+	c.Stdout = &b
59
+
60
+	if err := c.Run(); err != nil {
61
+		return nil, err
62
+	}
63
+
64
+	// Parse addr2line output and populate symbol map.
65
+	// Skip lines we fail to parse.
66
+	buf := bufio.NewReader(&b)
67
+	for {
68
+		line, err := buf.ReadString('\n')
69
+		if line == "" && err != nil {
70
+			if err == io.EOF {
71
+				break
72
+			}
73
+			return nil, err
74
+		}
75
+		fields := strings.SplitN(line, " ", 3)
76
+		if len(fields) != 3 {
77
+			continue
78
+		}
79
+		address, err := strconv.ParseUint(fields[0], 16, 64)
80
+		if err != nil {
81
+			continue
82
+		}
83
+		a.m = append(a.m, symbolInfo{
84
+			address: address + base,
85
+			name:    fields[2],
86
+		})
87
+	}
88
+
89
+	return a, nil
90
+}
91
+
92
+// addrInfo returns the stack frame information for a specific program
93
+// address. It returns nil if the address could not be identified.
94
+func (a *addr2LinerNM) addrInfo(addr uint64) ([]plugin.Frame, error) {
95
+	if len(a.m) == 0 || addr < a.m[0].address || addr > a.m[len(a.m)-1].address {
96
+		return nil, nil
97
+	}
98
+
99
+	// Binary search. Search until low, high are separated by 1.
100
+	low, high := 0, len(a.m)
101
+	for low+1 < high {
102
+		mid := (low + high) / 2
103
+		v := a.m[mid].address
104
+		if addr == v {
105
+			low = mid
106
+			break
107
+		} else if addr > v {
108
+			low = mid
109
+		} else {
110
+			high = mid
111
+		}
112
+	}
113
+
114
+	// Address is between a.m[low] and a.m[high].
115
+	// Pick low, as it represents [low, high).
116
+	f := []plugin.Frame{
117
+		{
118
+			Func: a.m[low].name,
119
+		},
120
+	}
121
+	return f, nil
122
+}

+ 242
- 0
src/internal/binutils/binutils.go Datei anzeigen

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
+
15
+// Package binutils provides access to the GNU binutils.
16
+package binutils
17
+
18
+import (
19
+	"debug/elf"
20
+	"fmt"
21
+	"os"
22
+	"os/exec"
23
+	"path/filepath"
24
+	"regexp"
25
+	"strings"
26
+
27
+	"internal/elfexec"
28
+	"internal/plugin"
29
+)
30
+
31
+// A Binutils implements plugin.ObjTool by invoking the GNU binutils.
32
+// SetConfig must be called before any of the other methods.
33
+type Binutils struct {
34
+	// Commands to invoke.
35
+	addr2line string
36
+	nm        string
37
+	objdump   string
38
+
39
+	// if fast, perform symbolization using nm (symbol names only),
40
+	// instead of file-line detail from the slower addr2line.
41
+	fast bool
42
+}
43
+
44
+// SetFastSymbolization sets a toggle that makes binutils use fast
45
+// symbolization (using nm), which is much faster than addr2line but
46
+// provides only symbol name information (no file/line).
47
+func (b *Binutils) SetFastSymbolization(fast bool) {
48
+	b.fast = fast
49
+}
50
+
51
+// SetTools processes the contents of the tools option. It
52
+// expects a set of entries separated by commas; each entry is a pair
53
+// of the form t:path, where cmd will be used to look only for the
54
+// tool named t. If t is not specified, the path is searched for all
55
+// tools.
56
+func (b *Binutils) SetTools(config string) {
57
+	// paths collect paths per tool; Key "" contains the default.
58
+	paths := make(map[string][]string)
59
+	for _, t := range strings.Split(config, ",") {
60
+		name, path := "", t
61
+		if ct := strings.SplitN(t, ":", 2); len(ct) == 2 {
62
+			name, path = ct[0], ct[1]
63
+		}
64
+		paths[name] = append(paths[name], path)
65
+	}
66
+
67
+	defaultPath := paths[""]
68
+	b.addr2line = findExe("addr2line", append(paths["addr2line"], defaultPath...))
69
+	b.nm = findExe("nm", append(paths["nm"], defaultPath...))
70
+	b.objdump = findExe("objdump", append(paths["objdump"], defaultPath...))
71
+}
72
+
73
+// findExe looks for an executable command on a set of paths.
74
+// If it cannot find it, returns cmd.
75
+func findExe(cmd string, paths []string) string {
76
+	for _, p := range paths {
77
+		cp := filepath.Join(p, cmd)
78
+		if c, err := exec.LookPath(cp); err == nil {
79
+			return c
80
+		}
81
+	}
82
+	return cmd
83
+}
84
+
85
+// Disasm returns the assembly instructions for the specified address range
86
+// of a binary.
87
+func (b *Binutils) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
88
+	if b.addr2line == "" {
89
+		// Update the command invocations if not initialized.
90
+		b.SetTools("")
91
+	}
92
+	return disassemble(b.objdump, file, start, end)
93
+}
94
+
95
+// Open satisfies the plugin.ObjTool interface.
96
+func (b *Binutils) Open(name string, start, limit, offset uint64) (plugin.ObjFile, error) {
97
+	if b.addr2line == "" {
98
+		// Update the command invocations if not initialized.
99
+		b.SetTools("")
100
+	}
101
+
102
+	// Make sure file is a supported executable.
103
+	// The pprof driver uses Open to sniff the difference
104
+	// between an executable and a profile.
105
+	// For now, only ELF is supported.
106
+	// Could read the first few bytes of the file and
107
+	// use a table of prefixes if we need to support other
108
+	// systems at some point.
109
+
110
+	f, err := os.Open(name)
111
+	if err != nil {
112
+		// For testing, do not require file name to exist.
113
+		if strings.Contains(b.addr2line, "testdata/") {
114
+			return &fileAddr2Line{file: file{b: b, name: name}}, nil
115
+		}
116
+
117
+		return nil, err
118
+	}
119
+	defer f.Close()
120
+
121
+	ef, err := elf.NewFile(f)
122
+	if err != nil {
123
+		return nil, fmt.Errorf("Parsing %s: %v", name, err)
124
+	}
125
+
126
+	var stextOffset *uint64
127
+	var pageAligned = func(addr uint64) bool { return addr%4096 == 0 }
128
+	if strings.Contains(name, "vmlinux") || !pageAligned(start) || !pageAligned(limit) || !pageAligned(offset) {
129
+		// Reading all Symbols is expensive, and we only rarely need it so
130
+		// we don't want to do it every time.  But if _stext happens to be
131
+		// page-aligned but isn't the same as Vaddr, we would symbolize
132
+		// wrong.  So if the name the addresses aren't page aligned, or if
133
+		// the name is "vmlinux" we read _stext.  We can be wrong if: (1)
134
+		// someone passes a kernel path that doesn't contain "vmlinux" AND
135
+		// (2) _stext is page-aligned AND (3) _stext is not at Vaddr
136
+		symbols, err := ef.Symbols()
137
+		if err != nil {
138
+			return nil, err
139
+		}
140
+		for _, s := range symbols {
141
+			if s.Name == "_stext" {
142
+				// The kernel may use _stext as the mapping start address.
143
+				stextOffset = &s.Value
144
+				break
145
+			}
146
+		}
147
+	}
148
+
149
+	base, err := elfexec.GetBase(&ef.FileHeader, nil, stextOffset, start, limit, offset)
150
+	if err != nil {
151
+		return nil, fmt.Errorf("Could not identify base for %s: %v", name, err)
152
+	}
153
+
154
+	// Find build ID, while we have the file open.
155
+	buildID := ""
156
+	if id, err := elfexec.GetBuildID(f); err == nil {
157
+		buildID = fmt.Sprintf("%x", id)
158
+	}
159
+	if b.fast {
160
+		return &fileNM{file: file{b, name, base, buildID}}, nil
161
+	}
162
+	return &fileAddr2Line{file: file{b, name, base, buildID}}, nil
163
+}
164
+
165
+// file implements the binutils.ObjFile interface.
166
+type file struct {
167
+	b       *Binutils
168
+	name    string
169
+	base    uint64
170
+	buildID string
171
+}
172
+
173
+func (f *file) Name() string {
174
+	return f.name
175
+}
176
+
177
+func (f *file) Base() uint64 {
178
+	return f.base
179
+}
180
+
181
+func (f *file) BuildID() string {
182
+	return f.buildID
183
+}
184
+
185
+func (f *file) SourceLine(addr uint64) ([]plugin.Frame, error) {
186
+	return []plugin.Frame{}, nil
187
+}
188
+
189
+func (f *file) Close() error {
190
+	return nil
191
+}
192
+
193
+func (f *file) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
194
+	return findSymbols(f.b.nm, f.name, r, addr)
195
+}
196
+
197
+// fileNM implements the binutils.ObjFile interface, using 'nm' to map
198
+// addresses to symbols (without file/line number information). It is
199
+// faster than fileAddr2Line.
200
+type fileNM struct {
201
+	file
202
+	addr2linernm *addr2LinerNM
203
+}
204
+
205
+func (f *fileNM) SourceLine(addr uint64) ([]plugin.Frame, error) {
206
+	if f.addr2linernm == nil {
207
+		addr2liner, err := newAddr2LinerNM(f.b.nm, f.name, f.base)
208
+		if err != nil {
209
+			return nil, err
210
+		}
211
+		f.addr2linernm = addr2liner
212
+	}
213
+	return f.addr2linernm.addrInfo(addr)
214
+}
215
+
216
+// fileAddr2Line implements the binutils.ObjFile interface, using
217
+// 'addr2line' to map addresses to symbols (with file/line number
218
+// information). It can be slow for large binaries with debug
219
+// information.
220
+type fileAddr2Line struct {
221
+	file
222
+	addr2liner *addr2Liner
223
+}
224
+
225
+func (f *fileAddr2Line) SourceLine(addr uint64) ([]plugin.Frame, error) {
226
+	if f.addr2liner == nil {
227
+		addr2liner, err := newAddr2Liner(f.b.addr2line, f.name, f.base)
228
+		if err != nil {
229
+			return nil, err
230
+		}
231
+		f.addr2liner = addr2liner
232
+	}
233
+	return f.addr2liner.addrInfo(addr)
234
+}
235
+
236
+func (f *fileAddr2Line) Close() error {
237
+	if f.addr2liner != nil {
238
+		f.addr2liner.close()
239
+		f.addr2liner = nil
240
+	}
241
+	return nil
242
+}

+ 111
- 0
src/internal/binutils/binutils_test.go Datei anzeigen

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
+
15
+package binutils
16
+
17
+import (
18
+	"fmt"
19
+	"testing"
20
+
21
+	"internal/plugin"
22
+)
23
+
24
+var testAddrMap = map[int]string{
25
+	1000: "_Z3fooid.clone2",
26
+	2000: "_ZNSaIiEC1Ev.clone18",
27
+	3000: "_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm",
28
+}
29
+
30
+func functionName(level int) (name string) {
31
+	if name = testAddrMap[level]; name != "" {
32
+		return name
33
+	}
34
+	return fmt.Sprintf("fun%d", level)
35
+}
36
+
37
+func TestAddr2Liner(t *testing.T) {
38
+	const offset = 0x500
39
+
40
+	a, err := newAddr2Liner("testdata/wrapper/addr2line", "executable", offset)
41
+	if err != nil {
42
+		t.Fatalf("Addr2Liner Open: %v", err)
43
+	}
44
+
45
+	for i := 1; i < 8; i++ {
46
+		addr := i*0x1000 + offset
47
+		s, err := a.addrInfo(uint64(addr))
48
+		if err != nil {
49
+			t.Fatalf("addrInfo(%#x): %v", addr, err)
50
+		}
51
+		if len(s) != i {
52
+			t.Fatalf("addrInfo(%#x): got len==%d, want %d", addr, len(s), i)
53
+		}
54
+		for l, f := range s {
55
+			level := (len(s) - l) * 1000
56
+			want := plugin.Frame{functionName(level), fmt.Sprintf("file%d", level), level}
57
+
58
+			if f != want {
59
+				t.Errorf("AddrInfo(%#x)[%d]: = %+v, want %+v", addr, l, f, want)
60
+			}
61
+		}
62
+	}
63
+	s, err := a.addrInfo(0xFFFF)
64
+	if err != nil {
65
+		t.Fatalf("addrInfo(0xFFFF): %v", err)
66
+	}
67
+	if len(s) != 0 {
68
+		t.Fatalf("AddrInfo(0xFFFF): got len==%d, want 0", len(s))
69
+	}
70
+	a.close()
71
+}
72
+
73
+func TestAddr2LinerLookup(t *testing.T) {
74
+	oddSizedMap := addr2LinerNM{
75
+		m: []symbolInfo{
76
+			{0x1000, "0x1000"},
77
+			{0x2000, "0x2000"},
78
+			{0x3000, "0x3000"},
79
+		},
80
+	}
81
+	evenSizedMap := addr2LinerNM{
82
+		m: []symbolInfo{
83
+			{0x1000, "0x1000"},
84
+			{0x2000, "0x2000"},
85
+			{0x3000, "0x3000"},
86
+			{0x4000, "0x4000"},
87
+		},
88
+	}
89
+	for _, a := range []*addr2LinerNM{
90
+		&oddSizedMap, &evenSizedMap,
91
+	} {
92
+		for address, want := range map[uint64]string{
93
+			0x1000: "0x1000",
94
+			0x1001: "0x1000",
95
+			0x1FFF: "0x1000",
96
+			0x2000: "0x2000",
97
+			0x2001: "0x2000",
98
+		} {
99
+			if got, _ := a.addrInfo(address); !checkAddress(got, address, want) {
100
+				t.Errorf("%x: got %v, want %s", address, got, want)
101
+			}
102
+		}
103
+	}
104
+}
105
+
106
+func checkAddress(got []plugin.Frame, address uint64, want string) bool {
107
+	if len(got) != 1 {
108
+		return false
109
+	}
110
+	return got[0].Func == want
111
+}

+ 156
- 0
src/internal/binutils/disasm.go Datei anzeigen

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
+
15
+package binutils
16
+
17
+import (
18
+	"bytes"
19
+	"fmt"
20
+	"io"
21
+	"os/exec"
22
+	"regexp"
23
+	"strconv"
24
+
25
+	"internal/plugin"
26
+	"golang/demangle"
27
+)
28
+
29
+var (
30
+	nmOutputRE            = regexp.MustCompile(`^\s*([[:xdigit:]]+)\s+(.)\s+(.*)`)
31
+	objdumpAsmOutputRE    = regexp.MustCompile(`^\s*([[:xdigit:]]+):\s+(.*)`)
32
+	objdumpOutputFileLine = regexp.MustCompile(`^(.*):([0-9]+)`)
33
+)
34
+
35
+func findSymbols(nm, file string, r *regexp.Regexp, address uint64) ([]*plugin.Sym, error) {
36
+	// Get from nm a list of symbols sorted by address.
37
+	cmd := exec.Command(nm, "-n", file)
38
+	out, err := cmd.Output()
39
+	if err != nil {
40
+		return nil, fmt.Errorf("%v: %v", cmd.Args, err)
41
+	}
42
+
43
+	// Collect all symbols from the nm output, grouping names mapped to
44
+	// the same address into a single symbol.
45
+	var symbols []*plugin.Sym
46
+	names, start := []string{}, uint64(0)
47
+	buf := bytes.NewBuffer(out)
48
+	for symAddr, name, err := nextSymbol(buf); err == nil; symAddr, name, err = nextSymbol(buf) {
49
+		if err != nil {
50
+			return nil, err
51
+		}
52
+		if start == symAddr {
53
+			names = append(names, name)
54
+			continue
55
+		}
56
+		if match := matchSymbol(names, start, symAddr-1, r, address); match != nil {
57
+			symbols = append(symbols, &plugin.Sym{match, file, start, symAddr - 1})
58
+		}
59
+		names, start = []string{name}, symAddr
60
+	}
61
+
62
+	return symbols, nil
63
+}
64
+
65
+// matchSymbol checks if a symbol is to be selected by checking its
66
+// name to the regexp and optionally its address. It returns the name(s)
67
+// to be used for the matched symbol, or nil if no match
68
+func matchSymbol(names []string, start, end uint64, r *regexp.Regexp, address uint64) []string {
69
+	if address != 0 && address >= start && address <= end {
70
+		return names
71
+	}
72
+	for _, name := range names {
73
+		if r.MatchString(name) {
74
+			return []string{name}
75
+		}
76
+
77
+		// Match all possible demangled versions of the name.
78
+		for _, o := range [][]demangle.Option{
79
+			{demangle.NoClones},
80
+			{demangle.NoParams},
81
+			{demangle.NoParams, demangle.NoTemplateParams},
82
+		} {
83
+			if demangled, err := demangle.ToString(name, o...); err == nil && r.MatchString(demangled) {
84
+				return []string{demangled}
85
+			}
86
+		}
87
+	}
88
+	return nil
89
+}
90
+
91
+// disassemble returns the assembly instructions in a function from a
92
+// binary file. It uses objdump to obtain the assembly listing.
93
+func disassemble(objdump string, file string, start, stop uint64) ([]plugin.Inst, error) {
94
+	cmd := exec.Command(objdump, "-d", "-C", "--no-show-raw-insn", "-l",
95
+		fmt.Sprintf("--start-address=%#x", start),
96
+		fmt.Sprintf("--stop-address=%#x", stop),
97
+		file)
98
+	out, err := cmd.Output()
99
+	if err != nil {
100
+		return nil, fmt.Errorf("%v: %v", cmd.Args, err)
101
+	}
102
+
103
+	buf := bytes.NewBuffer(out)
104
+	file, line := "", 0
105
+	var assembly []plugin.Inst
106
+	for {
107
+		input, err := buf.ReadString('\n')
108
+		if err != nil {
109
+			if err != io.EOF {
110
+				return nil, err
111
+			}
112
+			if input == "" {
113
+				break
114
+			}
115
+		}
116
+
117
+		if fields := objdumpAsmOutputRE.FindStringSubmatch(input); len(fields) == 3 {
118
+			if address, err := strconv.ParseUint(fields[1], 16, 64); err == nil {
119
+				assembly = append(assembly,
120
+					plugin.Inst{
121
+						Addr: address,
122
+						Text: fields[2],
123
+						File: file,
124
+						Line: line,
125
+					})
126
+				continue
127
+			}
128
+		}
129
+		if fields := objdumpOutputFileLine.FindStringSubmatch(input); len(fields) == 3 {
130
+			if l, err := strconv.ParseUint(fields[2], 10, 32); err == nil {
131
+				file, line = fields[1], int(l)
132
+			}
133
+		}
134
+	}
135
+
136
+	return assembly, nil
137
+}
138
+
139
+// nextSymbol parses the nm output to find the next symbol listed.
140
+// Skips over any output it cannot recognize.
141
+func nextSymbol(buf *bytes.Buffer) (uint64, string, error) {
142
+	for {
143
+		line, err := buf.ReadString('\n')
144
+		if err != nil {
145
+			if err != io.EOF || line == "" {
146
+				return 0, "", err
147
+			}
148
+		}
149
+
150
+		if fields := nmOutputRE.FindStringSubmatch(line); len(fields) == 4 {
151
+			if address, err := strconv.ParseUint(fields[1], 16, 64); err == nil {
152
+				return address, fields[3], nil
153
+			}
154
+		}
155
+	}
156
+}

+ 133
- 0
src/internal/binutils/disasm_test.go Datei anzeigen

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
+
15
+package binutils
16
+
17
+import (
18
+	"fmt"
19
+	"regexp"
20
+	"testing"
21
+
22
+	"internal/plugin"
23
+)
24
+
25
+// TestFindSymbols tests the FindSymbols routine by using a fake nm
26
+// script.
27
+func TestFindSymbols(t *testing.T) {
28
+	type testcase struct {
29
+		query string
30
+		want  []plugin.Sym
31
+	}
32
+
33
+	testcases := []testcase{
34
+		{
35
+			"line.*[AC]",
36
+			[]plugin.Sym{
37
+				{[]string{"lineA001"}, "object.o", 0x1000, 0x1FFF},
38
+				{[]string{"line200A"}, "object.o", 0x2000, 0x2FFF},
39
+				{[]string{"lineB00C"}, "object.o", 0x3000, 0x3FFF},
40
+			},
41
+		},
42
+		{
43
+			"Dumb::operator",
44
+			[]plugin.Sym{
45
+				{[]string{"Dumb::operator()(char const*) const"}, "object.o", 0x3000, 0x3FFF},
46
+			},
47
+		},
48
+	}
49
+
50
+	const nm = "testdata/wrapper/nm"
51
+	for _, tc := range testcases {
52
+		syms, err := findSymbols(nm, "object.o", regexp.MustCompile(tc.query), 0)
53
+		if err != nil {
54
+			t.Fatalf("%q: findSymbols: %v", tc.query, err)
55
+		}
56
+		if err := checkSymbol(syms, tc.want); err != nil {
57
+			t.Errorf("%q: %v", tc.query, err)
58
+		}
59
+	}
60
+}
61
+
62
+func checkSymbol(got []*plugin.Sym, want []plugin.Sym) error {
63
+	if len(got) != len(want) {
64
+		return fmt.Errorf("unexpected number of symbols %d (want %d)\n", len(got), len(want))
65
+	}
66
+
67
+	for i, g := range got {
68
+		w := want[i]
69
+		if len(g.Name) != len(w.Name) {
70
+			return fmt.Errorf("names, got %d, want %d", len(g.Name), len(w.Name))
71
+		}
72
+		for n := range g.Name {
73
+			if g.Name[n] != w.Name[n] {
74
+				return fmt.Errorf("name %d, got %q, want %q", n, g.Name[n], w.Name[n])
75
+			}
76
+		}
77
+		if g.File != w.File {
78
+			return fmt.Errorf("filename, got %q, want %q", g.File, w.File)
79
+		}
80
+		if g.Start != w.Start {
81
+			return fmt.Errorf("start address, got %#x, want %#x", g.Start, w.Start)
82
+		}
83
+		if g.End != w.End {
84
+			return fmt.Errorf("end address, got %#x, want %#x", g.End, w.End)
85
+		}
86
+	}
87
+	return nil
88
+}
89
+
90
+// TestFunctionAssembly tests the FunctionAssembly routine by using a
91
+// fake objdump script.
92
+func TestFunctionAssembly(t *testing.T) {
93
+	type testcase struct {
94
+		s    plugin.Sym
95
+		want []plugin.Inst
96
+	}
97
+	testcases := []testcase{
98
+		{
99
+			plugin.Sym{[]string{"symbol1"}, "", 0x1000, 0x1FFF},
100
+			[]plugin.Inst{
101
+				{0x1000, "instruction one", "", 0},
102
+				{0x1001, "instruction two", "", 0},
103
+				{0x1002, "instruction three", "", 0},
104
+				{0x1003, "instruction four", "", 0},
105
+			},
106
+		},
107
+		{
108
+			plugin.Sym{[]string{"symbol2"}, "", 0x2000, 0x2FFF},
109
+			[]plugin.Inst{
110
+				{0x2000, "instruction one", "", 0},
111
+				{0x2001, "instruction two", "", 0},
112
+			},
113
+		},
114
+	}
115
+
116
+	const objdump = "testdata/wrapper/objdump"
117
+
118
+	for _, tc := range testcases {
119
+		insns, err := disassemble(objdump, "object.o", tc.s.Start, tc.s.End)
120
+		if err != nil {
121
+			t.Fatalf("FunctionAssembly: %v", err)
122
+		}
123
+
124
+		if len(insns) != len(tc.want) {
125
+			t.Errorf("Unexpected number of assembly instructions %d (want %d)\n", len(insns), len(tc.want))
126
+		}
127
+		for i := range insns {
128
+			if insns[i] != tc.want[i] {
129
+				t.Errorf("Expected symbol %v, got %v\n", tc.want[i], insns[i])
130
+			}
131
+		}
132
+	}
133
+}

+ 85
- 0
src/internal/binutils/testdata/wrapper/addr2line Datei anzeigen

1
+#!/bin/bash
2
+# Copyright 2014 Google Inc. All Rights Reserved.
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+#     http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS,
12
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+#
16
+# addr2line stub for testing of addr2liner.
17
+# Will recognize (and ignore) the -aiCfej options.
18
+#
19
+# Accepts addresses 1000 to 9000 and output multiple frames of the form:
20
+#   0x9000/fun9000/file9000:9000
21
+#   0x8000/fun8000/file8000:8000
22
+#   0x7000/fun7000/file7000:7000
23
+#   ...
24
+#   0x1000/fun1000/file1000:1000
25
+#
26
+# Returns ??/??/??:0 for all other inputs.
27
+
28
+while getopts aiCfe:j: opt; do
29
+  case "$opt" in
30
+    a|i|C|f|e|j) ;;
31
+    *)
32
+      echo "unrecognized option: $1" >&2
33
+      exit 1
34
+  esac
35
+done
36
+
37
+while read input
38
+do
39
+  address="$input"
40
+  
41
+  # remove 0x from input.
42
+  case "${address}" in
43
+    0x*)
44
+      address=$(printf '%x' "$address")
45
+      ;;
46
+    *)
47
+      address=$(printf '%x' "0x$address")
48
+  esac
49
+
50
+  printf '0x%x\n' "0x$address"
51
+  loop=1
52
+  while [ $loop -eq 1 ]
53
+  do
54
+    # prepare default output.
55
+    output2="fun${address}"
56
+    output3="file${address}:${address}"
57
+
58
+    # specialize output for selected cases.
59
+	  case "${address}" in
60
+      1000)
61
+        output2="_Z3fooid.clone2"
62
+        loop=0
63
+        ;;
64
+      2000)
65
+        output2="_ZNSaIiEC1Ev.clone18"
66
+        address=1000
67
+        ;;
68
+      3000)
69
+        output2="_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm"
70
+        address=2000
71
+        ;;
72
+      [4-9]000)
73
+        address=$(expr ${address} - 1000)
74
+        ;;
75
+      *)
76
+        output2='??'
77
+        output3='??:0'
78
+        loop=0
79
+    esac
80
+
81
+    echo "$output2"
82
+    echo "$output3"
83
+  done
84
+done
85
+exit 0

+ 62
- 0
src/internal/binutils/testdata/wrapper/nm Datei anzeigen

1
+#!/bin/bash
2
+# Copyright 2014 Google Inc. All Rights Reserved.
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+#     http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS,
12
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+#
16
+# nm stub for testing of listing.
17
+# Will recognize (and ignore) the -nC options.
18
+#
19
+# Outputs fixed nm output.
20
+
21
+while getopts nC opt; do
22
+  case "$opt" in
23
+    n) ;;
24
+    C) demangle=1;;
25
+    *)
26
+      echo "unrecognized option: $1" >&2
27
+      exit 1
28
+  esac
29
+done
30
+
31
+if [ $demangle ] 
32
+then
33
+  cat <<EOF
34
+0000000000001000 t lineA001
35
+0000000000001000 t lineA002
36
+0000000000001000 t line1000
37
+0000000000002000 t line200A
38
+0000000000002000 t line2000
39
+0000000000002000 t line200B
40
+0000000000003000 t line3000
41
+0000000000003000 t Dumb::operator()(char const*) const
42
+0000000000003000 t lineB00C
43
+0000000000003000 t line300D
44
+0000000000004000 t _the_end
45
+EOF
46
+  exit 0
47
+fi
48
+
49
+cat <<EOF
50
+0000000000001000 t lineA001
51
+0000000000001000 t lineA002
52
+0000000000001000 t line1000
53
+0000000000002000 t line200A
54
+0000000000002000 t line2000
55
+0000000000002000 t line200B
56
+0000000000003000 t line3000
57
+0000000000003000 t _ZNK4DumbclEPKc
58
+0000000000003000 t lineB00C
59
+0000000000003000 t line300D
60
+0000000000004000 t _the_end
61
+EOF
62
+exit 0

+ 59
- 0
src/internal/binutils/testdata/wrapper/objdump Datei anzeigen

1
+#!/bin/bash
2
+# Copyright 2014 Google Inc. All Rights Reserved.
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+#     http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS,
12
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+#
16
+# objdump stub for testing of listing.
17
+# Will recognize (and ignore) the -nC options.
18
+#
19
+# Outputs fixed nm output.
20
+
21
+START=0
22
+STOP=0
23
+while [ $# -gt 1 ] ; do 
24
+ case "$1" in 
25
+   --start-address=*) START=$(echo $1 | sed 's/.*=//') ;;
26
+   --stop-address=*) STOP=$(echo $1 | sed 's/.*=//') ;; 
27
+   --no-show-raw-insn|-d|-C|-n|-l) ;;
28
+   *) echo "Unrecognized option $1"
29
+     exit 1
30
+ esac
31
+ shift
32
+done
33
+
34
+case "$START$STOP" in 
35
+  "0x10000x1fff")
36
+  cat <<EOF
37
+  1000: instruction one
38
+  1001: instruction two
39
+  1002: instruction three
40
+  1003: instruction four
41
+EOF
42
+  ;;
43
+  "0x20000x2fff")
44
+  cat <<EOF
45
+  2000: instruction one
46
+  2001: instruction two
47
+EOF
48
+  ;;
49
+  "0x30000x3fff")
50
+  cat <<EOF
51
+  3000: instruction one
52
+  3001: instruction two
53
+  3002: instruction three
54
+  3003: instruction four
55
+  3004: instruction five
56
+EOF
57
+  ;;
58
+esac
59
+exit 0

+ 271
- 0
src/internal/driver/cli.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"fmt"
19
+	"os"
20
+	"strings"
21
+
22
+	"internal/binutils"
23
+	"internal/plugin"
24
+)
25
+
26
+type source struct {
27
+	Sources  []string
28
+	ExecName string
29
+	BuildID  string
30
+	Base     []string
31
+
32
+	Seconds   int
33
+	Timeout   int
34
+	Symbolize string
35
+}
36
+
37
+// Parse parses the command lines through the specified flags package
38
+// and returns the source of the profile and optionally the command
39
+// for the kind of report to generate (nil for interactive use).
40
+func parseFlags(o *plugin.Options) (*source, []string, error) {
41
+	flag := o.Flagset
42
+	// Comparisons.
43
+	flagBase := flag.StringList("base", "", "Source for base profile for comparison")
44
+	// Internal options.
45
+	flagSymbolize := flag.String("symbolize", "", "Options for profile symbolization")
46
+	flagBuildID := flag.String("buildid", "", "Override build id for first mapping")
47
+	// CPU profile options
48
+	flagSeconds := flag.Int("seconds", -1, "Length of time for dynamic profiles")
49
+	// Heap profile options
50
+	flagInUseSpace := flag.Bool("inuse_space", false, "Display in-use memory size")
51
+	flagInUseObjects := flag.Bool("inuse_objects", false, "Display in-use object counts")
52
+	flagAllocSpace := flag.Bool("alloc_space", false, "Display allocated memory size")
53
+	flagAllocObjects := flag.Bool("alloc_objects", false, "Display allocated object counts")
54
+	// Contention profile options
55
+	flagTotalDelay := flag.Bool("total_delay", false, "Display total delay at each region")
56
+	flagContentions := flag.Bool("contentions", false, "Display number of delays at each region")
57
+	flagMeanDelay := flag.Bool("mean_delay", false, "Display mean delay at each region")
58
+	flagTools := flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames")
59
+
60
+	flagTimeout := flag.Int("timeout", -1, "Timeout in seconds for fetching a profile")
61
+
62
+	// Flags used during command processing
63
+	installedFlags := installFlags(flag)
64
+
65
+	flagCommands := make(map[string]*bool)
66
+	flagParamCommands := make(map[string]*string)
67
+	for name, cmd := range pprofCommands {
68
+		if cmd.hasParam {
69
+			flagParamCommands[name] = flag.String(name, "", "Generate a report in "+name+" format, matching regexp")
70
+		} else {
71
+			flagCommands[name] = flag.Bool(name, false, "Generate a report in "+name+" format")
72
+		}
73
+	}
74
+
75
+	args := flag.Parse(func() {
76
+		o.UI.Print(usageMsgHdr +
77
+			usage(true) +
78
+			usageMsgSrc +
79
+			flag.ExtraUsage() +
80
+			usageMsgVars)
81
+	})
82
+	if len(args) == 0 {
83
+		return nil, nil, fmt.Errorf("no profile source specified")
84
+	}
85
+
86
+	var execName string
87
+	// Recognize first argument as an executable or buildid override.
88
+	if len(args) > 1 {
89
+		arg0 := args[0]
90
+		if file, err := o.Obj.Open(arg0, 0, ^uint64(0), 0); err == nil {
91
+			file.Close()
92
+			execName = arg0
93
+			args = args[1:]
94
+		} else if *flagBuildID == "" && isBuildID(arg0) {
95
+			*flagBuildID = arg0
96
+			args = args[1:]
97
+		}
98
+	}
99
+
100
+	// Report conflicting options
101
+	if err := updateFlags(installedFlags); err != nil {
102
+		return nil, nil, err
103
+	}
104
+
105
+	cmd, err := outputFormat(flagCommands, flagParamCommands)
106
+	if err != nil {
107
+		return nil, nil, err
108
+	}
109
+
110
+	si := pprofVariables["sample_index"].value
111
+	si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI)
112
+	si = sampleIndex(flagMeanDelay, si, "delay", "-mean_delay", o.UI)
113
+	si = sampleIndex(flagContentions, si, "contentions", "-contentions", o.UI)
114
+	si = sampleIndex(flagInUseSpace, si, "inuse_space", "-inuse_space", o.UI)
115
+	si = sampleIndex(flagInUseObjects, si, "inuse_objects", "-inuse_objects", o.UI)
116
+	si = sampleIndex(flagAllocSpace, si, "alloc_space", "-alloc_space", o.UI)
117
+	si = sampleIndex(flagAllocObjects, si, "alloc_objects", "-alloc_objects", o.UI)
118
+	pprofVariables.set("sample_index", si)
119
+
120
+	if *flagMeanDelay {
121
+		pprofVariables.set("mean", "true")
122
+	}
123
+
124
+	source := &source{
125
+		Sources:   args,
126
+		ExecName:  execName,
127
+		BuildID:   *flagBuildID,
128
+		Seconds:   *flagSeconds,
129
+		Timeout:   *flagTimeout,
130
+		Symbolize: *flagSymbolize,
131
+	}
132
+
133
+	for _, s := range *flagBase {
134
+		if *s != "" {
135
+			source.Base = append(source.Base, *s)
136
+		}
137
+	}
138
+
139
+	if bu, ok := o.Obj.(*binutils.Binutils); ok {
140
+		bu.SetTools(*flagTools)
141
+	}
142
+	return source, cmd, nil
143
+}
144
+
145
+// installFlags creates command line flags for pprof variables.
146
+func installFlags(flag plugin.FlagSet) flagsInstalled {
147
+	f := flagsInstalled{
148
+		ints:    make(map[string]*int),
149
+		bools:   make(map[string]*bool),
150
+		floats:  make(map[string]*float64),
151
+		strings: make(map[string]*string),
152
+	}
153
+	for n, v := range pprofVariables {
154
+		switch v.kind {
155
+		case boolKind:
156
+			if v.group != "" {
157
+				// Set all radio variables to false to identify conflicts.
158
+				f.bools[n] = flag.Bool(n, false, v.help)
159
+			} else {
160
+				f.bools[n] = flag.Bool(n, v.boolValue(), v.help)
161
+			}
162
+		case intKind:
163
+			f.ints[n] = flag.Int(n, v.intValue(), v.help)
164
+		case floatKind:
165
+			f.floats[n] = flag.Float64(n, v.floatValue(), v.help)
166
+		case stringKind:
167
+			f.strings[n] = flag.String(n, v.value, v.help)
168
+		}
169
+	}
170
+	return f
171
+}
172
+
173
+// updateFlags updates the pprof variables according to the flags
174
+// parsed in the command line.
175
+func updateFlags(f flagsInstalled) error {
176
+	vars := pprofVariables
177
+	groups := map[string]string{}
178
+	for n, v := range f.bools {
179
+		vars.set(n, fmt.Sprint(*v))
180
+		if *v {
181
+			g := vars[n].group
182
+			if g != "" && groups[g] != "" {
183
+				return fmt.Errorf("conflicting options %q and %q set", n, groups[g])
184
+			}
185
+			groups[g] = n
186
+		}
187
+	}
188
+	for n, v := range f.ints {
189
+		vars.set(n, fmt.Sprint(*v))
190
+	}
191
+	for n, v := range f.floats {
192
+		vars.set(n, fmt.Sprint(*v))
193
+	}
194
+	for n, v := range f.strings {
195
+		vars.set(n, *v)
196
+	}
197
+	return nil
198
+}
199
+
200
+type flagsInstalled struct {
201
+	ints    map[string]*int
202
+	bools   map[string]*bool
203
+	floats  map[string]*float64
204
+	strings map[string]*string
205
+}
206
+
207
+// isBuildID determines if the profile may contain a build ID, by
208
+// checking that it is a string of hex digits.
209
+func isBuildID(id string) bool {
210
+	return strings.Trim(id, "0123456789abcdefABCDEF") == ""
211
+}
212
+
213
+func sampleIndex(flag *bool, si string, sampleType, option string, ui plugin.UI) string {
214
+	if *flag {
215
+		if si == "" {
216
+			return sampleType
217
+		}
218
+		ui.PrintErr("Multiple value selections, ignoring ", option)
219
+	}
220
+	return si
221
+}
222
+
223
+func outputFormat(bcmd map[string]*bool, acmd map[string]*string) (cmd []string, err error) {
224
+	for n, b := range bcmd {
225
+		if *b {
226
+			if cmd != nil {
227
+				return nil, fmt.Errorf("must set at most one output format")
228
+			}
229
+			cmd = []string{n}
230
+		}
231
+	}
232
+	for n, s := range acmd {
233
+		if *s != "" {
234
+			if cmd != nil {
235
+				return nil, fmt.Errorf("must set at most one output format")
236
+			}
237
+			cmd = []string{n, *s}
238
+		}
239
+	}
240
+	return cmd, nil
241
+}
242
+
243
+var usageMsgHdr = "usage: pprof [options] [-base source] [binary] <source> ...\n"
244
+
245
+var usageMsgSrc = "\n\n" +
246
+	"  Source options:\n" +
247
+	"    -seconds              Duration for time-based profile collection\n" +
248
+	"    -timeout              Timeout in seconds for profile collection\n" +
249
+	"    -buildid              Override build id for main binary\n" +
250
+	"    -base source          Source of profile to use as baseline\n" +
251
+	"    profile.pb.gz         Profile in compressed protobuf format\n" +
252
+	"    legacy_profile        Profile in legacy pprof format\n" +
253
+	"    http://host/profile   URL for profile handler to retrieve\n" +
254
+	"    -symbolize=           Controls source of symbol information\n" +
255
+	"      none                  Do not attempt symbolization\n" +
256
+	"      local                 Examine only local binaries\n" +
257
+	"      fastlocal             Only get function names from local binaries\n" +
258
+	"      remote                Do not examine local binaries\n" +
259
+	"      force                 Force re-symbolization\n" +
260
+	"    Binary                  Local path or build id of binary for symbolization\n"
261
+
262
+var usageMsgVars = "\n\n" +
263
+	"  Misc options:\n" +
264
+	"   -tools                 Search path for object tools\n" +
265
+	"\n" +
266
+	"  Environment Variables:\n" +
267
+	"   PPROF_TMPDIR       Location for temporary files (default $HOME/pprof)\n" +
268
+	"   PPROF_TOOLS        Search path for object-level tools\n" +
269
+	"   PPROF_BINARY_PATH  Search path for local binary files\n" +
270
+	"                      default: $HOME/pprof/binaries\n" +
271
+	"                      finds binaries by $name and $buildid/$name\n"

+ 580
- 0
src/internal/driver/commands.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"bytes"
19
+	"fmt"
20
+	"io"
21
+	"os"
22
+	"os/exec"
23
+	"runtime"
24
+	"sort"
25
+	"strconv"
26
+	"strings"
27
+	"time"
28
+
29
+	"internal/plugin"
30
+	"internal/report"
31
+	"profile"
32
+	"svg"
33
+)
34
+
35
+// commands describes the commands accepted by pprof.
36
+type commands map[string]*command
37
+
38
+// command describes the actions for a pprof command. Includes a
39
+// function for command-line completion, the report format to use
40
+// during report generation, any postprocessing functions, and whether
41
+// the command expects a regexp parameter (typically a function name).
42
+type command struct {
43
+	format      int           // report format to generate
44
+	postProcess PostProcessor // postprocessing to run on report
45
+	hasParam    bool          // collect a parameter from the CLI
46
+	description string        // single-line description text saying what the command does
47
+	usage       string        // multi-line help text saying how the command is used
48
+}
49
+
50
+// help returns a help string for a command.
51
+func (c *command) help(name string) string {
52
+	message := c.description + "\n"
53
+	if c.usage != "" {
54
+		message += "  Usage:\n"
55
+		lines := strings.Split(c.usage, "\n")
56
+		for _, line := range lines {
57
+			message += fmt.Sprintf("    %s\n", line)
58
+		}
59
+	}
60
+	return message + "\n"
61
+}
62
+
63
+func AddCommand(cmd string, format int, post PostProcessor, desc, usage string) {
64
+	pprofCommands[cmd] = &command{format, post, false, desc, usage}
65
+}
66
+
67
+// PostProcessor is a function that applies post-processing to the report output
68
+type PostProcessor func(input []byte, output io.Writer, ui plugin.UI) error
69
+
70
+// WaitForVisualizer makes pprof wait for visualizers to complete
71
+// before continuing, returning any errors.
72
+var waitForVisualizer = true
73
+
74
+// pprofCommands are the report generation commands recognized by pprof.
75
+var pprofCommands = commands{
76
+	// Commands that require no post-processing.
77
+	"tags":     {report.Tags, nil, false, "Outputs all tags in the profile", "tags [tag_regex]* [-ignore_regex]* [>file]\nList tags with key:value matching tag_regex and exclude ignore_regex."},
78
+	"raw":      {report.Raw, nil, false, "Outputs a text representation of the raw profile", ""},
79
+	"dot":      {report.Dot, nil, false, "Outputs a graph in DOT format", reportHelp("dot", false, true)},
80
+	"top":      {report.Text, nil, false, "Outputs top entries in text form", reportHelp("top", true, true)},
81
+	"tree":     {report.Tree, nil, false, "Outputs a text rendering of call graph", reportHelp("tree", true, true)},
82
+	"text":     {report.Text, nil, false, "Outputs top entries in text form", reportHelp("text", true, true)},
83
+	"traces":   {report.Traces, nil, false, "Outputs all profile samples in text form", ""},
84
+	"topproto": {report.TopProto, awayFromTTY("pb.gz"), false, "Outputs top entries in compressed protobuf format", ""},
85
+	"disasm":   {report.Dis, nil, true, "Output assembly listings annotated with samples", listHelp("disasm", true)},
86
+	"list":     {report.List, nil, true, "Output annotated source for functions matching regexp", listHelp("list", false)},
87
+	"peek":     {report.Tree, nil, true, "Output callers/callees of functions matching regexp", "peek func_regex\nDisplay callers and callees of functions matching func_regex."},
88
+
89
+	// Save binary formats to a file
90
+	"callgrind": {report.Callgrind, awayFromTTY("callgraph.out"), false, "Outputs a graph in callgrind format", reportHelp("callgrind", false, true)},
91
+	"proto":     {report.Proto, awayFromTTY("pb.gz"), false, "Outputs the profile in compressed protobuf format", ""},
92
+
93
+	// Generate report in DOT format and postprocess with dot
94
+	"gif": {report.Dot, invokeDot("gif"), false, "Outputs a graph image in GIF format", reportHelp("gif", false, true)},
95
+	"pdf": {report.Dot, invokeDot("pdf"), false, "Outputs a graph in PDF format", reportHelp("pdf", false, true)},
96
+	"png": {report.Dot, invokeDot("png"), false, "Outputs a graph image in PNG format", reportHelp("png", false, true)},
97
+	"ps":  {report.Dot, invokeDot("ps"), false, "Outputs a graph in PS format", reportHelp("ps", false, true)},
98
+
99
+	// Save SVG output into a file
100
+	"svg": {report.Dot, saveSVGToFile(), false, "Outputs a graph in SVG format", reportHelp("svg", false, true)},
101
+
102
+	// Visualize postprocessed dot output
103
+	"eog":    {report.Dot, invokeVisualizer(invokeDot("svg"), "svg", []string{"eog"}), false, "Visualize graph through eog", reportHelp("eog", false, false)},
104
+	"evince": {report.Dot, invokeVisualizer(invokeDot("pdf"), "pdf", []string{"evince"}), false, "Visualize graph through evince", reportHelp("evince", false, false)},
105
+	"gv":     {report.Dot, invokeVisualizer(invokeDot("ps"), "ps", []string{"gv --noantialias"}), false, "Visualize graph through gv", reportHelp("gv", false, false)},
106
+	"web":    {report.Dot, invokeVisualizer(saveSVGToFile(), "svg", browsers()), false, "Visualize graph through web browser", reportHelp("web", false, false)},
107
+
108
+	// Visualize callgrind output
109
+	"kcachegrind": {report.Callgrind, invokeVisualizer(nil, "grind", kcachegrind), false, "Visualize report in KCachegrind", reportHelp("kcachegrind", false, false)},
110
+
111
+	// Visualize HTML directly generated by report.
112
+	"weblist": {report.WebList, invokeVisualizer(awayFromTTY("html"), "html", browsers()), true, "Display annotated source in a web browser", listHelp("weblist", false)},
113
+}
114
+
115
+// pprofVariables are the configuration parameters that affect the
116
+// reported generated by pprof.
117
+var pprofVariables = variables{
118
+	// Filename for file-based output formats, stdout by default.
119
+	"output": &variable{stringKind, "", "", helpText("Output filename for file-based outputs")},
120
+
121
+	// Comparisons.
122
+	"drop_negative": &variable{boolKind, "f", "", helpText(
123
+		"Ignore negative differences",
124
+		"Do not show any locations with values <0.")},
125
+
126
+	// Comparisons.
127
+	"positive_percentages": &variable{boolKind, "f", "", helpText(
128
+		"Ignore negative samples when computing percentages",
129
+		" Do not count negative samples when computing the total value",
130
+		" of the profile, used to compute percentages. If set, and the -base",
131
+		" option is used, percentages reported will be computed against the",
132
+		" main profile, ignoring the base profile.")},
133
+
134
+	// Graph handling options.
135
+	"call_tree": &variable{boolKind, "f", "", helpText(
136
+		"Create a context-sensitive call tree",
137
+		"Treat locations reached through different paths as separate.")},
138
+
139
+	// Display options.
140
+	"relative_percentages": &variable{boolKind, "f", "", helpText(
141
+		"Show percentages relative to focused subgraph",
142
+		"If unset, percentages are relative to full graph before focusing",
143
+		"to facilitate comparison with original graph.")},
144
+	"unit": &variable{stringKind, "minimum", "", helpText(
145
+		"Measurement units to display",
146
+		"Scale the sample values to this unit.",
147
+		" For time-based profiles, use seconds, milliseconds, nanoseconds, etc.",
148
+		" For memory profiles, use megabytes, kiloyes, bytes, etc.",
149
+		" auto will scale each value independently to the most natural unit.")},
150
+	"compact_labels": &variable{boolKind, "f", "", "Show minimal headers"},
151
+
152
+	// Filtering options
153
+	"nodecount": &variable{intKind, "-1", "", helpText(
154
+		"Max number of nodes to show",
155
+		"Uses heuristics to limit the number of locations to be displayed.",
156
+		"On graphs, dotted edges represent paths through nodes that have been removed.")},
157
+	"nodefraction": &variable{floatKind, "0.005", "", "Hide nodes below <f>*total"},
158
+	"edgefraction": &variable{floatKind, "0.001", "", "Hide edges below <f>*total"},
159
+	"trim": &variable{boolKind, "t", "", helpText(
160
+		"Honor nodefraction/edgefraction/nodecount defaults",
161
+		"Set to false to get the full profile, without any trimming.")},
162
+	"focus": &variable{stringKind, "", "", helpText(
163
+		"Restricts to samples going through a node matching regexp",
164
+		"Discard samples that do not include a node matching this regexp.",
165
+		"Matching includes the function name, filename or object name.")},
166
+	"ignore": &variable{stringKind, "", "", helpText(
167
+		"Skips paths going through any nodes matching regexp",
168
+		"If set, discard samples that include a node matching this regexp.",
169
+		"Matching includes the function name, filename or object name.")},
170
+	"prune_from": &variable{stringKind, "", "", helpText(
171
+		"Drops any functions below the matched frame.",
172
+		"If set, any frames matching the specified regexp and any frames",
173
+		"below it will be dropped from each sample.")},
174
+	"hide": &variable{stringKind, "", "", helpText(
175
+		"Skips nodes matching regexp",
176
+		"Discard nodes that match this location.",
177
+		"Other nodes from samples that include this location will be shown.",
178
+		"Matching includes the function name, filename or object name.")},
179
+	"show": &variable{stringKind, "", "", helpText(
180
+		"Only show nodes matching regexp",
181
+		"If set, only show nodes that match this location.",
182
+		"Matching includes the function name, filename or object name.")},
183
+	"tagfocus": &variable{stringKind, "", "", helpText(
184
+		"Restrict to samples with tags in range or matched by regexp",
185
+		"Discard samples that do not include a node with a tag matching this regexp.")},
186
+	"tagignore": &variable{stringKind, "", "", helpText(
187
+		"Discard samples with tags in range or matched by regexp",
188
+		"Discard samples that do include a node with a tag matching this regexp.")},
189
+
190
+	// Heap profile options
191
+	"divide_by": &variable{floatKind, "1", "", helpText(
192
+		"Ratio to divide all samples before visualization",
193
+		"Divide all samples values by a constant, eg the number of processors or jobs.")},
194
+	"mean": &variable{boolKind, "f", "", helpText(
195
+		"Average sample value over first value (count)",
196
+		"For memory profiles, report average memory per allocation.",
197
+		"For time-based profiles, report average time per event.")},
198
+	"sample_index": &variable{stringKind, "", "", helpText(
199
+		"Sample value to report",
200
+		"Profiles contain multiple values per sample.",
201
+		"Use sample_value=index to select the ith value or select it by name.")},
202
+
203
+	// Data sorting criteria
204
+	"flat": &variable{boolKind, "t", "cumulative", helpText("Sort entries based on own weight")},
205
+	"cum":  &variable{boolKind, "f", "cumulative", helpText("Sort entries based on cumulative weight")},
206
+
207
+	// Output granularity
208
+	"functions": &variable{boolKind, "t", "granularity", helpText(
209
+		"Aggregate at the function level.",
210
+		"Takes into account the filename/lineno where the function was defined.")},
211
+	"functionnameonly": &variable{boolKind, "f", "granularity", helpText(
212
+		"Aggregate at the function level.",
213
+		"Ignores the filename/lineno where the function was defined.")},
214
+	"files": &variable{boolKind, "f", "granularity", "Aggregate at the file level."},
215
+	"lines": &variable{boolKind, "f", "granularity", "Aggregate at the source code line level."},
216
+	"addresses": &variable{boolKind, "f", "granularity", helpText(
217
+		"Aggregate at the function level.",
218
+		"Includes functions' addresses in the output.")},
219
+	"noinlines": &variable{boolKind, "f", "granularity", helpText(
220
+		"Aggregate at the function level.",
221
+		"Attributes inlined functions to their first out-of-line caller.")},
222
+	"addressnoinlines": &variable{boolKind, "f", "granularity", helpText(
223
+		"Aggregate at the function level, including functions' addresses in the output.",
224
+		"Attributes inlined functions to their first out-of-line caller.")},
225
+}
226
+
227
+func helpText(s ...string) string {
228
+	return strings.Join(s, "\n") + "\n"
229
+}
230
+
231
+// usage returns a string describing the pprof commands and variables.
232
+// if commandLine is set, the output reflect cli usage.
233
+func usage(commandLine bool) string {
234
+	var prefix string
235
+	if commandLine {
236
+		prefix = "-"
237
+	}
238
+	fmtHelp := func(c, d string) string {
239
+		return fmt.Sprintf("    %-16s %s", c, strings.SplitN(d, "\n", 2)[0])
240
+	}
241
+
242
+	var commands []string
243
+	for name, cmd := range pprofCommands {
244
+		commands = append(commands, fmtHelp(prefix+name, cmd.description))
245
+	}
246
+	sort.Strings(commands)
247
+
248
+	var help string
249
+	if commandLine {
250
+		help = "  Output formats (select only one):\n"
251
+	} else {
252
+		help = "  Commands:\n"
253
+		commands = append(commands, fmtHelp("quit/exit/^D", "Exit pprof"))
254
+	}
255
+
256
+	help = help + strings.Join(commands, "\n") + "\n\n" +
257
+		"  Options:\n"
258
+
259
+	// Print help for variables after sorting them.
260
+	// Collect radio variables by their group name to print them together.
261
+	radioOptions := make(map[string][]string)
262
+	var variables []string
263
+	for name, vr := range pprofVariables {
264
+		if vr.group != "" {
265
+			radioOptions[vr.group] = append(radioOptions[vr.group], name)
266
+			continue
267
+		}
268
+		variables = append(variables, fmtHelp(prefix+name, vr.help))
269
+	}
270
+	sort.Strings(variables)
271
+
272
+	help = help + strings.Join(variables, "\n") + "\n\n" +
273
+		"  Option groups (only set one per group):\n"
274
+
275
+	var radioStrings []string
276
+	for radio, ops := range radioOptions {
277
+		sort.Strings(ops)
278
+		s := []string{fmtHelp(radio, "")}
279
+		for _, op := range ops {
280
+			s = append(s, "  "+fmtHelp(prefix+op, pprofVariables[op].help))
281
+		}
282
+
283
+		radioStrings = append(radioStrings, strings.Join(s, "\n"))
284
+	}
285
+	sort.Strings(radioStrings)
286
+	return help + strings.Join(radioStrings, "\n")
287
+}
288
+
289
+func reportHelp(c string, cum, redirect bool) string {
290
+	h := []string{
291
+		c + " [n] [focus_regex]* [-ignore_regex]*",
292
+		"Include up to n samples",
293
+		"Include samples matching focus_regex, and exclude ignore_regex.",
294
+	}
295
+	if cum {
296
+		h[0] += " [-cum]"
297
+		h = append(h, "-cum sorts the output by cumulative weight")
298
+	}
299
+	if redirect {
300
+		h[0] += " >f"
301
+		h = append(h, "Optionally save the report on the file f")
302
+	}
303
+	return strings.Join(h, "\n")
304
+}
305
+
306
+func listHelp(c string, redirect bool) string {
307
+	h := []string{
308
+		c + "<func_regex|address> [-focus_regex]* [-ignore_regex]*",
309
+		"Include functions matching func_regex, or including the address specified.",
310
+		"Include samples matching focus_regex, and exclude ignore_regex.",
311
+	}
312
+	if redirect {
313
+		h[0] += " >f"
314
+		h = append(h, "Optionally save the report on the file f")
315
+	}
316
+	return strings.Join(h, "\n")
317
+}
318
+
319
+// browsers returns a list of commands to attempt for web visualization.
320
+func browsers() []string {
321
+	cmds := []string{"chrome", "google-chrome", "firefox"}
322
+	switch runtime.GOOS {
323
+	case "darwin":
324
+		return append(cmds, "/usr/bin/open")
325
+	case "windows":
326
+		return append(cmds, "cmd /c start")
327
+	default:
328
+		user_browser := os.Getenv("BROWSER")
329
+		if user_browser != "" {
330
+			cmds = append([]string{user_browser, "sensible-browser"}, cmds...)
331
+		} else {
332
+			cmds = append([]string{"sensible-browser"}, cmds...)
333
+		}
334
+		return append(cmds, "xdg-open")
335
+	}
336
+}
337
+
338
+var kcachegrind = []string{"kcachegrind"}
339
+
340
+// awayFromTTY saves the output in a file if it would otherwise go to
341
+// the terminal screen. This is used to avoid dumping binary data on
342
+// the screen.
343
+func awayFromTTY(format string) PostProcessor {
344
+	return func(input []byte, output io.Writer, ui plugin.UI) error {
345
+		if output == os.Stdout && ui.IsTerminal() {
346
+			tempFile, err := newTempFile("", "profile", "."+format)
347
+			if err != nil {
348
+				return err
349
+			}
350
+			ui.PrintErr("Generating report in ", tempFile.Name())
351
+			_, err = fmt.Fprint(tempFile, string(input))
352
+			return err
353
+		}
354
+		_, err := fmt.Fprint(output, string(input))
355
+		return err
356
+	}
357
+}
358
+
359
+func invokeDot(format string) PostProcessor {
360
+	divert := awayFromTTY(format)
361
+	return func(input []byte, output io.Writer, ui plugin.UI) error {
362
+		cmd := exec.Command("dot", "-T"+format)
363
+		var buf bytes.Buffer
364
+		cmd.Stdin, cmd.Stdout, cmd.Stderr = bytes.NewBuffer(input), &buf, os.Stderr
365
+		if err := cmd.Run(); err != nil {
366
+			return fmt.Errorf("Failed to execute dot. Is Graphviz installed? Error: %v", err)
367
+		}
368
+		return divert(buf.Bytes(), output, ui)
369
+	}
370
+}
371
+
372
+func saveSVGToFile() PostProcessor {
373
+	generateSVG := invokeDot("svg")
374
+	divert := awayFromTTY("svg")
375
+	return func(input []byte, output io.Writer, ui plugin.UI) error {
376
+		baseSVG := &bytes.Buffer{}
377
+		if err := generateSVG(input, baseSVG, ui); err != nil {
378
+			return err
379
+		}
380
+
381
+		return divert([]byte(svg.Massage(*baseSVG)), output, ui)
382
+	}
383
+}
384
+
385
+func invokeVisualizer(format PostProcessor, suffix string, visualizers []string) PostProcessor {
386
+	return func(input []byte, output io.Writer, ui plugin.UI) error {
387
+		if output != os.Stdout {
388
+			if format != nil {
389
+				return format(input, output, ui)
390
+			}
391
+			_, err := fmt.Fprint(output, string(input))
392
+			return err
393
+		}
394
+
395
+		tempFile, err := newTempFile(os.Getenv("PPROF_TMPDIR"), "pprof", "."+suffix)
396
+		if err != nil {
397
+			return err
398
+		}
399
+		deferDeleteTempFile(tempFile.Name())
400
+		if format != nil {
401
+			if err := format(input, tempFile, ui); err != nil {
402
+				return err
403
+			}
404
+		} else {
405
+			if _, err := fmt.Fprint(tempFile, string(input)); err != nil {
406
+				return err
407
+			}
408
+		}
409
+		tempFile.Close()
410
+		// Try visualizers until one is successful
411
+		for _, v := range visualizers {
412
+			// Separate command and arguments for exec.Command.
413
+			args := strings.Split(v, " ")
414
+			if len(args) == 0 {
415
+				continue
416
+			}
417
+			viewer := exec.Command(args[0], append(args[1:], tempFile.Name())...)
418
+			viewer.Stderr = os.Stderr
419
+			if err = viewer.Start(); err == nil {
420
+				// Wait for a second so that the visualizer has a chance to
421
+				// open the input file. This needs to be done even if we're
422
+				// waiting for the visualizer as it can be just a wrapper that
423
+				// spawns a browser tab and returns right away.
424
+				defer func(t <-chan time.Time) {
425
+					<-t
426
+				}(time.After(time.Second))
427
+				if waitForVisualizer {
428
+					return viewer.Wait()
429
+				}
430
+				return nil
431
+			}
432
+		}
433
+		return err
434
+	}
435
+}
436
+
437
+// locateSampleIndex returns the appropriate index for a value of sample index.
438
+// If numeric, it returns the number, otherwise it looks up the text in the
439
+// profile sample types.
440
+func locateSampleIndex(p *profile.Profile, sampleIndex string) (int, error) {
441
+	if sampleIndex == "" {
442
+		// By default select the last sample value
443
+		return len(p.SampleType) - 1, nil
444
+	}
445
+	if i, err := strconv.Atoi(sampleIndex); err == nil {
446
+		if i < 0 || i >= len(p.SampleType) {
447
+			return 0, fmt.Errorf("sample_index %s is outside the range [0..%d]", sampleIndex, len(p.SampleType)-1)
448
+		}
449
+		return i, nil
450
+	}
451
+
452
+	// Remove the inuse_ prefix to support legacy pprof options
453
+	// "inuse_space" and "inuse_objects" for profiles containing types
454
+	// "space" and "objects".
455
+	noInuse := strings.TrimPrefix(sampleIndex, "inuse_")
456
+	sampleTypes := make([]string, len(p.SampleType))
457
+	for i, t := range p.SampleType {
458
+		if t.Type == sampleIndex || t.Type == noInuse {
459
+			return i, nil
460
+		}
461
+		sampleTypes[i] = t.Type
462
+	}
463
+
464
+	return 0, fmt.Errorf("sample_index %q must be one of: %v", sampleIndex, sampleTypes)
465
+}
466
+
467
+// variables describe the configuration parameters recognized by pprof.
468
+type variables map[string]*variable
469
+
470
+// variable is a single configuration parameter.
471
+type variable struct {
472
+	kind  int    // How to interpret the value, must be one of the enums below.
473
+	value string // Effective value. Only values appropriate for the Kind should be set.
474
+	group string // boolKind variables with the same Group != "" cannot be set simultaneously.
475
+	help  string // Text describing the variable, in multiple lines separated by newline.
476
+}
477
+
478
+const (
479
+	// variable.kind must be one of these variables.
480
+	boolKind = iota
481
+	intKind
482
+	floatKind
483
+	stringKind
484
+)
485
+
486
+// set updates the value of a variable, checking that the value is
487
+// suitable for the variable Kind.
488
+func (vars variables) set(name, value string) error {
489
+	v := vars[name]
490
+	if v == nil {
491
+		return fmt.Errorf("no variable %s", name)
492
+	}
493
+	var err error
494
+	switch v.kind {
495
+	case boolKind:
496
+		var b bool
497
+		if b, err = stringToBool(value); err == nil {
498
+			if v.group != "" && b == false {
499
+				err = fmt.Errorf("%q can only be set to true", name)
500
+			}
501
+		}
502
+	case intKind:
503
+		_, err = strconv.Atoi(value)
504
+	case floatKind:
505
+		_, err = strconv.ParseFloat(value, 64)
506
+	}
507
+	if err != nil {
508
+		return err
509
+	}
510
+	vars[name].value = value
511
+	if group := vars[name].group; group != "" {
512
+		for vname, vvar := range vars {
513
+			if vvar.group == group && vname != name {
514
+				vvar.value = "f"
515
+			}
516
+		}
517
+	}
518
+	return err
519
+}
520
+
521
+// boolValue returns the value of a boolean variable.
522
+func (v *variable) boolValue() bool {
523
+	b, err := stringToBool(v.value)
524
+	if err != nil {
525
+		panic("unexpected value " + v.value + " for bool ")
526
+	}
527
+	return b
528
+}
529
+
530
+// intValue returns the value of an intKind variable.
531
+func (v *variable) intValue() int {
532
+	i, err := strconv.Atoi(v.value)
533
+	if err != nil {
534
+		panic("unexpected value " + v.value + " for int ")
535
+	}
536
+	return i
537
+}
538
+
539
+// floatValue returns the value of a Float variable.
540
+func (v *variable) floatValue() float64 {
541
+	f, err := strconv.ParseFloat(v.value, 64)
542
+	if err != nil {
543
+		panic("unexpected value " + v.value + " for float ")
544
+	}
545
+	return f
546
+}
547
+
548
+// stringValue returns a canonical representation for a variable.
549
+func (v *variable) stringValue() string {
550
+	switch v.kind {
551
+	case boolKind:
552
+		return fmt.Sprint(v.boolValue())
553
+	case intKind:
554
+		return fmt.Sprint(v.intValue())
555
+	case floatKind:
556
+		return fmt.Sprint(v.floatValue())
557
+	}
558
+	return v.value
559
+}
560
+
561
+func stringToBool(s string) (bool, error) {
562
+	switch strings.ToLower(s) {
563
+	case "true", "t", "yes", "y", "1", "":
564
+		return true, nil
565
+	case "false", "f", "no", "n", "0":
566
+		return false, nil
567
+	default:
568
+		return false, fmt.Errorf(`illegal value "%s" for bool variable`, s)
569
+	}
570
+}
571
+
572
+// makeCopy returns a duplicate of a set of shell variables.
573
+func (vars variables) makeCopy() variables {
574
+	varscopy := make(variables, len(vars))
575
+	for n, v := range vars {
576
+		vcopy := *v
577
+		varscopy[n] = &vcopy
578
+	}
579
+	return varscopy
580
+}

+ 276
- 0
src/internal/driver/driver.go Datei anzeigen

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
+
15
+// Package driver implements the core pprof functionality. It can be
16
+// parameterized with a flag implementation, fetch and symbolize
17
+// mechanisms.
18
+package driver
19
+
20
+import (
21
+	"bytes"
22
+	"fmt"
23
+	"io"
24
+	"os"
25
+	"path/filepath"
26
+	"regexp"
27
+
28
+	"internal/plugin"
29
+	"profile"
30
+	"internal/report"
31
+)
32
+
33
+// PProf acquires a profile, and symbolizes it using a profile
34
+// manager. Then it generates a report formatted according to the
35
+// options selected through the flags package.
36
+func PProf(eo *plugin.Options) error {
37
+	// Remove any temporary files created during pprof processing.
38
+	defer cleanupTempFiles()
39
+
40
+	o := setDefaults(eo)
41
+
42
+	src, cmd, err := parseFlags(o)
43
+	if err != nil {
44
+		return err
45
+	}
46
+
47
+	p, err := fetchProfiles(src, o)
48
+	if err != nil {
49
+		return err
50
+	}
51
+
52
+	if cmd != nil {
53
+		return generateReport(p, cmd, pprofVariables, o)
54
+	}
55
+
56
+	return interactive(p, o)
57
+}
58
+
59
+func generateReport(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
60
+	p = p.Copy() // Prevent modification to the incoming profile.
61
+
62
+	var w io.Writer
63
+	switch output := vars["output"].value; output {
64
+	case "":
65
+		w = os.Stdout
66
+	default:
67
+		o.UI.PrintErr("Generating report in ", output)
68
+		outputFile, err := o.Writer.Open(output)
69
+		if err != nil {
70
+			return err
71
+		}
72
+		defer outputFile.Close()
73
+		w = outputFile
74
+	}
75
+
76
+	vars = applyCommandOverrides(cmd, vars)
77
+
78
+	// Delay focus after configuring report to get percentages on all samples.
79
+	relative := vars["relative_percentages"].boolValue()
80
+	if relative {
81
+		if err := applyFocus(p, vars, o.UI); err != nil {
82
+			return err
83
+		}
84
+	}
85
+	ropt, err := reportOptions(p, vars)
86
+	if err != nil {
87
+		return err
88
+	}
89
+	c := pprofCommands[cmd[0]]
90
+	if c == nil {
91
+		panic("unexpected nil command")
92
+	}
93
+	ropt.OutputFormat = c.format
94
+	post := c.postProcess
95
+	if len(cmd) == 2 {
96
+		s, err := regexp.Compile(cmd[1])
97
+		if err != nil {
98
+			return fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
99
+		}
100
+		ropt.Symbol = s
101
+	}
102
+
103
+	rpt := report.New(p, ropt)
104
+	if !relative {
105
+		if err := applyFocus(p, vars, o.UI); err != nil {
106
+			return err
107
+		}
108
+	}
109
+
110
+	if err := aggregate(p, vars); err != nil {
111
+		return err
112
+	}
113
+
114
+	if post == nil {
115
+		return report.Generate(w, rpt, o.Obj)
116
+	}
117
+
118
+	// Capture output into buffer and send to postprocessing command.
119
+	buf := &bytes.Buffer{}
120
+	if err := report.Generate(buf, rpt, o.Obj); err != nil {
121
+		return err
122
+	}
123
+	return post(buf.Bytes(), w, o.UI)
124
+}
125
+
126
+func applyCommandOverrides(cmd []string, v variables) variables {
127
+	trim, focus, hide := v["trim"].boolValue(), true, true
128
+
129
+	switch cmd[0] {
130
+	case "proto", "raw":
131
+		trim, focus, hide = false, false, false
132
+		v.set("addresses", "t")
133
+	case "disasm", "weblist":
134
+		trim = false
135
+		v.set("addressnoinlines", "t")
136
+	case "peek":
137
+		trim, focus, hide = false, false, false
138
+	case "callgrind", "list":
139
+		v.set("nodecount", "0")
140
+		v.set("lines", "t")
141
+	case "text", "top", "topproto":
142
+		if v["nodecount"].intValue() == -1 {
143
+			v.set("nodecount", "0")
144
+		}
145
+	default:
146
+		if v["nodecount"].intValue() == -1 {
147
+			v.set("nodecount", "80")
148
+		}
149
+	}
150
+	if trim == false {
151
+		v.set("nodecount", "0")
152
+		v.set("nodefraction", "0")
153
+		v.set("edgefraction", "0")
154
+	}
155
+	if focus == false {
156
+		v.set("focus", "")
157
+		v.set("ignore", "")
158
+		v.set("tagfocus", "")
159
+		v.set("tagignore", "")
160
+	}
161
+	if hide == false {
162
+		v.set("hide", "")
163
+		v.set("show", "")
164
+	}
165
+	return v
166
+}
167
+
168
+func aggregate(prof *profile.Profile, v variables) error {
169
+	var inlines, function, filename, linenumber, address bool
170
+	switch {
171
+	case v["addresses"].boolValue():
172
+		return nil
173
+	case v["lines"].boolValue():
174
+		inlines = true
175
+		function = true
176
+		filename = true
177
+		linenumber = true
178
+	case v["files"].boolValue():
179
+		inlines = true
180
+		filename = true
181
+	case v["functions"].boolValue():
182
+		inlines = true
183
+		function = true
184
+		filename = true
185
+	case v["noinlines"].boolValue():
186
+		function = true
187
+		filename = true
188
+	case v["addressnoinlines"].boolValue():
189
+		function = true
190
+		filename = true
191
+		linenumber = true
192
+		address = true
193
+	case v["functionnameonly"].boolValue():
194
+		inlines = true
195
+		function = true
196
+	default:
197
+		return fmt.Errorf("unexpected granularity")
198
+	}
199
+	return prof.Aggregate(inlines, function, filename, linenumber, address)
200
+}
201
+
202
+func reportOptions(p *profile.Profile, vars variables) (*report.Options, error) {
203
+	si, mean := vars["sample_index"].value, vars["mean"].boolValue()
204
+	value, sample, err := sampleFormat(p, si, mean)
205
+	if err != nil {
206
+		return nil, err
207
+	}
208
+
209
+	stype := sample.Type
210
+	if mean {
211
+		stype = "mean_" + stype
212
+	}
213
+
214
+	if vars["divide_by"].floatValue() == 0 {
215
+		return nil, fmt.Errorf("zero divisor specified")
216
+	}
217
+
218
+	ropt := &report.Options{
219
+		CumSort:             vars["cum"].boolValue(),
220
+		CallTree:            vars["call_tree"].boolValue(),
221
+		DropNegative:        vars["drop_negative"].boolValue(),
222
+		PositivePercentages: vars["positive_percentages"].boolValue(),
223
+
224
+		CompactLabels: vars["compact_labels"].boolValue(),
225
+		Ratio:         1 / vars["divide_by"].floatValue(),
226
+
227
+		NodeCount:    vars["nodecount"].intValue(),
228
+		NodeFraction: vars["nodefraction"].floatValue(),
229
+		EdgeFraction: vars["edgefraction"].floatValue(),
230
+
231
+		SampleValue: value,
232
+		SampleType:  stype,
233
+		SampleUnit:  sample.Unit,
234
+
235
+		OutputUnit: vars["unit"].value,
236
+	}
237
+
238
+	if len(p.Mapping) > 0 {
239
+		ropt.Title = filepath.Base(p.Mapping[0].File)
240
+	}
241
+
242
+	return ropt, nil
243
+}
244
+
245
+type sampleValueFunc func([]int64) int64
246
+
247
+// sampleFormat returns a function to extract values out of a profile.Sample,
248
+// and the type/units of those values.
249
+func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (sampleValueFunc, *profile.ValueType, error) {
250
+	if len(p.SampleType) == 0 {
251
+		return nil, nil, fmt.Errorf("profile has no samples")
252
+	}
253
+	index, err := locateSampleIndex(p, sampleIndex)
254
+	if err != nil {
255
+		return nil, nil, err
256
+	}
257
+	if mean {
258
+		return meanExtractor(index), p.SampleType[index], nil
259
+	}
260
+	return valueExtractor(index), p.SampleType[index], nil
261
+}
262
+
263
+func valueExtractor(ix int) sampleValueFunc {
264
+	return func(v []int64) int64 {
265
+		return v[ix]
266
+	}
267
+}
268
+
269
+func meanExtractor(ix int) sampleValueFunc {
270
+	return func(v []int64) int64 {
271
+		if v[0] == 0 {
272
+			return 0
273
+		}
274
+		return v[ix] / v[0]
275
+	}
276
+}

+ 168
- 0
src/internal/driver/driver_focus.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"fmt"
19
+	"regexp"
20
+	"strconv"
21
+	"strings"
22
+
23
+	"internal/measurement"
24
+	"internal/plugin"
25
+	"profile"
26
+)
27
+
28
+var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)")
29
+
30
+// applyFocus filters samples based on the focus/ignore options
31
+func applyFocus(prof *profile.Profile, v variables, ui plugin.UI) error {
32
+	focus, err := compileRegexOption("focus", v["focus"].value, nil)
33
+	ignore, err := compileRegexOption("ignore", v["ignore"].value, err)
34
+	hide, err := compileRegexOption("hide", v["hide"].value, err)
35
+	show, err := compileRegexOption("show", v["show"].value, err)
36
+	tagfocus, err := compileTagFilter("tagfocus", v["tagfocus"].value, ui, err)
37
+	tagignore, err := compileTagFilter("tagignore", v["tagignore"].value, ui, err)
38
+	prunefrom, err := compileRegexOption("prune_from", v["prune_from"].value, err)
39
+	if err != nil {
40
+		return err
41
+	}
42
+
43
+	fm, im, hm, hnm := prof.FilterSamplesByName(focus, ignore, hide, show)
44
+	warnNoMatches(focus == nil || fm, "Focus", ui)
45
+	warnNoMatches(ignore == nil || im, "Ignore", ui)
46
+	warnNoMatches(hide == nil || hm, "Hide", ui)
47
+	warnNoMatches(show == nil || hnm, "Show", ui)
48
+
49
+	tfm, tim := prof.FilterSamplesByTag(tagfocus, tagignore)
50
+	warnNoMatches(tagfocus == nil || tfm, "TagFocus", ui)
51
+	warnNoMatches(tagignore == nil || tim, "TagIgnore", ui)
52
+
53
+	if prunefrom != nil {
54
+		prof.PruneFrom(prunefrom)
55
+	}
56
+	return nil
57
+}
58
+
59
+func compileRegexOption(name, value string, err error) (*regexp.Regexp, error) {
60
+	if value == "" || err != nil {
61
+		return nil, err
62
+	}
63
+	rx, err := regexp.Compile(value)
64
+	if err != nil {
65
+		return nil, fmt.Errorf("parsing %s regexp: %v", name, err)
66
+	}
67
+	return rx, nil
68
+}
69
+
70
+func compileTagFilter(name, value string, ui plugin.UI, err error) (func(*profile.Sample) bool, error) {
71
+	if value == "" || err != nil {
72
+		return nil, err
73
+	}
74
+	if numFilter := parseTagFilterRange(value); numFilter != nil {
75
+		ui.PrintErr(name, ":Interpreted '", value, "' as range, not regexp")
76
+		return func(s *profile.Sample) bool {
77
+			for key, vals := range s.NumLabel {
78
+				for _, val := range vals {
79
+					if numFilter(val, key) {
80
+						return true
81
+					}
82
+				}
83
+			}
84
+			return false
85
+		}, nil
86
+	}
87
+	var rfx []*regexp.Regexp
88
+	for _, tagf := range strings.Split(value, ",") {
89
+		fx, err := regexp.Compile(tagf)
90
+		if err != nil {
91
+			return nil, fmt.Errorf("parsing %s regexp: %v", name, err)
92
+		}
93
+		rfx = append(rfx, fx)
94
+	}
95
+	return func(s *profile.Sample) bool {
96
+	matchedrx:
97
+		for _, rx := range rfx {
98
+			for key, vals := range s.Label {
99
+				for _, val := range vals {
100
+					if rx.MatchString(key + ":" + val) {
101
+						continue matchedrx
102
+					}
103
+				}
104
+			}
105
+			return false
106
+		}
107
+		return true
108
+	}, nil
109
+}
110
+
111
+// parseTagFilterRange returns a function to checks if a value is
112
+// contained on the range described by a string. It can recognize
113
+// strings of the form:
114
+// "32kb" -- matches values == 32kb
115
+// ":64kb" -- matches values <= 64kb
116
+// "4mb:" -- matches values >= 4mb
117
+// "12kb:64mb" -- matches values between 12kb and 64mb (both included).
118
+func parseTagFilterRange(filter string) func(int64, string) bool {
119
+	ranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2)
120
+	if len(ranges) == 0 {
121
+		return nil // No ranges were identified
122
+	}
123
+	v, err := strconv.ParseInt(ranges[0][1], 10, 64)
124
+	if err != nil {
125
+		panic(fmt.Errorf("Failed to parse int %s: %v", ranges[0][1], err))
126
+	}
127
+	scaledValue, unit := measurement.Scale(v, ranges[0][2], ranges[0][2])
128
+	if len(ranges) == 1 {
129
+		switch match := ranges[0][0]; filter {
130
+		case match:
131
+			return func(v int64, u string) bool {
132
+				sv, su := measurement.Scale(v, u, unit)
133
+				return su == unit && sv == scaledValue
134
+			}
135
+		case match + ":":
136
+			return func(v int64, u string) bool {
137
+				sv, su := measurement.Scale(v, u, unit)
138
+				return su == unit && sv >= scaledValue
139
+			}
140
+		case ":" + match:
141
+			return func(v int64, u string) bool {
142
+				sv, su := measurement.Scale(v, u, unit)
143
+				return su == unit && sv <= scaledValue
144
+			}
145
+		}
146
+		return nil
147
+	}
148
+	if filter != ranges[0][0]+":"+ranges[1][0] {
149
+		return nil
150
+	}
151
+	if v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil {
152
+		panic(fmt.Errorf("Failed to parse int %s: %v", ranges[1][1], err))
153
+	}
154
+	scaledValue2, unit2 := measurement.Scale(v, ranges[1][2], unit)
155
+	if unit != unit2 {
156
+		return nil
157
+	}
158
+	return func(v int64, u string) bool {
159
+		sv, su := measurement.Scale(v, u, unit)
160
+		return su == unit && sv >= scaledValue && sv <= scaledValue2
161
+	}
162
+}
163
+
164
+func warnNoMatches(match bool, option string, ui plugin.UI) {
165
+	if !match {
166
+		ui.PrintErr(option + " expression matched no samples")
167
+	}
168
+}

+ 1004
- 0
src/internal/driver/driver_test.go
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 367
- 0
src/internal/driver/fetch.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"fmt"
19
+	"io"
20
+	"net/http"
21
+	"net/url"
22
+	"os"
23
+	"path/filepath"
24
+	"strconv"
25
+	"sync"
26
+	"time"
27
+
28
+	"internal/measurement"
29
+	"internal/plugin"
30
+	"profile"
31
+)
32
+
33
+// fetchProfiles fetches and symbolizes the profiles specified by s.
34
+// It will merge all the profiles it is able to retrieve, even if
35
+// there are some failures. It will return an error if it is unable to
36
+// fetch any profiles.
37
+func fetchProfiles(s *source, o *plugin.Options) (*profile.Profile, error) {
38
+	if err := setTmpDir(o.UI); err != nil {
39
+		return nil, err
40
+	}
41
+
42
+	p, msrcs, save, err := concurrentGrab(s, o.Fetch, o.Obj, o.UI)
43
+	if err != nil {
44
+		return nil, err
45
+	}
46
+
47
+	// Symbolize the merged profile.
48
+	if err := o.Sym.Symbolize(s.Symbolize, msrcs, p); err != nil {
49
+		return nil, err
50
+	}
51
+	p.RemoveUninteresting()
52
+
53
+	// Save a copy of the merged profile if there is at least one remote source.
54
+	if save {
55
+		prefix := "pprof."
56
+		if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
57
+			prefix += filepath.Base(p.Mapping[0].File) + "."
58
+		}
59
+		for _, s := range p.SampleType {
60
+			prefix += s.Type + "."
61
+		}
62
+
63
+		dir := os.Getenv("PPROF_TMPDIR")
64
+		tempFile, err := newTempFile(dir, prefix, ".pb.gz")
65
+		if err == nil {
66
+			if err = p.Write(tempFile); err == nil {
67
+				o.UI.PrintErr("Saved profile in ", tempFile.Name())
68
+			}
69
+		}
70
+		if err != nil {
71
+			o.UI.PrintErr("Could not save profile: ", err)
72
+		}
73
+	}
74
+
75
+	if err := p.CheckValid(); err != nil {
76
+		return nil, err
77
+	}
78
+
79
+	return p, nil
80
+}
81
+
82
+// concurrentGrab fetches multiple profiles concurrently
83
+func concurrentGrab(s *source, fetch plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (*profile.Profile, plugin.MappingSources, bool, error) {
84
+	wg := sync.WaitGroup{}
85
+	numprofs := len(s.Sources) + len(s.Base)
86
+	profs := make([]*profile.Profile, numprofs)
87
+	msrcs := make([]plugin.MappingSources, numprofs)
88
+	remote := make([]bool, numprofs)
89
+	errs := make([]error, numprofs)
90
+	for i, source := range s.Sources {
91
+		wg.Add(1)
92
+		go func(i int, src string) {
93
+			defer wg.Done()
94
+			profs[i], msrcs[i], remote[i], errs[i] = grabProfile(s, src, 1, fetch, obj, ui)
95
+		}(i, source)
96
+	}
97
+	for i, source := range s.Base {
98
+		wg.Add(1)
99
+		go func(i int, src string) {
100
+			defer wg.Done()
101
+			profs[i], msrcs[i], remote[i], errs[i] = grabProfile(s, src, -1, fetch, obj, ui)
102
+		}(i+len(s.Sources), source)
103
+	}
104
+	wg.Wait()
105
+	var save bool
106
+	var numFailed = 0
107
+	for i, src := range s.Sources {
108
+		if errs[i] != nil {
109
+			ui.PrintErr(src + ": " + errs[i].Error())
110
+			numFailed++
111
+		}
112
+		save = save || remote[i]
113
+	}
114
+	for i, src := range s.Base {
115
+		b := i + len(s.Sources)
116
+		if errs[b] != nil {
117
+			ui.PrintErr(src + ": " + errs[b].Error())
118
+			numFailed++
119
+		}
120
+		save = save || remote[b]
121
+	}
122
+	if numFailed == numprofs {
123
+		return nil, nil, false, fmt.Errorf("failed to fetch any profiles")
124
+	}
125
+	if numFailed > 0 {
126
+		ui.PrintErr(fmt.Sprintf("fetched %d profiles out of %d", numprofs-numFailed, numprofs))
127
+	}
128
+
129
+	scaled := make([]*profile.Profile, 0, numprofs)
130
+	for _, p := range profs {
131
+		if p != nil {
132
+			scaled = append(scaled, p)
133
+		}
134
+	}
135
+
136
+	// Merge profiles.
137
+	if err := measurement.ScaleProfiles(scaled); err != nil {
138
+		return nil, nil, false, err
139
+	}
140
+
141
+	p, err := profile.Merge(scaled)
142
+	if err != nil {
143
+		return nil, nil, false, err
144
+	}
145
+
146
+	// Combine mapping sources.
147
+	msrc := make(plugin.MappingSources)
148
+	for _, ms := range msrcs {
149
+		for m, s := range ms {
150
+			msrc[m] = append(msrc[m], s...)
151
+		}
152
+	}
153
+
154
+	return p, msrc, save, nil
155
+}
156
+
157
+// setTmpDir sets the PPROF_TMPDIR environment variable with a new
158
+// temp directory, if not already set.
159
+func setTmpDir(ui plugin.UI) error {
160
+	if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir != "" {
161
+		return nil
162
+	}
163
+	for _, tmpDir := range []string{os.Getenv("HOME") + "/pprof", "/tmp"} {
164
+		if err := os.MkdirAll(tmpDir, 0755); err != nil {
165
+			ui.PrintErr("Could not use temp dir ", tmpDir, ": ", err.Error())
166
+			continue
167
+		}
168
+		os.Setenv("PPROF_TMPDIR", tmpDir)
169
+		return nil
170
+	}
171
+	return fmt.Errorf("failed to identify temp dir")
172
+}
173
+
174
+// grabProfile fetches a profile. Returns the profile, sources for the
175
+// profile mappings, a bool indicating if the profile was fetched
176
+// remotely, and an error.
177
+func grabProfile(s *source, source string, scale float64, fetcher plugin.Fetcher, obj plugin.ObjTool, ui plugin.UI) (p *profile.Profile, msrc plugin.MappingSources, remote bool, err error) {
178
+	var src string
179
+	duration, timeout := time.Duration(s.Seconds)*time.Second, time.Duration(s.Timeout)*time.Second
180
+	if fetcher != nil {
181
+		p, src, err = fetcher.Fetch(source, duration, timeout)
182
+		if err != nil {
183
+			return
184
+		}
185
+	}
186
+	if err != nil || p == nil {
187
+		// Fetch the profile over HTTP or from a file.
188
+		p, src, err = fetch(source, duration, timeout, ui)
189
+		if err != nil {
190
+			return
191
+		}
192
+	}
193
+
194
+	if err = p.CheckValid(); err != nil {
195
+		return
196
+	}
197
+
198
+	// Apply local changes to the profile.
199
+	p.Scale(scale)
200
+
201
+	// Update the binary locations from command line and paths.
202
+	locateBinaries(p, s, obj, ui)
203
+
204
+	// Collect the source URL for all mappings.
205
+	if src != "" {
206
+		msrc = collectMappingSources(p, src)
207
+		remote = true
208
+	}
209
+	return
210
+}
211
+
212
+// collectMappingSources saves the mapping sources of a profile.
213
+func collectMappingSources(p *profile.Profile, source string) plugin.MappingSources {
214
+	ms := plugin.MappingSources{}
215
+	for _, m := range p.Mapping {
216
+		src := struct {
217
+			Source string
218
+			Start  uint64
219
+		}{
220
+			source, m.Start,
221
+		}
222
+		if key := m.BuildID; key != "" {
223
+			ms[key] = append(ms[key], src)
224
+		}
225
+		if key := m.File; key != "" {
226
+			ms[key] = append(ms[key], src)
227
+		}
228
+	}
229
+	return ms
230
+}
231
+
232
+// locateBinaries searches for binary files listed in the profile and, if found,
233
+// updates the profile accordingly.
234
+func locateBinaries(p *profile.Profile, s *source, obj plugin.ObjTool, ui plugin.UI) {
235
+	// Construct search path to examine
236
+	searchPath := os.Getenv("PPROF_BINARY_PATH")
237
+	if searchPath == "" {
238
+		// Use $HOME/pprof/binaries as default directory for local symbolization binaries
239
+		searchPath = filepath.Join(os.Getenv("HOME"), "pprof", "binaries")
240
+	}
241
+
242
+mapping:
243
+	for i, m := range p.Mapping {
244
+		var baseName string
245
+		// Replace executable filename/buildID with the overrides from source.
246
+		// Assumes the executable is the first Mapping entry.
247
+		if i == 0 {
248
+			if s.ExecName != "" {
249
+				m.File = s.ExecName
250
+			}
251
+			if s.BuildID != "" {
252
+				m.BuildID = s.BuildID
253
+			}
254
+		}
255
+		if m.File != "" {
256
+			baseName = filepath.Base(m.File)
257
+		}
258
+
259
+		for _, path := range filepath.SplitList(searchPath) {
260
+			var fileNames []string
261
+			if m.BuildID != "" {
262
+				fileNames = []string{filepath.Join(path, m.BuildID, baseName)}
263
+				if matches, err := filepath.Glob(filepath.Join(path, m.BuildID, "*")); err == nil {
264
+					fileNames = append(fileNames, matches...)
265
+				}
266
+			}
267
+			if baseName != "" {
268
+				fileNames = append(fileNames, filepath.Join(path, baseName))
269
+			}
270
+			for _, name := range fileNames {
271
+				if f, err := obj.Open(name, m.Start, m.Limit, m.Offset); err == nil {
272
+					defer f.Close()
273
+					fileBuildID := f.BuildID()
274
+					if m.BuildID != "" && m.BuildID != fileBuildID {
275
+						ui.PrintErr("Ignoring local file " + name + ": build-id mismatch (" + m.BuildID + " != " + fileBuildID + ")")
276
+					} else {
277
+						m.File = name
278
+						continue mapping
279
+					}
280
+				}
281
+			}
282
+		}
283
+	}
284
+}
285
+
286
+// fetch fetches a profile from source, within the timeout specified,
287
+// producing messages through the ui. It returns the profile and the
288
+// url of the actual source of the profile for remote profiles.
289
+func fetch(source string, duration, timeout time.Duration, ui plugin.UI) (p *profile.Profile, src string, err error) {
290
+	var f io.ReadCloser
291
+
292
+	if sourceURL, timeout := adjustURL(source, duration, timeout); sourceURL != "" {
293
+		ui.Print("Fetching profile over HTTP from " + sourceURL)
294
+		if duration > 0 {
295
+			ui.Print(fmt.Sprintf("Please wait... (%v)", duration))
296
+		}
297
+		f, err = fetchURL(sourceURL, timeout)
298
+		src = sourceURL
299
+	} else {
300
+		f, err = os.Open(source)
301
+	}
302
+	if err == nil {
303
+		defer f.Close()
304
+		p, err = profile.Parse(f)
305
+	}
306
+	return
307
+}
308
+
309
+// fetchURL fetches a profile from a URL using HTTP.
310
+func fetchURL(source string, timeout time.Duration) (io.ReadCloser, error) {
311
+	resp, err := httpGet(source, timeout)
312
+	if err != nil {
313
+		return nil, fmt.Errorf("http fetch %s: %v", source, err)
314
+	}
315
+	if resp.StatusCode != http.StatusOK {
316
+		return nil, fmt.Errorf("server response: %s", resp.Status)
317
+	}
318
+
319
+	return resp.Body, nil
320
+}
321
+
322
+// adjustURL validates if a profile source is a URL and returns an
323
+// cleaned up URL and the timeout to use for retrieval over HTTP.
324
+// If the source cannot be recognized as a URL it returns an empty string.
325
+func adjustURL(source string, duration, timeout time.Duration) (string, time.Duration) {
326
+	u, err := url.Parse(source)
327
+	if err != nil || (u.Host == "" && u.Scheme != "" && u.Scheme != "file") {
328
+		// Try adding http:// to catch sources of the form hostname:port/path.
329
+		// url.Parse treats "hostname" as the scheme.
330
+		u, err = url.Parse("http://" + source)
331
+	}
332
+	if err != nil || u.Host == "" {
333
+		return "", 0
334
+	}
335
+
336
+	// Apply duration/timeout overrides to URL.
337
+	values := u.Query()
338
+	if duration > 0 {
339
+		values.Set("seconds", fmt.Sprint(int(duration.Seconds())))
340
+	} else {
341
+		if urlSeconds := values.Get("seconds"); urlSeconds != "" {
342
+			if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
343
+				duration = time.Duration(us) * time.Second
344
+			}
345
+		}
346
+	}
347
+	if timeout <= 0 {
348
+		if duration > 0 {
349
+			timeout = duration + duration/2
350
+		} else {
351
+			timeout = 60 * time.Second
352
+		}
353
+	}
354
+	u.RawQuery = values.Encode()
355
+	return u.String(), timeout
356
+}
357
+
358
+// httpGet is a wrapper around http.Get; it is defined as a variable
359
+// so it can be redefined during for testing.
360
+var httpGet = func(url string, timeout time.Duration) (*http.Response, error) {
361
+	client := &http.Client{
362
+		Transport: &http.Transport{
363
+			ResponseHeaderTimeout: timeout + 5*time.Second,
364
+		},
365
+	}
366
+	return client.Get(url)
367
+}

+ 148
- 0
src/internal/driver/fetch_test.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"fmt"
19
+	"io/ioutil"
20
+	"net/http"
21
+	"net/url"
22
+	"os"
23
+	"path/filepath"
24
+	"regexp"
25
+	"testing"
26
+	"time"
27
+
28
+	"internal/plugin"
29
+	"internal/proftest"
30
+	"profile"
31
+)
32
+
33
+func TestSymbolizationPath(t *testing.T) {
34
+	// Save environment variables to restore after test
35
+	saveHome := os.Getenv("HOME")
36
+	savePath := os.Getenv("PPROF_BINARY_PATH")
37
+
38
+	tempdir, err := ioutil.TempDir("", "home")
39
+	if err != nil {
40
+		t.Fatal("creating temp dir: ", err)
41
+	}
42
+	defer os.RemoveAll(tempdir)
43
+	os.MkdirAll(filepath.Join(tempdir, "pprof", "binaries", "abcde10001"), 0700)
44
+	os.Create(filepath.Join(tempdir, "pprof", "binaries", "abcde10001", "binary"))
45
+
46
+	obj := testObj{tempdir}
47
+	os.Setenv("HOME", tempdir)
48
+	for _, tc := range []struct {
49
+		env, file, buildID, want string
50
+		msgCount                 int
51
+	}{
52
+		{"", "/usr/bin/binary", "", "/usr/bin/binary", 0},
53
+		{"", "/usr/bin/binary", "fedcb10000", "/usr/bin/binary", 0},
54
+		{"", "/prod/path/binary", "abcde10001", filepath.Join(tempdir, "pprof/binaries/abcde10001/binary"), 0},
55
+		{"/alternate/architecture", "/usr/bin/binary", "", "/alternate/architecture/binary", 0},
56
+		{"/alternate/architecture", "/usr/bin/binary", "abcde10001", "/alternate/architecture/binary", 0},
57
+		{"/nowhere:/alternate/architecture", "/usr/bin/binary", "fedcb10000", "/usr/bin/binary", 1},
58
+		{"/nowhere:/alternate/architecture", "/usr/bin/binary", "abcde10002", "/usr/bin/binary", 1},
59
+	} {
60
+		os.Setenv("PPROF_BINARY_PATH", tc.env)
61
+		p := &profile.Profile{
62
+			Mapping: []*profile.Mapping{
63
+				{
64
+					File:    tc.file,
65
+					BuildID: tc.buildID,
66
+				},
67
+			},
68
+		}
69
+		s := &source{}
70
+		locateBinaries(p, s, obj, &proftest.TestUI{t, tc.msgCount})
71
+		if file := p.Mapping[0].File; file != tc.want {
72
+			t.Errorf("%s:%s:%s, want %s, got %s", tc.env, tc.file, tc.buildID, tc.want, file)
73
+		}
74
+	}
75
+	os.Setenv("HOME", saveHome)
76
+	os.Setenv("PPROF_BINARY_PATH", savePath)
77
+}
78
+
79
+type testObj struct {
80
+	home string
81
+}
82
+
83
+func (o testObj) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
84
+	switch file {
85
+	case "/alternate/architecture/binary":
86
+		return testFile{file, "abcde10001"}, nil
87
+	case "/usr/bin/binary":
88
+		return testFile{file, "fedcb10000"}, nil
89
+	case filepath.Join(o.home, "pprof/binaries/abcde10001/binary"):
90
+		return testFile{file, "abcde10001"}, nil
91
+	}
92
+	return nil, fmt.Errorf("not found: %s", file)
93
+}
94
+func (testObj) Demangler(_ string) func(names []string) (map[string]string, error) {
95
+	return func(names []string) (map[string]string, error) { return nil, nil }
96
+}
97
+func (testObj) Disasm(file string, start, end uint64) ([]plugin.Inst, error) { return nil, nil }
98
+
99
+type testFile struct{ name, buildID string }
100
+
101
+func (f testFile) Name() string                                               { return f.name }
102
+func (testFile) Base() uint64                                                 { return 0 }
103
+func (f testFile) BuildID() string                                            { return f.buildID }
104
+func (testFile) SourceLine(addr uint64) ([]plugin.Frame, error)               { return nil, nil }
105
+func (testFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) { return nil, nil }
106
+func (testFile) Close() error                                                 { return nil }
107
+
108
+func TestFetch(t *testing.T) {
109
+	const path = "testdata/"
110
+
111
+	// Intercept http.Get calls from HTTPFetcher.
112
+	httpGet = stubHTTPGet
113
+
114
+	for _, source := range [][2]string{
115
+		{path + "go.crc32.cpu", "go.crc32.cpu"},
116
+		{"http://localhost/profile?file=cppbench.cpu", "cppbench.cpu"},
117
+	} {
118
+		p, _, err := fetch(source[0], 0, 10*time.Second, &proftest.TestUI{t, 0})
119
+		if err != nil {
120
+			t.Fatalf("%s: %s", source[0], err)
121
+		}
122
+		if len(p.Sample) == 0 {
123
+			t.Errorf("want non-zero samples")
124
+		}
125
+	}
126
+}
127
+
128
+// stubHTTPGet intercepts a call to http.Get and rewrites it to use
129
+// "file://" to get the profile directly from a file.
130
+func stubHTTPGet(source string, _ time.Duration) (*http.Response, error) {
131
+	url, err := url.Parse(source)
132
+	if err != nil {
133
+		return nil, err
134
+	}
135
+
136
+	values := url.Query()
137
+	file := values.Get("file")
138
+
139
+	if file == "" {
140
+		return nil, fmt.Errorf("want .../file?profile, got %s", source)
141
+	}
142
+
143
+	t := &http.Transport{}
144
+	t.RegisterProtocol("file", http.NewFileTransport(http.Dir("testdata/")))
145
+
146
+	c := &http.Client{Transport: t}
147
+	return c.Get("file:///" + file)
148
+}

+ 372
- 0
src/internal/driver/interactive.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"fmt"
19
+	"io"
20
+	"sort"
21
+	"strconv"
22
+	"strings"
23
+
24
+	"internal/plugin"
25
+	"internal/report"
26
+	"profile"
27
+)
28
+
29
+// interactive starts a shell to read pprof commands.
30
+func interactive(p *profile.Profile, o *plugin.Options) error {
31
+	// Enter command processing loop.
32
+	o.UI.SetAutoComplete(newCompleter(functionNames(p)))
33
+	pprofVariables.set("compact_labels", "true")
34
+
35
+	// Do not wait for the visualizer to complete, to allow multiple
36
+	// graphs to be visualized simultaneously.
37
+	waitForVisualizer = false
38
+	shortcuts := profileShortcuts(p)
39
+
40
+	greetings(p, o.UI)
41
+	for {
42
+		input, err := o.UI.ReadLine(pprofPrompt(p))
43
+		if err != nil {
44
+			if err != io.EOF {
45
+				return err
46
+			}
47
+			if input == "" {
48
+				return nil
49
+			}
50
+		}
51
+
52
+		for _, input := range shortcuts.expand(input) {
53
+			// Process assignments of the form variable=value
54
+			if s := strings.SplitN(input, "=", 2); len(s) > 0 {
55
+				name := strings.TrimSpace(s[0])
56
+
57
+				if v := pprofVariables[name]; v != nil {
58
+					var value string
59
+					if len(s) == 2 {
60
+						value = strings.TrimSpace(s[1])
61
+					}
62
+					if err := pprofVariables.set(name, value); err != nil {
63
+						o.UI.PrintErr(err)
64
+					}
65
+					continue
66
+				}
67
+			}
68
+
69
+			tokens := strings.Fields(input)
70
+			if len(tokens) == 0 {
71
+				continue
72
+			}
73
+
74
+			switch tokens[0] {
75
+			case "exit", "quit":
76
+				return nil
77
+			case "help":
78
+				commandHelp(strings.Join(tokens[1:], " "), o.UI)
79
+				continue
80
+			}
81
+
82
+			args, vars, err := parseCommandLine(tokens)
83
+			if err == nil {
84
+				err = generateReportWrapper(p, args, vars, o)
85
+			}
86
+
87
+			if err != nil {
88
+				o.UI.PrintErr(err)
89
+			}
90
+		}
91
+	}
92
+}
93
+
94
+var generateReportWrapper = generateReport // For testing purposes.
95
+
96
+// greetings prints a brief welcome and some overall profile
97
+// information before accepting interactive commands.
98
+func greetings(p *profile.Profile, ui plugin.UI) {
99
+	ropt, err := reportOptions(p, pprofVariables)
100
+	if err == nil {
101
+		ui.Print(strings.Join(report.ProfileLabels(report.New(p, ropt)), "\n"))
102
+	}
103
+	ui.Print("Entering interactive mode (type \"help\" for commands)")
104
+}
105
+
106
+// shortcuts represents composite commands that expand into a sequence
107
+// of other commands.
108
+type shortcuts map[string][]string
109
+
110
+func (a shortcuts) expand(input string) []string {
111
+	if a != nil {
112
+		if r, ok := a[input]; ok {
113
+			return r
114
+		}
115
+	}
116
+	return []string{input}
117
+}
118
+
119
+var pprofShortcuts = shortcuts{
120
+	":": []string{"focus=", "ignore=", "hide=", "tagfocus=", "tagignore="},
121
+}
122
+
123
+// profileShortcuts creates macros for convenience and backward compatibility.
124
+func profileShortcuts(p *profile.Profile) shortcuts {
125
+	s := pprofShortcuts
126
+	// Add shortcuts for sample types
127
+	for _, st := range p.SampleType {
128
+		command := fmt.Sprintf("sample_index=%s", st.Type)
129
+		s[st.Type] = []string{command}
130
+		s["total_"+st.Type] = []string{"mean=0", command}
131
+		s["mean_"+st.Type] = []string{"mean=1", command}
132
+	}
133
+	return s
134
+}
135
+
136
+// pprofPrompt returns the prompt displayed to accept commands.
137
+// hides some default values to reduce clutter.
138
+func pprofPrompt(p *profile.Profile) string {
139
+	var args []string
140
+	for n, o := range pprofVariables {
141
+		v := o.stringValue()
142
+		if v == "" {
143
+			continue
144
+		}
145
+		// Do not show some default values.
146
+		switch {
147
+		case n == "unit" && v == "minimum":
148
+			continue
149
+		case n == "divide_by" && v == "1":
150
+			continue
151
+		case n == "nodecount" && v == "-1":
152
+			continue
153
+		case n == "sample_index":
154
+			index, err := locateSampleIndex(p, v)
155
+			if err != nil {
156
+				v = "ERROR: " + err.Error()
157
+			} else {
158
+				v = fmt.Sprintf("%s (%d)", p.SampleType[index].Type, index)
159
+			}
160
+		case n == "trim" || n == "compact_labels":
161
+			if o.boolValue() == true {
162
+				continue
163
+			}
164
+		case o.kind == boolKind:
165
+			if o.boolValue() == false {
166
+				continue
167
+			}
168
+		}
169
+		args = append(args, fmt.Sprintf("  %-25s : %s", n, v))
170
+	}
171
+	sort.Strings(args)
172
+	return "Options:\n" + strings.Join(args, "\n") + "\nPPROF>"
173
+}
174
+
175
+// parseCommandLine parses a command and returns the pprof command to
176
+// execute and a set of variables for the report.
177
+func parseCommandLine(input []string) ([]string, variables, error) {
178
+	cmd, args := input[:1], input[1:]
179
+	name := cmd[0]
180
+
181
+	c := pprofCommands[cmd[0]]
182
+	if c == nil {
183
+		return nil, nil, fmt.Errorf("Unrecognized command: %q", name)
184
+	}
185
+
186
+	if c.hasParam {
187
+		if len(args) == 0 {
188
+			return nil, nil, fmt.Errorf("command %s requires an argument", name)
189
+		}
190
+		cmd = append(cmd, args[0])
191
+		args = args[1:]
192
+	}
193
+
194
+	// Copy the variables as options set in the command line are not persistent.
195
+	vcopy := pprofVariables.makeCopy()
196
+
197
+	var focus, ignore string
198
+	for i := 0; i < len(args); i++ {
199
+		t := args[i]
200
+		if _, err := strconv.ParseInt(t, 10, 32); err == nil {
201
+			vcopy.set("nodecount", t)
202
+			continue
203
+		}
204
+		switch t[0] {
205
+		case '>':
206
+			outputFile := t[1:]
207
+			if outputFile == "" {
208
+				i++
209
+				if i >= len(args) {
210
+					return nil, nil, fmt.Errorf("Unexpected end of line after >")
211
+				}
212
+				outputFile = args[i]
213
+			}
214
+			vcopy.set("output", outputFile)
215
+		case '-':
216
+			if t == "--cum" || t == "-cum" {
217
+				vcopy.set("cum", "t")
218
+				continue
219
+			}
220
+			ignore = catRegex(ignore, t[1:])
221
+		default:
222
+			focus = catRegex(focus, t)
223
+		}
224
+	}
225
+
226
+	if name == "tags" {
227
+		updateFocusIgnore(vcopy, "tag", focus, ignore)
228
+	} else {
229
+		updateFocusIgnore(vcopy, "", focus, ignore)
230
+	}
231
+
232
+	if vcopy["nodecount"].intValue() == -1 && (name == "text" || name == "top") {
233
+		vcopy.set("nodecount", "10")
234
+	}
235
+
236
+	return cmd, vcopy, nil
237
+}
238
+
239
+func updateFocusIgnore(v variables, prefix, f, i string) {
240
+	if f != "" {
241
+		focus := prefix + "focus"
242
+		v.set(focus, catRegex(v[focus].value, f))
243
+	}
244
+
245
+	if i != "" {
246
+		ignore := prefix + "ignore"
247
+		v.set(ignore, catRegex(v[ignore].value, i))
248
+	}
249
+}
250
+
251
+func catRegex(a, b string) string {
252
+	if a != "" && b != "" {
253
+		return a + "|" + b
254
+	}
255
+	return a + b
256
+}
257
+
258
+// commandHelp displays help and usage information for all Commands
259
+// and Variables or a specific Command or Variable.
260
+func commandHelp(args string, ui plugin.UI) {
261
+	if args == "" {
262
+		help := usage(false)
263
+		help = help + `
264
+  :   Clear focus/ignore/hide/tagfocus/tagignore
265
+
266
+  type "help <cmd|option>" for more information
267
+`
268
+
269
+		ui.Print(help)
270
+		return
271
+	}
272
+
273
+	if c := pprofCommands[args]; c != nil {
274
+		ui.Print(c.help(args))
275
+		return
276
+	}
277
+
278
+	if v := pprofVariables[args]; v != nil {
279
+		ui.Print(v.help + "\n")
280
+		return
281
+	}
282
+
283
+	ui.PrintErr("Unknown command: " + args)
284
+}
285
+
286
+// newCompleter creates an autocompletion function for a set of commands.
287
+func newCompleter(fns []string) func(string) string {
288
+	return func(line string) string {
289
+		v := pprofVariables
290
+		switch tokens := strings.Fields(line); len(tokens) {
291
+		case 0:
292
+			// Nothing to complete
293
+		case 1:
294
+			// Single token -- complete command name
295
+			if match := matchVariableOrCommand(v, tokens[0]); match != "" {
296
+				return match
297
+			}
298
+		case 2:
299
+			if tokens[0] == "help" {
300
+				if match := matchVariableOrCommand(v, tokens[1]); match != "" {
301
+					return tokens[0] + " " + match
302
+				}
303
+				return line
304
+			}
305
+			fallthrough
306
+		default:
307
+			// Multiple tokens -- complete using functions, except for tags
308
+			if cmd := pprofCommands[tokens[0]]; cmd != nil && tokens[0] != "tags" {
309
+				lastTokenIdx := len(tokens) - 1
310
+				lastToken := tokens[lastTokenIdx]
311
+				if strings.HasPrefix(lastToken, "-") {
312
+					lastToken = "-" + functionCompleter(lastToken[1:], fns)
313
+				} else {
314
+					lastToken = functionCompleter(lastToken, fns)
315
+				}
316
+				return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
317
+			}
318
+		}
319
+		return line
320
+	}
321
+}
322
+
323
+// matchCommand attempts to match a string token to the prefix of a Command.
324
+func matchVariableOrCommand(v variables, token string) string {
325
+	token = strings.ToLower(token)
326
+	found := ""
327
+	for cmd := range pprofCommands {
328
+		if strings.HasPrefix(cmd, token) {
329
+			if found != "" {
330
+				return ""
331
+			}
332
+			found = cmd
333
+		}
334
+	}
335
+	for variable := range v {
336
+		if strings.HasPrefix(variable, token) {
337
+			if found != "" {
338
+				return ""
339
+			}
340
+			found = variable
341
+		}
342
+	}
343
+	return found
344
+}
345
+
346
+// functionCompleter replaces provided substring with a function
347
+// name retrieved from a profile if a single match exists. Otherwise,
348
+// it returns unchanged substring. It defaults to no-op if the profile
349
+// is not specified.
350
+func functionCompleter(substring string, fns []string) string {
351
+	found := ""
352
+	for _, fName := range fns {
353
+		if strings.Contains(fName, substring) {
354
+			if found != "" {
355
+				return substring
356
+			}
357
+			found = fName
358
+		}
359
+	}
360
+	if found != "" {
361
+		return found
362
+	}
363
+	return substring
364
+}
365
+
366
+func functionNames(p *profile.Profile) []string {
367
+	var fns []string
368
+	for _, fn := range p.Function {
369
+		fns = append(fns, fn.Name)
370
+	}
371
+	return fns
372
+}

+ 305
- 0
src/internal/driver/interactive_test.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"fmt"
19
+	"io"
20
+	"math/rand"
21
+	"strings"
22
+	"testing"
23
+
24
+	"internal/plugin"
25
+	"profile"
26
+	"internal/report"
27
+)
28
+
29
+func TestShell(t *testing.T) {
30
+	p := &profile.Profile{}
31
+	generateReportWrapper = checkValue
32
+	defer func() { generateReportWrapper = generateReport }()
33
+
34
+	// Use test commands and variables to exercise interactive processing
35
+	var savedCommands commands
36
+	savedCommands, pprofCommands = pprofCommands, testCommands
37
+	defer func() { pprofCommands = savedCommands }()
38
+
39
+	savedVariables := pprofVariables
40
+	defer func() { pprofVariables = savedVariables }()
41
+
42
+	// Random interleave of independent scripts
43
+	pprofVariables = testVariables(savedVariables)
44
+	o := setDefaults(nil)
45
+	o.UI = newUI(t, interleave(script, 0))
46
+	if err := interactive(p, o); err != nil {
47
+		t.Error("first attempt:", err)
48
+	}
49
+	// Random interleave of independent scripts
50
+	pprofVariables = testVariables(savedVariables)
51
+	o.UI = newUI(t, interleave(script, 1))
52
+	if err := interactive(p, o); err != nil {
53
+		t.Error("second attempt:", err)
54
+	}
55
+
56
+	// Random interleave of independent scripts with shortcuts
57
+	pprofVariables = testVariables(savedVariables)
58
+	var scScript []string
59
+	pprofShortcuts, scScript = makeShortcuts(interleave(script, 2), 1)
60
+	o.UI = newUI(t, scScript)
61
+	if err := interactive(p, o); err != nil {
62
+		t.Error("first shortcut attempt:", err)
63
+	}
64
+
65
+	// Random interleave of independent scripts with shortcuts
66
+	pprofVariables = testVariables(savedVariables)
67
+	pprofShortcuts, scScript = makeShortcuts(interleave(script, 1), 2)
68
+	o.UI = newUI(t, scScript)
69
+	if err := interactive(p, o); err != nil {
70
+		t.Error("second shortcut attempt:", err)
71
+	}
72
+
73
+	// Verify propagation of IO errors
74
+	pprofVariables = testVariables(savedVariables)
75
+	o.UI = newUI(t, []string{"**error**"})
76
+	if err := interactive(p, o); err == nil {
77
+		t.Error("expected IO error, got nil")
78
+	}
79
+
80
+}
81
+
82
+var testCommands = commands{
83
+	"check": &command{report.Raw, nil, true, "", ""},
84
+}
85
+
86
+func testVariables(base variables) variables {
87
+	v := base.makeCopy()
88
+
89
+	v["b"] = &variable{boolKind, "f", "", ""}
90
+	v["bb"] = &variable{boolKind, "f", "", ""}
91
+	v["i"] = &variable{intKind, "0", "", ""}
92
+	v["ii"] = &variable{intKind, "0", "", ""}
93
+	v["f"] = &variable{floatKind, "0", "", ""}
94
+	v["ff"] = &variable{floatKind, "0", "", ""}
95
+	v["s"] = &variable{stringKind, "", "", ""}
96
+	v["ss"] = &variable{stringKind, "", "", ""}
97
+
98
+	v["ta"] = &variable{boolKind, "f", "radio", ""}
99
+	v["tb"] = &variable{boolKind, "f", "radio", ""}
100
+	v["tc"] = &variable{boolKind, "t", "radio", ""}
101
+
102
+	return v
103
+}
104
+
105
+// script contains sequences of commands to be executed for testing. Commands
106
+// are split by semicolon and interleaved randomly, so they must be
107
+// independent from each other.
108
+var script = []string{
109
+	"bb=true;bb=false;check bb=false;bb=yes;check bb=true",
110
+	"b=1;check b=true;b=n;check b=false",
111
+	"i=-1;i=-2;check i=-2;i=999999;check i=999999",
112
+	"check ii=0;ii=-1;check ii=-1;ii=100;check ii=100",
113
+	"f=-1;f=-2.5;check f=-2.5;f=0.0001;check f=0.0001",
114
+	"check ff=0;ff=-1.01;check ff=-1.01;ff=100;check ff=100",
115
+	"s=one;s=two;check s=two",
116
+	"ss=tree;check ss=tree;ss=;check ss;ss=forest;check ss=forest",
117
+	"ta=true;check ta=true;check tb=false;check tc=false;tb=1;check tb=true;check ta=false;check tc=false;tc=yes;check tb=false;check ta=false;check tc=true",
118
+}
119
+
120
+func makeShortcuts(input []string, seed int) (shortcuts, []string) {
121
+	rand.Seed(int64(seed))
122
+
123
+	s := shortcuts{}
124
+	var output, chunk []string
125
+	for _, l := range input {
126
+		chunk = append(chunk, l)
127
+		switch rand.Intn(3) {
128
+		case 0:
129
+			// Create a macro for commands in 'chunk'.
130
+			macro := fmt.Sprintf("alias%d", len(s))
131
+			s[macro] = chunk
132
+			output = append(output, macro)
133
+			chunk = nil
134
+		case 1:
135
+			// Append commands in 'chunk' by themselves.
136
+			output = append(output, chunk...)
137
+			chunk = nil
138
+		case 2:
139
+			// Accumulate commands into 'chunk'
140
+		}
141
+	}
142
+	output = append(output, chunk...)
143
+	return s, output
144
+}
145
+
146
+func newUI(t *testing.T, input []string) plugin.UI {
147
+	return &testUI{
148
+		t:     t,
149
+		input: input,
150
+	}
151
+}
152
+
153
+type testUI struct {
154
+	t     *testing.T
155
+	input []string
156
+	index int
157
+}
158
+
159
+func (ui *testUI) ReadLine(_ string) (string, error) {
160
+	if ui.index >= len(ui.input) {
161
+		return "", io.EOF
162
+	}
163
+	input := ui.input[ui.index]
164
+	if input == "**error**" {
165
+		return "", fmt.Errorf("Error: %s", input)
166
+	}
167
+	ui.index++
168
+	return input, nil
169
+}
170
+
171
+func (ui *testUI) Print(args ...interface{}) {
172
+}
173
+
174
+func (ui *testUI) PrintErr(args ...interface{}) {
175
+	output := fmt.Sprint(args)
176
+	if output != "" {
177
+		ui.t.Error(output)
178
+	}
179
+}
180
+
181
+func (ui *testUI) IsTerminal() bool {
182
+	return false
183
+}
184
+
185
+func (ui *testUI) SetAutoComplete(func(string) string) {
186
+}
187
+
188
+func checkValue(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
189
+	if len(cmd) != 2 {
190
+		return fmt.Errorf("expected len(cmd)==2, got %v", cmd)
191
+	}
192
+
193
+	input := cmd[1]
194
+	args := strings.SplitN(input, "=", 2)
195
+	if len(args) == 0 {
196
+		return fmt.Errorf("unexpected empty input")
197
+	}
198
+	name, value := args[0], ""
199
+	if len(args) == 2 {
200
+		value = args[1]
201
+	}
202
+
203
+	gotv := vars[name]
204
+	if gotv == nil {
205
+		return fmt.Errorf("Could not find variable named %s", name)
206
+	}
207
+
208
+	if got := gotv.stringValue(); got != value {
209
+		return fmt.Errorf("Variable %s, want %s, got %s", name, value, got)
210
+	}
211
+	return nil
212
+}
213
+
214
+func interleave(input []string, seed int) []string {
215
+	var inputs [][]string
216
+	for _, s := range input {
217
+		inputs = append(inputs, strings.Split(s, ";"))
218
+	}
219
+	rand.Seed(int64(seed))
220
+	var output []string
221
+	for len(inputs) > 0 {
222
+		next := rand.Intn(len(inputs))
223
+		output = append(output, inputs[next][0])
224
+		if tail := inputs[next][1:]; len(tail) > 0 {
225
+			inputs[next] = tail
226
+		} else {
227
+			inputs = append(inputs[:next], inputs[next+1:]...)
228
+		}
229
+	}
230
+	return output
231
+}
232
+
233
+func TestInteractiveCommands(t *testing.T) {
234
+	type interactiveTestcase struct {
235
+		input string
236
+		want  map[string]string
237
+	}
238
+
239
+	testcases := []interactiveTestcase{
240
+		{
241
+			"top 10 --cum focus1 -ignore focus2",
242
+			map[string]string{
243
+				"functions": "true",
244
+				"nodecount": "10",
245
+				"cum":       "true",
246
+				"focus":     "focus1|focus2",
247
+				"ignore":    "ignore",
248
+			},
249
+		},
250
+		{
251
+			"dot",
252
+			map[string]string{
253
+				"functions": "true",
254
+				"nodecount": "80",
255
+				"cum":       "false",
256
+			},
257
+		},
258
+		{
259
+			"tags   -ignore1 -ignore2 focus1 >out",
260
+			map[string]string{
261
+				"functions": "true",
262
+				"nodecount": "80",
263
+				"cum":       "false",
264
+				"output":    "out",
265
+				"tagfocus":  "focus1",
266
+				"tagignore": "ignore1|ignore2",
267
+			},
268
+		},
269
+		{"weblist  find -test",
270
+			map[string]string{
271
+				"functions":        "false",
272
+				"addressnoinlines": "true",
273
+				"nodecount":        "0",
274
+				"cum":              "false",
275
+				"flat":             "true",
276
+				"ignore":           "test",
277
+			},
278
+		},
279
+		{
280
+			"callgrind   fun -ignore  >out",
281
+			map[string]string{
282
+				"functions": "false",
283
+				"lines":     "true",
284
+				"nodecount": "0",
285
+				"cum":       "false",
286
+				"flat":      "true",
287
+				"output":    "out",
288
+			},
289
+		},
290
+	}
291
+
292
+	for _, tc := range testcases {
293
+		cmd, vars, err := parseCommandLine(strings.Fields(tc.input))
294
+		vars = applyCommandOverrides(cmd, vars)
295
+		if err != nil {
296
+			t.Errorf("failed on %q: %v", tc.input, err)
297
+		}
298
+
299
+		for n, want := range tc.want {
300
+			if got := vars[n].stringValue(); got != want {
301
+				t.Errorf("failed on %q, cmd=%q, %s got %s, want %s", tc.input, cmd, n, got, want)
302
+			}
303
+		}
304
+	}
305
+}

+ 148
- 0
src/internal/driver/options.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"bufio"
19
+	"flag"
20
+	"fmt"
21
+	"io"
22
+	"os"
23
+	"strings"
24
+
25
+	"internal/binutils"
26
+	"internal/plugin"
27
+	"internal/symbolizer"
28
+)
29
+
30
+// setDefaults returns a new plugin.Options with zero fields sets to
31
+// sensible defaults.
32
+func setDefaults(o *plugin.Options) *plugin.Options {
33
+	d := &plugin.Options{}
34
+	if o != nil {
35
+		*d = *o
36
+	}
37
+	if d.Writer == nil {
38
+		d.Writer = oswriter{}
39
+	}
40
+	if d.Flagset == nil {
41
+		d.Flagset = goFlags{}
42
+	}
43
+	if d.Obj == nil {
44
+		d.Obj = &binutils.Binutils{}
45
+	}
46
+	if d.UI == nil {
47
+		d.UI = &stdUI{r: bufio.NewReader(os.Stdin)}
48
+	}
49
+	if d.Sym == nil {
50
+		d.Sym = &symbolizer.Symbolizer{d.Obj, d.UI}
51
+	}
52
+	return d
53
+}
54
+
55
+// goFlags returns a flagset implementation based on the standard flag
56
+// package from the Go distribution. It implements the plugin.FlagSet
57
+// interface.
58
+type goFlags struct{}
59
+
60
+func (goFlags) Bool(o string, d bool, c string) *bool {
61
+	return flag.Bool(o, d, c)
62
+}
63
+
64
+func (goFlags) Int(o string, d int, c string) *int {
65
+	return flag.Int(o, d, c)
66
+}
67
+
68
+func (goFlags) Float64(o string, d float64, c string) *float64 {
69
+	return flag.Float64(o, d, c)
70
+}
71
+
72
+func (goFlags) String(o, d, c string) *string {
73
+	return flag.String(o, d, c)
74
+}
75
+
76
+func (goFlags) BoolVar(b *bool, o string, d bool, c string) {
77
+	flag.BoolVar(b, o, d, c)
78
+}
79
+
80
+func (goFlags) IntVar(i *int, o string, d int, c string) {
81
+	flag.IntVar(i, o, d, c)
82
+}
83
+
84
+func (goFlags) Float64Var(f *float64, o string, d float64, c string) {
85
+	flag.Float64Var(f, o, d, c)
86
+}
87
+
88
+func (goFlags) StringVar(s *string, o, d, c string) {
89
+	flag.StringVar(s, o, d, c)
90
+}
91
+
92
+func (goFlags) StringList(o, d, c string) *[]*string {
93
+	return &[]*string{flag.String(o, d, c)}
94
+}
95
+
96
+func (goFlags) ExtraUsage() string {
97
+	return ""
98
+}
99
+
100
+func (goFlags) Parse(usage func()) []string {
101
+	flag.Usage = usage
102
+	flag.Parse()
103
+	args := flag.Args()
104
+	if len(args) == 0 {
105
+		usage()
106
+	}
107
+	return args
108
+}
109
+
110
+type stdUI struct {
111
+	r *bufio.Reader
112
+}
113
+
114
+func (ui *stdUI) ReadLine(prompt string) (string, error) {
115
+	os.Stdout.WriteString(prompt)
116
+	return ui.r.ReadString('\n')
117
+}
118
+
119
+func (ui *stdUI) Print(args ...interface{}) {
120
+	ui.fprint(os.Stderr, args)
121
+}
122
+
123
+func (ui *stdUI) PrintErr(args ...interface{}) {
124
+	ui.fprint(os.Stderr, args)
125
+}
126
+
127
+func (ui *stdUI) IsTerminal() bool {
128
+	return false
129
+}
130
+
131
+func (ui *stdUI) SetAutoComplete(func(string) string) {
132
+}
133
+
134
+func (ui *stdUI) fprint(f *os.File, args []interface{}) {
135
+	text := fmt.Sprint(args...)
136
+	if !strings.HasSuffix(text, "\n") {
137
+		text += "\n"
138
+	}
139
+	f.WriteString(text)
140
+}
141
+
142
+// oswriter implements the Writer interface using a regular file.
143
+type oswriter struct{}
144
+
145
+func (oswriter) Open(name string) (io.WriteCloser, error) {
146
+	f, err := os.Create(name)
147
+	return f, err
148
+}

+ 54
- 0
src/internal/driver/tempfile.go Datei anzeigen

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
+
15
+package driver
16
+
17
+import (
18
+	"fmt"
19
+	"os"
20
+	"path/filepath"
21
+	"sync"
22
+)
23
+
24
+// newTempFile returns an unused filename for output files.
25
+func newTempFile(dir, prefix, suffix string) (*os.File, error) {
26
+	for index := 1; index < 10000; index++ {
27
+		path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
28
+		if _, err := os.Stat(path); err != nil {
29
+			return os.Create(path)
30
+		}
31
+	}
32
+	// Give up
33
+	return nil, fmt.Errorf("could not create file of the form %s%03d%s", prefix, 1, suffix)
34
+}
35
+
36
+var tempFiles []string
37
+var tempFilesMu = sync.Mutex{}
38
+
39
+// deferDeleteTempFile marks a file to be deleted by next call to Cleanup()
40
+func deferDeleteTempFile(path string) {
41
+	tempFilesMu.Lock()
42
+	tempFiles = append(tempFiles, path)
43
+	tempFilesMu.Unlock()
44
+}
45
+
46
+// cleanupTempFiles removes any temporary files selected for deferred cleaning.
47
+func cleanupTempFiles() {
48
+	tempFilesMu.Lock()
49
+	for _, f := range tempFiles {
50
+		os.Remove(f)
51
+	}
52
+	tempFiles = nil
53
+	tempFilesMu.Unlock()
54
+}

BIN
src/internal/driver/testdata/cppbench.cpu Datei anzeigen


+ 55
- 0
src/internal/driver/testdata/cppbench.svg Datei anzeigen

1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+<!-- Generated by graphviz version 2.36.0 (20140111.2315)
5
+ -->
6
+<!-- Title: cppbench_server_main Pages: 1 -->
7
+<svg width="555pt" height="210pt"
8
+ viewBox="0.00 0.00 555.00 210.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
9
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 206)">
10
+<title>cppbench_server_main</title>
11
+<polygon fill="white" stroke="none" points="-4,4 -4,-206 551,-206 551,4 -4,4"/>
12
+<g id="clust1" class="cluster"><title>cluster_L</title>
13
+<polygon fill="none" stroke="black" points="8,-80 8,-194 402,-194 402,-80 8,-80"/>
14
+</g>
15
+<!-- File: cppbench_server_main -->
16
+<g id="node1" class="node"><title>File: cppbench_server_main</title>
17
+<polygon fill="#f8f8f8" stroke="black" points="394.25,-186 15.75,-186 15.75,-88 394.25,-88 394.25,-186"/>
18
+<text text-anchor="start" x="23.5" y="-169.2" font-family="Times,serif" font-size="16.00">File: cppbench_server_main</text>
19
+<text text-anchor="start" x="23.5" y="-151.2" font-family="Times,serif" font-size="16.00">Type: cpu</text>
20
+<text text-anchor="start" x="23.5" y="-133.2" font-family="Times,serif" font-size="16.00">0 of 7120000000ns total (0%)</text>
21
+<text text-anchor="start" x="23.5" y="-115.2" font-family="Times,serif" font-size="16.00">Dropped 56 nodes (cum &lt;= 35600000ns)</text>
22
+<text text-anchor="start" x="23.5" y="-97.2" font-family="Times,serif" font-size="16.00">Showing top 2 nodes out of 38 (cum &gt;= 7070000000ns)</text>
23
+</g>
24
+<!-- N1 -->
25
+<g id="node2" class="node"><title>N1</title>
26
+<g id="a_node2"><a xlink:title="start_thread (7120000000ns)">
27
+<polygon fill="#f8f8f8" stroke="black" points="513,-155 413,-155 413,-119 513,-119 513,-155"/>
28
+<text text-anchor="middle" x="463" y="-139.6" font-family="Times,serif" font-size="8.00">start_thread</text>
29
+<text text-anchor="middle" x="463" y="-130.6" font-family="Times,serif" font-size="8.00">0 of 7120000000ns(100%)</text>
30
+</a>
31
+</g>
32
+</g>
33
+<!-- N2 -->
34
+<g id="node3" class="node"><title>N2</title>
35
+<g id="a_node3"><a xlink:title="RunWorkerLoop (7070000000ns)">
36
+<polygon fill="#f8f8f8" stroke="black" points="515.5,-36 410.5,-36 410.5,-0 515.5,-0 515.5,-36"/>
37
+<text text-anchor="middle" x="463" y="-20.6" font-family="Times,serif" font-size="8.00">RunWorkerLoop</text>
38
+<text text-anchor="middle" x="463" y="-11.6" font-family="Times,serif" font-size="8.00">0 of 7070000000ns(99.30%)</text>
39
+</a>
40
+</g>
41
+</g>
42
+<!-- N1&#45;&gt;N2 -->
43
+<g id="edge1" class="edge"><title>N1&#45;&gt;N2</title>
44
+<g id="a_edge1"><a xlink:title="start_thread ... RunWorkerLoop (7070000000ns)">
45
+<path fill="none" stroke="black" stroke-width="5" stroke-dasharray="1,5" d="M463,-118.987C463,-99.9242 463,-68.7519 463,-46.2768"/>
46
+<polygon fill="black" stroke="black" stroke-width="5" points="467.375,-46.0333 463,-36.0333 458.625,-46.0334 467.375,-46.0333"/>
47
+</a>
48
+</g>
49
+<g id="a_edge1&#45;label"><a xlink:title="start_thread ... RunWorkerLoop (7070000000ns)">
50
+<text text-anchor="middle" x="505" y="-58.3" font-family="Times,serif" font-size="14.00"> 7070000000ns</text>
51
+</a>
52
+</g>
53
+</g>
54
+</g>
55
+</svg>

+ 17
- 0
src/internal/driver/testdata/file1000.src Datei anzeigen

1
+line1
2
+line2
3
+line3
4
+line4
5
+line5
6
+line6
7
+line7
8
+line8
9
+line9
10
+line0
11
+line1
12
+line2
13
+line3
14
+line4
15
+line5
16
+		
17
+

+ 17
- 0
src/internal/driver/testdata/file2000.src Datei anzeigen

1
+line1
2
+line2
3
+line3
4
+line4
5
+line5
6
+line6
7
+line7
8
+line8
9
+line9
10
+line0
11
+line1
12
+line2
13
+line3
14
+line4
15
+line5
16
+		
17
+

+ 17
- 0
src/internal/driver/testdata/file3000.src Datei anzeigen

1
+line1
2
+line2
3
+line3
4
+line4
5
+line5
6
+line6
7
+line7
8
+line8
9
+line9
10
+line0
11
+line1
12
+line2
13
+line3
14
+line4
15
+line5
16
+		
17
+

BIN
src/internal/driver/testdata/go.crc32.cpu Datei anzeigen


+ 10
- 0
src/internal/driver/testdata/pprof.contention.cum.files.dot Datei anzeigen

1
+digraph "." {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lShowing nodes accounting for 149.50ms, 100% of 149.50ms total\l"] }
4
+N1 [label="file3000.src\n32.77ms (21.92%)\nof 149.50ms (100%)" fontsize=20 shape=box tooltip="testdata/file3000.src (149.50ms)" color="#b20000" fillcolor="#edd5d5"]
5
+N2 [label="file1000.src\n51.20ms (34.25%)" fontsize=23 shape=box tooltip="testdata/file1000.src (51.20ms)" color="#b23100" fillcolor="#eddbd5"]
6
+N3 [label="file2000.src\n65.54ms (43.84%)\nof 75.78ms (50.68%)" fontsize=24 shape=box tooltip="testdata/file2000.src (75.78ms)" color="#b22000" fillcolor="#edd9d5"]
7
+N1 -> N3 [label=" 75.78ms" weight=51 penwidth=3 color="#b22000" tooltip="testdata/file3000.src -> testdata/file2000.src (75.78ms)" labeltooltip="testdata/file3000.src -> testdata/file2000.src (75.78ms)"]
8
+N1 -> N2 [label=" 40.96ms" weight=28 penwidth=2 color="#b23900" tooltip="testdata/file3000.src -> testdata/file1000.src (40.96ms)" labeltooltip="testdata/file3000.src -> testdata/file1000.src (40.96ms)"]
9
+N3 -> N2 [label=" 10.24ms" weight=7 color="#b29775" tooltip="testdata/file2000.src -> testdata/file1000.src (10.24ms)" labeltooltip="testdata/file2000.src -> testdata/file1000.src (10.24ms)"]
10
+}

+ 9
- 0
src/internal/driver/testdata/pprof.contention.flat.addresses.dot.focus.ignore Datei anzeigen

1
+digraph "." {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "Build ID: buildid-contention" [shape=box fontsize=16 label="Build ID: buildid-contention\lComment #1\lComment #2\lType: delay\lShowing nodes accounting for 40.96ms, 27.40% of 149.50ms total\l"] }
4
+N1 [label="0000000000001000\nline1000\nfile1000.src:1\n40.96ms (27.40%)" fontsize=24 shape=box tooltip="0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
5
+N2 [label="0000000000003001\nline3000\nfile3000.src:5\n0 of 40.96ms (27.40%)" fontsize=8 shape=box tooltip="0000000000003001 line3000 testdata/file3000.src:5 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
6
+N3 [label="0000000000003001\nline3001\nfile3000.src:3\n0 of 40.96ms (27.40%)" fontsize=8 shape=box tooltip="0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" color="#b23900" fillcolor="#edddd5"]
7
+N2 -> N3 [label=" 40.96ms\n (inline)" weight=28 penwidth=2 color="#b23900" tooltip="0000000000003001 line3000 testdata/file3000.src:5 -> 0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)" labeltooltip="0000000000003001 line3000 testdata/file3000.src:5 -> 0000000000003001 line3001 testdata/file3000.src:3 (40.96ms)"]
8
+N3 -> N1 [label=" 40.96ms" weight=28 penwidth=2 color="#b23900" tooltip="0000000000003001 line3001 testdata/file3000.src:3 -> 0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)" labeltooltip="0000000000003001 line3001 testdata/file3000.src:3 -> 0000000000001000 line1000 testdata/file1000.src:1 (40.96ms)"]
9
+}

+ 77
- 0
src/internal/driver/testdata/pprof.cpu.callgrind Datei anzeigen

1
+events: cpu(ms)
2
+fl=(1) testdata/file1000.src
3
+fn=(1) line1000
4
+1 1100
5
+
6
+fl=(2) testdata/file2000.src
7
+fn=(2) line2001
8
+9 10
9
+cfl=(1)
10
+cfn=(1)
11
+calls=0 1
12
+9 1000
13
+
14
+fl=(3) testdata/file3000.src
15
+fn=(3) line3002
16
+2 10
17
+cfl=(2)
18
+cfn=(4) line2000
19
+calls=0 4
20
+2 1000
21
+
22
+fl=(2)
23
+fn=(4)
24
+4 0
25
+cfl=(2)
26
+cfn=(2)
27
+calls=0 9
28
+4 1010
29
+
30
+fl=(3)
31
+fn=(5) line3000
32
+6 0
33
+cfl=(3)
34
+cfn=(6) line3001
35
+calls=0 5
36
+6 1010
37
+
38
+fl=(3)
39
+fn=(5)
40
+7 0
41
+cfl=(3)
42
+cfn=(3)
43
+calls=0 5
44
+7 10
45
+
46
+fl=(3)
47
+fn=(5)
48
+9 0
49
+cfl=(3)
50
+cfn=(6)
51
+calls=0 8
52
+9 100
53
+
54
+fl=(3)
55
+fn=(6)
56
+5 0
57
+cfl=(3)
58
+cfn=(3)
59
+calls=0 2
60
+5 1010
61
+
62
+fl=(3)
63
+fn=(6)
64
+8 0
65
+cfl=(1)
66
+cfn=(1)
67
+calls=0 1
68
+8 100
69
+
70
+fl=(3)
71
+fn=(3)
72
+5 0
73
+cfl=(2)
74
+cfn=(4)
75
+calls=0 4
76
+5 10
77
+

+ 5
- 0
src/internal/driver/testdata/pprof.cpu.cum.lines.text.hide Datei anzeigen

1
+Showing nodes accounting for 1.11s, 99.11% of 1.12s total
2
+      flat  flat%   sum%        cum   cum%
3
+     1.10s 98.21% 98.21%      1.10s 98.21%  line1000 testdata/file1000.src:1
4
+         0     0% 98.21%      1.01s 90.18%  line2000 testdata/file2000.src:4
5
+     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 testdata/file2000.src:9 (inline)

+ 5
- 0
src/internal/driver/testdata/pprof.cpu.cum.lines.text.show Datei anzeigen

1
+Showing nodes accounting for 1.11s, 99.11% of 1.12s total
2
+      flat  flat%   sum%        cum   cum%
3
+     1.10s 98.21% 98.21%      1.10s 98.21%  line1000 testdata/file1000.src:1
4
+         0     0% 98.21%      1.01s 90.18%  line2000 testdata/file2000.src:4
5
+     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 testdata/file2000.src:9 (inline)

+ 5
- 0
src/internal/driver/testdata/pprof.cpu.cum.lines.topproto.hide Datei anzeigen

1
+Showing nodes accounting for 1s, 100% of 1s total
2
+      flat  flat%   sum%        cum   cum%
3
+        1s   100%   100%         1s   100%  mangled1000 testdata/file1000.src:1
4
+         0     0%   100%          0     0%  mangled2000 testdata/file2000.src:4
5
+         0     0%   100%          0     0%  mangled2001 testdata/file2000.src:9

+ 14
- 0
src/internal/driver/testdata/pprof.cpu.flat.addresses.disasm Datei anzeigen

1
+Total: 1.12s
2
+ROUTINE ======================== line1000
3
+     1.10s      1.10s (flat, cum) 98.21% of Total
4
+     1.10s      1.10s       1000: instruction one
5
+         .          .       1001: instruction two
6
+         .          .       1002: instruction three
7
+         .          .       1003: instruction four
8
+ROUTINE ======================== line3000
9
+      10ms      1.12s (flat, cum)   100% of Total
10
+      10ms      1.01s       3000: instruction one
11
+         .      100ms       3001: instruction two
12
+         .       10ms       3002: instruction three
13
+         .          .       3003: instruction four
14
+         .          .       3004: instruction five

+ 109
- 0
src/internal/driver/testdata/pprof.cpu.flat.addresses.weblist Datei anzeigen

1
+
2
+<!DOCTYPE html>
3
+<html>
4
+<head>
5
+<title>Pprof listing</title>
6
+<style type="text/css">
7
+body {
8
+font-family: sans-serif;
9
+}
10
+h1 {
11
+  font-size: 1.5em;
12
+  margin-bottom: 4px;
13
+}
14
+.legend {
15
+  font-size: 1.25em;
16
+}
17
+.line {
18
+color: #aaaaaa;
19
+}
20
+.nop {
21
+color: #aaaaaa;
22
+}
23
+.unimportant {
24
+color: #cccccc;
25
+}
26
+.disasmloc {
27
+color: #000000;
28
+}
29
+.deadsrc {
30
+cursor: pointer;
31
+}
32
+.deadsrc:hover {
33
+background-color: #eeeeee;
34
+}
35
+.livesrc {
36
+color: #0000ff;
37
+cursor: pointer;
38
+}
39
+.livesrc:hover {
40
+background-color: #eeeeee;
41
+}
42
+.asm {
43
+color: #008800;
44
+display: none;
45
+}
46
+</style>
47
+<script type="text/javascript">
48
+function pprof_toggle_asm(e) {
49
+  var target;
50
+  if (!e) e = window.event;
51
+  if (e.target) target = e.target;
52
+  else if (e.srcElement) target = e.srcElement;
53
+
54
+  if (target) {
55
+    var asm = target.nextSibling;
56
+    if (asm && asm.className == "asm") {
57
+      asm.style.display = (asm.style.display == "block" ? "" : "block");
58
+      e.preventDefault();
59
+      return false;
60
+    }
61
+  }
62
+}
63
+</script>
64
+</head>
65
+<body>
66
+
67
+<div class="legend">File: testbinary<br>
68
+Type: cpu<br>
69
+Duration: 10s, Total samples = 1.12s (11.20%)<br>Total: 1.12s</div><h1>line1000</h1>testdata/file1000.src
70
+<pre onClick="pprof_toggle_asm()">
71
+  Total:       1.10s      1.10s (flat, cum) 98.21%
72
+<span class=line>      1</span> <span class=deadsrc>       1.10s      1.10s line1 </span><span class=asm>               1.10s      1.10s     1000: instruction one                                  <span class=disasmloc>testdata/file1000.src:1</span>
73
+                   .          .     1001: instruction two                                  <span class=disasmloc></span>
74
+                   .          .     1002: instruction three                                <span class=disasmloc></span>
75
+                   .          .     1003: instruction four                                 <span class=disasmloc></span>
76
+</span>
77
+<span class=line>      2</span> <span class=nop>           .          . line2 </span>
78
+<span class=line>      3</span> <span class=nop>           .          . line3 </span>
79
+<span class=line>      4</span> <span class=nop>           .          . line4 </span>
80
+<span class=line>      5</span> <span class=nop>           .          . line5 </span>
81
+<span class=line>      6</span> <span class=nop>           .          . line6 </span>
82
+</pre>
83
+<h1>line3000</h1>testdata/file3000.src
84
+<pre onClick="pprof_toggle_asm()">
85
+  Total:        10ms      1.12s (flat, cum)   100%
86
+<span class=line>      1</span> <span class=nop>           .          . line1 </span>
87
+<span class=line>      2</span> <span class=nop>           .          . line2 </span>
88
+<span class=line>      3</span> <span class=nop>           .          . line3 </span>
89
+<span class=line>      4</span> <span class=nop>           .          . line4 </span>
90
+<span class=line>      5</span> <span class=nop>           .          . line5 </span>
91
+<span class=line>      6</span> <span class=deadsrc>        10ms      1.01s line6 </span><span class=asm>                10ms      1.01s     3000: instruction one                                  <span class=disasmloc>testdata/file3000.src:6</span>
92
+</span>
93
+<span class=line>      7</span> <span class=deadsrc>           .       10ms line7 </span><span class=asm>                   .       10ms     3002: instruction three                                <span class=disasmloc>testdata/file3000.src:7</span>
94
+                   .          .     3003: instruction four                                 <span class=disasmloc></span>
95
+                   .          .     3004: instruction five                                 <span class=disasmloc></span>
96
+</span>
97
+<span class=line>      8</span> <span class=nop>           .          . line8 </span>
98
+<span class=line>      9</span> <span class=deadsrc>           .      100ms line9 </span><span class=asm>                   .      100ms     3001: instruction two                                  <span class=disasmloc>testdata/file3000.src:9</span>
99
+</span>
100
+<span class=line>     10</span> <span class=nop>           .          . line0 </span>
101
+<span class=line>     11</span> <span class=nop>           .          . line1 </span>
102
+<span class=line>     12</span> <span class=nop>           .          . line2 </span>
103
+<span class=line>     13</span> <span class=nop>           .          . line3 </span>
104
+<span class=line>     14</span> <span class=nop>           .          . line4 </span>
105
+</pre>
106
+
107
+</body>
108
+</html>
109
+

+ 20
- 0
src/internal/driver/testdata/pprof.cpu.flat.functions.dot Datei anzeigen

1
+digraph "testbinary" {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "File: testbinary" [shape=box fontsize=16 label="File: testbinary\lType: cpu\lDuration: 10s, Total samples = 1.12s (11.20%)\lShowing nodes accounting for 1.12s, 100% of 1.12s total\l"] }
4
+N1 [label="line1000\nfile1000.src\n1.10s (98.21%)" fontsize=24 shape=box tooltip="line1000 testdata/file1000.src (1.10s)" color="#b20000" fillcolor="#edd5d5"]
5
+N1_0 [label = "key1:tag1\nkey2:tag1" fontsize=8 shape=box3d tooltip="1s"]
6
+N1 -> N1_0 [label=" 1s" weight=100 tooltip="1s" labeltooltip="1s"]
7
+N1_1 [label = "key1:tag2\nkey3:tag2" fontsize=8 shape=box3d tooltip="0.10s"]
8
+N1 -> N1_1 [label=" 0.10s" weight=100 tooltip="0.10s" labeltooltip="0.10s"]
9
+N2 [label="line3000\nfile3000.src\n0 of 1.12s (100%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (1.12s)" color="#b20000" fillcolor="#edd5d5"]
10
+N3 [label="line3001\nfile3000.src\n0 of 1.11s (99.11%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (1.11s)" color="#b20000" fillcolor="#edd5d5"]
11
+N4 [label="line3002\nfile3000.src\n0.01s (0.89%)\nof 1.02s (91.07%)" fontsize=10 shape=box tooltip="line3002 testdata/file3000.src (1.02s)" color="#b20400" fillcolor="#edd6d5"]
12
+N5 [label="line2001\nfile2000.src\n0.01s (0.89%)\nof 1.01s (90.18%)" fontsize=10 shape=box tooltip="line2001 testdata/file2000.src (1.01s)" color="#b20500" fillcolor="#edd6d5"]
13
+N6 [label="line2000\nfile2000.src\n0 of 1.01s (90.18%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (1.01s)" color="#b20500" fillcolor="#edd6d5"]
14
+N2 -> N3 [label=" 1.11s\n (inline)" weight=100 penwidth=5 color="#b20000" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (1.11s)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (1.11s)"]
15
+N6 -> N5 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (1.01s)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (1.01s)"]
16
+N3 -> N4 [label=" 1.01s\n (inline)" weight=91 penwidth=5 color="#b20500" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (1.01s)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (1.01s)"]
17
+N4 -> N6 [label=" 1.01s" weight=91 penwidth=5 color="#b20500" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (1.01s)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (1.01s)"]
18
+N5 -> N1 [label=" 1s" weight=90 penwidth=5 color="#b20500" tooltip="line2001 testdata/file2000.src -> line1000 testdata/file1000.src (1s)" labeltooltip="line2001 testdata/file2000.src -> line1000 testdata/file1000.src (1s)"]
19
+N3 -> N1 [label=" 0.10s" weight=9 color="#b28b62" tooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (0.10s)" labeltooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (0.10s)"]
20
+}

+ 8
- 0
src/internal/driver/testdata/pprof.cpu.flat.functions.text Datei anzeigen

1
+Showing nodes accounting for 1.12s, 100% of 1.12s total
2
+      flat  flat%   sum%        cum   cum%
3
+     1.10s 98.21% 98.21%      1.10s 98.21%  line1000 testdata/file1000.src
4
+     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 testdata/file2000.src (inline)
5
+     0.01s  0.89%   100%      1.02s 91.07%  line3002 testdata/file3000.src (inline)
6
+         0     0%   100%      1.01s 90.18%  line2000 testdata/file2000.src
7
+         0     0%   100%      1.12s   100%  line3000 testdata/file3000.src
8
+         0     0%   100%      1.11s 99.11%  line3001 testdata/file3000.src (inline)

+ 13
- 0
src/internal/driver/testdata/pprof.cpu.peek Datei anzeigen

1
+Showing nodes accounting for 1.12s, 100% of 1.12s total
2
+----------------------------------------------------------+-------------
3
+      flat  flat%   sum%        cum   cum%   calls calls% + context 	 	 
4
+----------------------------------------------------------+-------------
5
+                                             1.01s   100% |   line2000 testdata/file2000.src (inline)
6
+     0.01s  0.89%  0.89%      1.01s 90.18%                | line2001 testdata/file2000.src
7
+                                                1s 99.01% |   line1000 testdata/file1000.src
8
+----------------------------------------------------------+-------------
9
+                                             1.11s   100% |   line3000 testdata/file3000.src (inline)
10
+         0     0%  0.89%      1.11s 99.11%                | line3001 testdata/file3000.src
11
+                                             1.01s 90.99% |   line3002 testdata/file3000.src (inline)
12
+                                             0.10s  9.01% |   line1000 testdata/file1000.src
13
+----------------------------------------------------------+-------------

+ 54
- 0
src/internal/driver/testdata/pprof.cpu.svg Datei anzeigen

1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
3
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
4
+<!-- Generated by graphviz version 2.36.0 (20140111.2315)
5
+ -->
6
+<!-- Title: cppbench_server_main Pages: 1 -->
7
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
8
+<script></script><g id="viewport" transform="scale(0.5,0.5) translate(0,0)"><g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 206)">
9
+<title>cppbench_server_main</title>
10
+<polygon fill="white" stroke="none" points="-4,4 -4,-206 551,-206 551,4 -4,4"/>
11
+<g id="clust1" class="cluster"><title>cluster_L</title>
12
+<polygon fill="none" stroke="black" points="8,-80 8,-194 402,-194 402,-80 8,-80"/>
13
+</g>
14
+<!-- File: cppbench_server_main -->
15
+<g id="node1" class="node"><title>File: cppbench_server_main</title>
16
+<polygon fill="#f8f8f8" stroke="black" points="394.25,-186 15.75,-186 15.75,-88 394.25,-88 394.25,-186"/>
17
+<text text-anchor="start" x="23.5" y="-169.2" font-family="Times,serif" font-size="16.00">File: cppbench_server_main</text>
18
+<text text-anchor="start" x="23.5" y="-151.2" font-family="Times,serif" font-size="16.00">Type: cpu</text>
19
+<text text-anchor="start" x="23.5" y="-133.2" font-family="Times,serif" font-size="16.00">0 of 7120000000ns total (0%)</text>
20
+<text text-anchor="start" x="23.5" y="-115.2" font-family="Times,serif" font-size="16.00">Dropped 56 nodes (cum &lt;= 35600000ns)</text>
21
+<text text-anchor="start" x="23.5" y="-97.2" font-family="Times,serif" font-size="16.00">Showing top 2 nodes out of 38 (cum &gt;= 7070000000ns)</text>
22
+</g>
23
+<!-- N1 -->
24
+<g id="node2" class="node"><title>N1</title>
25
+<g id="a_node2"><a xlink:title="start_thread (7120000000ns)">
26
+<polygon fill="#f8f8f8" stroke="black" points="513,-155 413,-155 413,-119 513,-119 513,-155"/>
27
+<text text-anchor="middle" x="463" y="-139.6" font-family="Times,serif" font-size="8.00">start_thread</text>
28
+<text text-anchor="middle" x="463" y="-130.6" font-family="Times,serif" font-size="8.00">0 of 7120000000ns(100%)</text>
29
+</a>
30
+</g>
31
+</g>
32
+<!-- N2 -->
33
+<g id="node3" class="node"><title>N2</title>
34
+<g id="a_node3"><a xlink:title="RunWorkerLoop (7070000000ns)">
35
+<polygon fill="#f8f8f8" stroke="black" points="515.5,-36 410.5,-36 410.5,-0 515.5,-0 515.5,-36"/>
36
+<text text-anchor="middle" x="463" y="-20.6" font-family="Times,serif" font-size="8.00">RunWorkerLoop</text>
37
+<text text-anchor="middle" x="463" y="-11.6" font-family="Times,serif" font-size="8.00">0 of 7070000000ns(99.30%)</text>
38
+</a>
39
+</g>
40
+</g>
41
+<!-- N1&#45;&gt;N2 -->
42
+<g id="edge1" class="edge"><title>N1&#45;&gt;N2</title>
43
+<g id="a_edge1"><a xlink:title="start_thread ... RunWorkerLoop (7070000000ns)">
44
+<path fill="none" stroke="black" stroke-width="5" stroke-dasharray="1,5" d="M463,-118.987C463,-99.9242 463,-68.7519 463,-46.2768"/>
45
+<polygon fill="black" stroke="black" stroke-width="5" points="467.375,-46.0333 463,-36.0333 458.625,-46.0334 467.375,-46.0333"/>
46
+</a>
47
+</g>
48
+<g id="a_edge1&#45;label"><a xlink:title="start_thread ... RunWorkerLoop (7070000000ns)">
49
+<text text-anchor="middle" x="505" y="-58.3" font-family="Times,serif" font-size="14.00"> 7070000000ns</text>
50
+</a>
51
+</g>
52
+</g>
53
+</g>
54
+</g></svg>

+ 13
- 0
src/internal/driver/testdata/pprof.cpu.tags Datei anzeigen

1
+key1: Total 1120
2
+      1000 (89.29%): tag1
3
+       100 ( 8.93%): tag2
4
+        10 ( 0.89%): tag3
5
+        10 ( 0.89%): tag4
6
+
7
+key2: Total 1020
8
+      1010 (99.02%): tag1
9
+        10 ( 0.98%): tag2
10
+
11
+key3: Total 100
12
+       100 (  100%): tag2
13
+

+ 6
- 0
src/internal/driver/testdata/pprof.cpu.tags.focus.ignore Datei anzeigen

1
+key1: Total 100
2
+       100 (  100%): tag2
3
+
4
+key3: Total 100
5
+       100 (  100%): tag2
6
+

+ 32
- 0
src/internal/driver/testdata/pprof.cpu.traces Datei anzeigen

1
+File: testbinary
2
+Type: cpu
3
+Duration: 10s, Total samples = 1.12s (11.20%)
4
+-----------+-------------------------------------------------------
5
+      key1:  tag1
6
+      key2:  tag1
7
+        1s   line1000 testdata/file1000.src
8
+             line2001 testdata/file2000.src
9
+             line2000 testdata/file2000.src
10
+             line3002 testdata/file3000.src
11
+             line3001 testdata/file3000.src
12
+             line3000 testdata/file3000.src
13
+-----------+-------------------------------------------------------
14
+      key1:  tag2
15
+      key3:  tag2
16
+     100ms   line1000 testdata/file1000.src
17
+             line3001 testdata/file3000.src
18
+             line3000 testdata/file3000.src
19
+-----------+-------------------------------------------------------
20
+      key1:  tag3
21
+      key2:  tag2
22
+      10ms   line2001 testdata/file2000.src
23
+             line2000 testdata/file2000.src
24
+             line3002 testdata/file3000.src
25
+             line3000 testdata/file3000.src
26
+-----------+-------------------------------------------------------
27
+      key1:  tag4
28
+      key2:  tag1
29
+      10ms   line3002 testdata/file3000.src
30
+             line3001 testdata/file3000.src
31
+             line3000 testdata/file3000.src
32
+-----------+-------------------------------------------------------

+ 17
- 0
src/internal/driver/testdata/pprof.cpusmall.flat.addresses.tree Datei anzeigen

1
+Showing nodes accounting for 4s, 100% of 4s total
2
+Showing top 4 nodes out of 5 (cum >= 2s)
3
+----------------------------------------------------------+-------------
4
+      flat  flat%   sum%        cum   cum%   calls calls% + context 	 	 
5
+----------------------------------------------------------+-------------
6
+                                                1s   100% |   0000000000003000 [testbinary]
7
+        1s 25.00% 25.00%         1s 25.00%                | 0000000000001000 [testbinary]
8
+----------------------------------------------------------+-------------
9
+        1s 25.00% 50.00%         2s 50.00%                | 0000000000003000 [testbinary]
10
+                                                1s 50.00% |   0000000000001000 [testbinary]
11
+----------------------------------------------------------+-------------
12
+                                                1s   100% |   0000000000005000 [testbinary]
13
+        1s 25.00% 75.00%         1s 25.00%                | 0000000000004000 [testbinary]
14
+----------------------------------------------------------+-------------
15
+        1s 25.00%   100%         2s 50.00%                | 0000000000005000 [testbinary]
16
+                                                1s 50.00% |   0000000000004000 [testbinary]
17
+----------------------------------------------------------+-------------

+ 53
- 0
src/internal/driver/testdata/pprof.heap.callgrind Datei anzeigen

1
+events: inuse_space(MB)
2
+fl=(1) testdata/file2000.src
3
+fn=(1) line2001
4
+2 62
5
+cfl=(2) testdata/file1000.src
6
+cfn=(2) line1000
7
+calls=0 1
8
+2 0
9
+
10
+fl=(3) testdata/file3000.src
11
+fn=(3) line3002
12
+3 31
13
+cfl=(1)
14
+cfn=(4) line2000
15
+calls=0 3
16
+3 63
17
+
18
+fl=(2)
19
+fn=(2)
20
+1 4
21
+
22
+fl=(1)
23
+fn=(4)
24
+3 0
25
+cfl=(1)
26
+cfn=(1)
27
+calls=0 2
28
+3 63
29
+
30
+fl=(3)
31
+fn=(5) line3000
32
+4 0
33
+cfl=(3)
34
+cfn=(3)
35
+calls=0 3
36
+4 62
37
+cfl=(3)
38
+cfn=(6) line3001
39
+calls=0 2
40
+4 36
41
+
42
+fl=(3)
43
+fn=(6)
44
+2 0
45
+cfl=(3)
46
+cfn=(3)
47
+calls=0 3
48
+2 32
49
+cfl=(2)
50
+cfn=(2)
51
+calls=0 1
52
+2 3
53
+

+ 19
- 0
src/internal/driver/testdata/pprof.heap.cum.lines.tree.focus Datei anzeigen

1
+Showing nodes accounting for 62.50MB, 63.37% of 98.63MB total
2
+Dropped 2 nodes (cum <= 4.93MB)
3
+----------------------------------------------------------+-------------
4
+      flat  flat%   sum%        cum   cum%   calls calls% + context 	 	 
5
+----------------------------------------------------------+-------------
6
+                                           63.48MB   100% |   line3002 testdata/file3000.src:3
7
+         0     0%     0%    63.48MB 64.36%                | line2000 testdata/file2000.src:3
8
+                                           63.48MB   100% |   line2001 testdata/file2000.src:2 (inline)
9
+----------------------------------------------------------+-------------
10
+                                           63.48MB   100% |   line2000 testdata/file2000.src:3 (inline)
11
+   62.50MB 63.37% 63.37%    63.48MB 64.36%                | line2001 testdata/file2000.src:2
12
+----------------------------------------------------------+-------------
13
+         0     0% 63.37%    63.48MB 64.36%                | line3000 testdata/file3000.src:4
14
+                                           63.48MB   100% |   line3002 testdata/file3000.src:3 (inline)
15
+----------------------------------------------------------+-------------
16
+                                           63.48MB   100% |   line3000 testdata/file3000.src:4 (inline)
17
+         0     0% 63.37%    63.48MB 64.36%                | line3002 testdata/file3000.src:3
18
+                                           63.48MB   100% |   line2000 testdata/file2000.src:3
19
+----------------------------------------------------------+-------------

+ 19
- 0
src/internal/driver/testdata/pprof.heap.cum.relative_percentages.tree.focus Datei anzeigen

1
+Showing nodes accounting for 62.50MB, 98.46% of 63.48MB total
2
+Dropped 2 nodes (cum <= 3.17MB)
3
+----------------------------------------------------------+-------------
4
+      flat  flat%   sum%        cum   cum%   calls calls% + context 	 	 
5
+----------------------------------------------------------+-------------
6
+                                           63.48MB   100% |   line3002 testdata/file3000.src
7
+         0     0%     0%    63.48MB   100%                | line2000 testdata/file2000.src
8
+                                           63.48MB   100% |   line2001 testdata/file2000.src (inline)
9
+----------------------------------------------------------+-------------
10
+                                           63.48MB   100% |   line2000 testdata/file2000.src (inline)
11
+   62.50MB 98.46% 98.46%    63.48MB   100%                | line2001 testdata/file2000.src
12
+----------------------------------------------------------+-------------
13
+         0     0% 98.46%    63.48MB   100%                | line3000 testdata/file3000.src
14
+                                           63.48MB   100% |   line3002 testdata/file3000.src (inline)
15
+----------------------------------------------------------+-------------
16
+                                           63.48MB   100% |   line3000 testdata/file3000.src (inline)
17
+         0     0% 98.46%    63.48MB   100%                | line3002 testdata/file3000.src
18
+                                           63.48MB   100% |   line2000 testdata/file2000.src
19
+----------------------------------------------------------+-------------

+ 2
- 0
src/internal/driver/testdata/pprof.heap.flat.files.seconds.text Datei anzeigen

1
+Showing nodes accounting for 0, 0% of 0 total
2
+      flat  flat%   sum%        cum   cum%

+ 5
- 0
src/internal/driver/testdata/pprof.heap.flat.files.text Datei anzeigen

1
+Showing nodes accounting for 93.75MB, 95.05% of 98.63MB total
2
+Dropped 1 node (cum <= 4.93MB)
3
+      flat  flat%   sum%        cum   cum%
4
+   62.50MB 63.37% 63.37%    63.48MB 64.36%  testdata/file2000.src
5
+   31.25MB 31.68% 95.05%    98.63MB   100%  testdata/file3000.src

+ 8
- 0
src/internal/driver/testdata/pprof.heap.flat.inuse_objects.text Datei anzeigen

1
+Showing nodes accounting for 150, 100% of 150 total
2
+      flat  flat%   sum%        cum   cum%
3
+        80 53.33% 53.33%        130 86.67%  line3002 testdata/file3000.src (inline)
4
+        40 26.67% 80.00%         50 33.33%  line2001 testdata/file2000.src (inline)
5
+        30 20.00%   100%         30 20.00%  line1000 testdata/file1000.src
6
+         0     0%   100%         50 33.33%  line2000 testdata/file2000.src
7
+         0     0%   100%        150   100%  line3000 testdata/file3000.src
8
+         0     0%   100%        110 73.33%  line3001 testdata/file3000.src (inline)

+ 13
- 0
src/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus Datei anzeigen

1
+digraph "." {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lType: inuse_space\lShowing nodes accounting for 62.50MB, 63.37% of 98.63MB total\l"] }
4
+N1 [label="line2001\nfile2000.src\n62.50MB (63.37%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
5
+NN1_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"]
6
+N1 -> NN1_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
7
+N2 [label="line3000\nfile3000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
8
+N3 [label="line2000\nfile2000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
9
+N4 [label="line3002\nfile3000.src\n0 of 62.50MB (63.37%)" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src (62.50MB)" color="#b21600" fillcolor="#edd8d5"]
10
+N3 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (62.50MB)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (62.50MB)"]
11
+N2 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)" labeltooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)"]
12
+N4 -> N3 [label=" 62.50MB" weight=64 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (62.50MB)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (62.50MB)"]
13
+}

+ 15
- 0
src/internal/driver/testdata/pprof.heap.flat.inuse_space.dot.focus.ignore Datei anzeigen

1
+digraph "." {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lType: inuse_space\lShowing nodes accounting for 36.13MB, 36.63% of 98.63MB total\lDropped 2 nodes (cum <= 4.93MB)\l"] }
4
+N1 [label="line3002\nfile3000.src\n31.25MB (31.68%)\nof 32.23MB (32.67%)" fontsize=24 shape=box tooltip="line3002 testdata/file3000.src (32.23MB)" color="#b23200" fillcolor="#eddcd5"]
5
+NN1_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"]
6
+N1 -> NN1_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
7
+N2 [label="line3000\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
8
+N3 [label="line3001\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
9
+N4 [label="line1000\nfile1000.src\n4.88MB (4.95%)" fontsize=15 shape=box tooltip="line1000 testdata/file1000.src (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
10
+NN4_0 [label = "200kB" fontsize=8 shape=box3d tooltip="3.91MB"]
11
+N4 -> NN4_0 [label=" 3.91MB" weight=100 tooltip="3.91MB" labeltooltip="3.91MB"]
12
+N2 -> N3 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)"]
13
+N3 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)"]
14
+N3 -> N4 [label=" 3.91MB" weight=4 color="#b2a58f" tooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (3.91MB)" labeltooltip="line3001 testdata/file3000.src -> line1000 testdata/file1000.src (3.91MB)"]
15
+}

+ 21
- 0
src/internal/driver/testdata/pprof.heap.flat.lines.dot.focus Datei anzeigen

1
+digraph "." {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lType: inuse_space\lShowing nodes accounting for 67.38MB, 68.32% of 98.63MB total\l"] }
4
+N1 [label="line3000\nfile3000.src:4\n0 of 67.38MB (68.32%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src:4 (67.38MB)" color="#b21300" fillcolor="#edd7d5"]
5
+N2 [label="line2001\nfile2000.src:2\n62.50MB (63.37%)\nof 63.48MB (64.36%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src:2 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
6
+NN2_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"]
7
+N2 -> NN2_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
8
+N3 [label="line1000\nfile1000.src:1\n4.88MB (4.95%)" fontsize=13 shape=box tooltip="line1000 testdata/file1000.src:1 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
9
+NN3_0 [label = "200kB" fontsize=8 shape=box3d tooltip="3.91MB"]
10
+N3 -> NN3_0 [label=" 3.91MB" weight=100 tooltip="3.91MB" labeltooltip="3.91MB"]
11
+N4 [label="line3002\nfile3000.src:3\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line3002 testdata/file3000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
12
+N5 [label="line3001\nfile3000.src:2\n0 of 4.88MB (4.95%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src:2 (4.88MB)" color="#b2a086" fillcolor="#edeae7"]
13
+N6 [label="line2000\nfile2000.src:3\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src:3 (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
14
+N6 -> N2 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src:3 -> line2001 testdata/file2000.src:2 (63.48MB)" labeltooltip="line2000 testdata/file2000.src:3 -> line2001 testdata/file2000.src:2 (63.48MB)"]
15
+N4 -> N6 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src:3 -> line2000 testdata/file2000.src:3 (63.48MB)" labeltooltip="line3002 testdata/file3000.src:3 -> line2000 testdata/file2000.src:3 (63.48MB)"]
16
+N1 -> N4 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src:4 -> line3002 testdata/file3000.src:3 (62.50MB)" labeltooltip="line3000 testdata/file3000.src:4 -> line3002 testdata/file3000.src:3 (62.50MB)"]
17
+N1 -> N5 [label=" 4.88MB\n (inline)" weight=5 color="#b2a086" tooltip="line3000 testdata/file3000.src:4 -> line3001 testdata/file3000.src:2 (4.88MB)" labeltooltip="line3000 testdata/file3000.src:4 -> line3001 testdata/file3000.src:2 (4.88MB)"]
18
+N5 -> N3 [label=" 3.91MB" weight=4 color="#b2a58f" tooltip="line3001 testdata/file3000.src:2 -> line1000 testdata/file1000.src:1 (3.91MB)" labeltooltip="line3001 testdata/file3000.src:2 -> line1000 testdata/file1000.src:1 (3.91MB)"]
19
+N2 -> N3 [label=" 0.98MB" color="#b2b0a9" tooltip="line2001 testdata/file2000.src:2 -> line1000 testdata/file1000.src:1 (0.98MB)" labeltooltip="line2001 testdata/file2000.src:2 -> line1000 testdata/file1000.src:1 (0.98MB)" minlen=2]
20
+N5 -> N4 [label=" 0.98MB\n (inline)" color="#b2b0a9" tooltip="line3001 testdata/file3000.src:2 -> line3002 testdata/file3000.src:3 (0.98MB)" labeltooltip="line3001 testdata/file3000.src:2 -> line3002 testdata/file3000.src:3 (0.98MB)"]
21
+}

+ 8
- 0
src/internal/driver/testdata/pprof.heap_alloc.flat.alloc_objects.text Datei anzeigen

1
+Showing nodes accounting for 150, 100% of 150 total
2
+      flat  flat%   sum%        cum   cum%
3
+        80 53.33% 53.33%        130 86.67%  line3002 testdata/file3000.src (inline)
4
+        40 26.67% 80.00%         50 33.33%  line2001 testdata/file2000.src (inline)
5
+        30 20.00%   100%         30 20.00%  line1000 testdata/file1000.src
6
+         0     0%   100%         50 33.33%  line2000 testdata/file2000.src
7
+         0     0%   100%        150   100%  line3000 testdata/file3000.src
8
+         0     0%   100%        110 73.33%  line3001 testdata/file3000.src (inline)

+ 18
- 0
src/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.focus Datei anzeigen

1
+digraph "." {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lType: alloc_space\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
4
+N1 [label="line3002\nfile3000.src\n31.25MB (31.68%)\nof 94.73MB (96.04%)" fontsize=20 shape=box tooltip="line3002 testdata/file3000.src (94.73MB)" color="#b20200" fillcolor="#edd5d5"]
5
+NN1_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"]
6
+N1 -> NN1_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
7
+N2 [label="line3000\nfile3000.src\n0 of 98.63MB (100%)" fontsize=8 shape=box tooltip="line3000 testdata/file3000.src (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
8
+N3 [label="line2001\nfile2000.src\n62.50MB (63.37%)\nof 63.48MB (64.36%)" fontsize=24 shape=box tooltip="line2001 testdata/file2000.src (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
9
+NN3_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"]
10
+N3 -> NN3_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
11
+N4 [label="line2000\nfile2000.src\n0 of 63.48MB (64.36%)" fontsize=8 shape=box tooltip="line2000 testdata/file2000.src (63.48MB)" color="#b21600" fillcolor="#edd8d5"]
12
+N5 [label="line3001\nfile3000.src\n0 of 36.13MB (36.63%)" fontsize=8 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
13
+N4 -> N3 [label=" 63.48MB\n (inline)" weight=65 penwidth=4 color="#b21600" tooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (63.48MB)" labeltooltip="line2000 testdata/file2000.src -> line2001 testdata/file2000.src (63.48MB)"]
14
+N1 -> N4 [label=" 63.48MB" weight=65 penwidth=4 color="#b21600" tooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (63.48MB)" labeltooltip="line3002 testdata/file3000.src -> line2000 testdata/file2000.src (63.48MB)" minlen=2]
15
+N2 -> N1 [label=" 62.50MB\n (inline)" weight=64 penwidth=4 color="#b21600" tooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)" labeltooltip="line3000 testdata/file3000.src -> line3002 testdata/file3000.src (62.50MB)"]
16
+N2 -> N5 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)"]
17
+N5 -> N1 [label=" 32.23MB\n (inline)" weight=33 penwidth=2 color="#b23200" tooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)" labeltooltip="line3001 testdata/file3000.src -> line3002 testdata/file3000.src (32.23MB)"]
18
+}

+ 11
- 0
src/internal/driver/testdata/pprof.heap_alloc.flat.alloc_space.dot.hide Datei anzeigen

1
+digraph "." {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "Build ID: buildid" [shape=box fontsize=16 label="Build ID: buildid\lType: alloc_space\lShowing nodes accounting for 93.75MB, 95.05% of 98.63MB total\lDropped 1 node (cum <= 4.93MB)\l"] }
4
+N1 [label="line3000\nfile3000.src\n62.50MB (63.37%)\nof 98.63MB (100%)" fontsize=24 shape=box tooltip="line3000 testdata/file3000.src (98.63MB)" color="#b20000" fillcolor="#edd5d5"]
5
+NN1_0 [label = "1.56MB" fontsize=8 shape=box3d tooltip="62.50MB"]
6
+N1 -> NN1_0 [label=" 62.50MB" weight=100 tooltip="62.50MB" labeltooltip="62.50MB"]
7
+N2 [label="line3001\nfile3000.src\n31.25MB (31.68%)\nof 36.13MB (36.63%)" fontsize=20 shape=box tooltip="line3001 testdata/file3000.src (36.13MB)" color="#b22e00" fillcolor="#eddbd5"]
8
+NN2_0 [label = "400kB" fontsize=8 shape=box3d tooltip="31.25MB"]
9
+N2 -> NN2_0 [label=" 31.25MB" weight=100 tooltip="31.25MB" labeltooltip="31.25MB"]
10
+N1 -> N2 [label=" 36.13MB\n (inline)" weight=37 penwidth=2 color="#b22e00" tooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" labeltooltip="line3000 testdata/file3000.src -> line3001 testdata/file3000.src (36.13MB)" minlen=2]
11
+}

+ 8
- 0
src/internal/driver/testdata/pprof.unknown.flat.functions.text Datei anzeigen

1
+Showing nodes accounting for 1.12s, 100% of 1.12s total
2
+      flat  flat%   sum%        cum   cum%
3
+     1.10s 98.21% 98.21%      1.10s 98.21%  line1000 testdata/file1000.src
4
+     0.01s  0.89% 99.11%      1.01s 90.18%  line2001 testdata/file2000.src (inline)
5
+     0.01s  0.89%   100%      1.02s 91.07%  line3002 testdata/file3000.src (inline)
6
+         0     0%   100%      1.01s 90.18%  line2000 testdata/file2000.src
7
+         0     0%   100%      1.12s   100%  line3000 testdata/file3000.src
8
+         0     0%   100%      1.11s 99.11%  line3001 testdata/file3000.src (inline)

+ 85
- 0
src/internal/driver/testdata/wrapper/addr2line Datei anzeigen

1
+#!/bin/bash
2
+# Copyright 2014 Google Inc. All Rights Reserved.
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+#     http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS,
12
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+#
16
+# addr2line stub for testing of addr2liner.
17
+# Will recognize (and ignore) the -aiCfej options.
18
+#
19
+# Accepts addresses 1000 to 9000 and output multiple frames of the form:
20
+#   0x9000/fun9000/file9000:9000
21
+#   0x8000/fun8000/file8000:8000
22
+#   0x7000/fun7000/file7000:7000
23
+#   ...
24
+#   0x1000/fun1000/file1000:1000
25
+#
26
+# Returns ??/??/??:0 for all other inputs.
27
+
28
+while getopts aiCfe:j: opt; do
29
+  case "$opt" in
30
+    a|i|C|f|e|j) ;;
31
+    *)
32
+      echo "unrecognized option: $1" >&2
33
+      exit 1
34
+  esac
35
+done
36
+
37
+while read input
38
+do
39
+  address="$input"
40
+  
41
+  # remove 0x from input.
42
+  case "${address}" in
43
+    0x*)
44
+      address=$(printf '%x' "$address")
45
+      ;;
46
+    *)
47
+      address=$(printf '%x' "0x$address")
48
+  esac
49
+
50
+  printf '0x%x\n' "0x$address"
51
+  loop=1
52
+  while [ $loop -eq 1 ]
53
+  do
54
+    # prepare default output.
55
+    output2="fun${address}"
56
+    output3="file${address}:${address}"
57
+
58
+    # specialize output for selected cases.
59
+	  case "${address}" in
60
+      1000)
61
+        output2="_Z3fooid.clone2"
62
+        loop=0
63
+        ;;
64
+      2000)
65
+        output2="_ZNSaIiEC1Ev.clone18"
66
+        address=1000
67
+        ;;
68
+      3000)
69
+        output2="_ZNSt6vectorIS_IS_IiSaIiEESaIS1_EESaIS3_EEixEm"
70
+        address=2000
71
+        ;;
72
+      [4-9]000)
73
+        address=$(expr ${address} - 1000)
74
+        ;;
75
+      *)
76
+        output2='??'
77
+        output3='??:0'
78
+        loop=0
79
+    esac
80
+
81
+    echo "$output2"
82
+    echo "$output3"
83
+  done
84
+done
85
+exit 0

+ 29
- 0
src/internal/driver/testdata/wrapper/dot Datei anzeigen

1
+#!/bin/bash
2
+# Copyright 2014 Google Inc. All Rights Reserved.
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+#     http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS,
12
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+
16
+case "$1" in
17
+  "-Tsvg" )
18
+	  if ! grep -q 'Type: cpu.*Duration: 10s' ; then
19
+	    echo "Couldn't recognize dot input" >&2
20
+      exit 1
21
+    fi
22
+	  cat testdata/cppbench.svg
23
+    exit 0
24
+    ;;
25
+  *       )
26
+    echo "Unexpected argument $1" >&2
27
+    exit 1
28
+    ;;
29
+esac

+ 62
- 0
src/internal/driver/testdata/wrapper/nm Datei anzeigen

1
+#!/bin/bash
2
+# Copyright 2014 Google Inc. All Rights Reserved.
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+#     http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS,
12
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+#
16
+# nm stub for testing of listing.
17
+# Will recognize (and ignore) the -nC options.
18
+#
19
+# Outputs fixed nm output.
20
+
21
+while getopts nC opt; do
22
+  case "$opt" in
23
+    n) ;;
24
+    C) demangle=1;;
25
+    *)
26
+      echo "unrecognized option: $1" >&2
27
+      exit 1
28
+  esac
29
+done
30
+
31
+if [ $demangle ] 
32
+then
33
+  cat <<EOF
34
+0000000000001000 t lineA001
35
+0000000000001000 t lineA002
36
+0000000000001000 t line1000
37
+0000000000002000 t line200A
38
+0000000000002000 t line2000
39
+0000000000002000 t line200B
40
+0000000000003000 t line3000
41
+0000000000003000 t Dumb::operator()(char const*) const
42
+0000000000003000 t lineB00C
43
+0000000000003000 t line300D
44
+0000000000004000 t _the_end
45
+EOF
46
+  exit 0
47
+fi
48
+
49
+cat <<EOF
50
+0000000000001000 t lineA001
51
+0000000000001000 t lineA002
52
+0000000000001000 t line1000
53
+0000000000002000 t line200A
54
+0000000000002000 t line2000
55
+0000000000002000 t line200B
56
+0000000000003000 t line3000
57
+0000000000003000 t _ZNK4DumbclEPKc
58
+0000000000003000 t lineB00C
59
+0000000000003000 t line300D
60
+0000000000004000 t _the_end
61
+EOF
62
+exit 0

+ 59
- 0
src/internal/driver/testdata/wrapper/objdump Datei anzeigen

1
+#!/bin/bash
2
+# Copyright 2014 Google Inc. All Rights Reserved.
3
+#
4
+# Licensed under the Apache License, Version 2.0 (the "License");
5
+# you may not use this file except in compliance with the License.
6
+# You may obtain a copy of the License at
7
+#
8
+#     http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+# Unless required by applicable law or agreed to in writing, software
11
+# distributed under the License is distributed on an "AS IS" BASIS,
12
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+# See the License for the specific language governing permissions and
14
+# limitations under the License.
15
+#
16
+# objdump stub for testing of listing.
17
+# Will recognize (and ignore) the -nC options.
18
+#
19
+# Outputs fixed nm output.
20
+
21
+START=0
22
+STOP=0
23
+while [ $# -gt 1 ] ; do 
24
+ case "$1" in 
25
+   --start-address=*) START=$(echo $1 | sed 's/.*=//') ;;
26
+   --stop-address=*) STOP=$(echo $1 | sed 's/.*=//') ;; 
27
+   --no-show-raw-insn|-d|-C|-n|-l) ;;
28
+   *) echo "Unrecognized option $1"
29
+     exit 1
30
+ esac
31
+ shift
32
+done
33
+
34
+case "$START$STOP" in 
35
+  "0x10000x1fff")
36
+  cat <<EOF
37
+  1000: instruction one
38
+  1001: instruction two
39
+  1002: instruction three
40
+  1003: instruction four
41
+EOF
42
+  ;;
43
+  "0x20000x2fff")
44
+  cat <<EOF
45
+  2000: instruction one
46
+  2001: instruction two
47
+EOF
48
+  ;;
49
+  "0x30000x3fff")
50
+  cat <<EOF
51
+  3000: instruction one
52
+  3001: instruction two
53
+  3002: instruction three
54
+  3003: instruction four
55
+  3004: instruction five
56
+EOF
57
+  ;;
58
+esac
59
+exit 0

+ 248
- 0
src/internal/elfexec/elfexec.go Datei anzeigen

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
+
15
+// Package elfexec provides utility routines to examine ELF binaries.
16
+package elfexec
17
+
18
+import (
19
+	"bufio"
20
+	"debug/elf"
21
+	"encoding/binary"
22
+	"fmt"
23
+	"io"
24
+)
25
+
26
+const (
27
+	maxNoteSize        = 1 << 20 // in bytes
28
+	noteTypeGNUBuildID = 3
29
+)
30
+
31
+// elfNote is the payload of a Note Section in an ELF file.
32
+type elfNote struct {
33
+	Name string // Contents of the "name" field, omitting the trailing zero byte.
34
+	Desc []byte // Contents of the "desc" field.
35
+	Type uint32 // Contents of the "type" field.
36
+}
37
+
38
+// parseNotes returns the notes from a SHT_NOTE section or PT_NOTE segment.
39
+func parseNotes(reader io.Reader, alignment int, order binary.ByteOrder) ([]elfNote, error) {
40
+	r := bufio.NewReader(reader)
41
+
42
+	// padding returns the number of bytes required to pad the given size to an
43
+	// alignment boundary.
44
+	padding := func(size int) int {
45
+		return ((size + (alignment - 1)) &^ (alignment - 1)) - size
46
+	}
47
+
48
+	var notes []elfNote
49
+	for {
50
+		noteHeader := make([]byte, 12) // 3 4-byte words
51
+		if _, err := io.ReadFull(r, noteHeader); err == io.EOF {
52
+			break
53
+		} else if err != nil {
54
+			return nil, err
55
+		}
56
+		namesz := order.Uint32(noteHeader[0:4])
57
+		descsz := order.Uint32(noteHeader[4:8])
58
+		typ := order.Uint32(noteHeader[8:12])
59
+
60
+		if uint64(namesz) > uint64(maxNoteSize) {
61
+			return nil, fmt.Errorf("note name too long (%d bytes)", namesz)
62
+		}
63
+		var name string
64
+		if namesz > 0 {
65
+			// Documentation differs as to whether namesz is meant to include the
66
+			// trailing zero, but everyone agrees that name is null-terminated.
67
+			// So we'll just determine the actual length after the fact.
68
+			var err error
69
+			name, err = r.ReadString('\x00')
70
+			if err == io.EOF {
71
+				return nil, fmt.Errorf("missing note name (want %d bytes)", namesz)
72
+			} else if err != nil {
73
+				return nil, err
74
+			}
75
+			namesz = uint32(len(name))
76
+			name = name[:len(name)-1]
77
+		}
78
+
79
+		// Drop padding bytes until the desc field.
80
+		for n := padding(len(noteHeader) + int(namesz)); n > 0; n-- {
81
+			if _, err := r.ReadByte(); err == io.EOF {
82
+				return nil, fmt.Errorf(
83
+					"missing %d bytes of padding after note name", n)
84
+			} else if err != nil {
85
+				return nil, err
86
+			}
87
+		}
88
+
89
+		if uint64(descsz) > uint64(maxNoteSize) {
90
+			return nil, fmt.Errorf("note desc too long (%d bytes)", descsz)
91
+		}
92
+		desc := make([]byte, int(descsz))
93
+		if _, err := io.ReadFull(r, desc); err == io.EOF {
94
+			return nil, fmt.Errorf("missing desc (want %d bytes)", len(desc))
95
+		} else if err != nil {
96
+			return nil, err
97
+		}
98
+
99
+		notes = append(notes, elfNote{Name: name, Desc: desc, Type: typ})
100
+
101
+		// Drop padding bytes until the next note or the end of the section,
102
+		// whichever comes first.
103
+		for n := padding(len(desc)); n > 0; n-- {
104
+			if _, err := r.ReadByte(); err == io.EOF {
105
+				// We hit the end of the section before an alignment boundary.
106
+				// This can happen if this section is at the end of the file or the next
107
+				// section has a smaller alignment requirement.
108
+				break
109
+			} else if err != nil {
110
+				return nil, err
111
+			}
112
+		}
113
+	}
114
+	return notes, nil
115
+}
116
+
117
+// GetBuildID returns the GNU build-ID for an ELF binary.
118
+//
119
+// If no build-ID was found but the binary was read without error, it returns
120
+// (nil, nil).
121
+func GetBuildID(binary io.ReaderAt) ([]byte, error) {
122
+	f, err := elf.NewFile(binary)
123
+	if err != nil {
124
+		return nil, err
125
+	}
126
+
127
+	findBuildID := func(notes []elfNote) ([]byte, error) {
128
+		var buildID []byte
129
+		for _, note := range notes {
130
+			if note.Name == "GNU" && note.Type == noteTypeGNUBuildID {
131
+				if buildID == nil {
132
+					buildID = note.Desc
133
+				} else {
134
+					return nil, fmt.Errorf("multiple build ids found, don't know which to use!")
135
+				}
136
+			}
137
+		}
138
+		return buildID, nil
139
+	}
140
+
141
+	for _, p := range f.Progs {
142
+		if p.Type != elf.PT_NOTE {
143
+			continue
144
+		}
145
+		notes, err := parseNotes(p.Open(), int(p.Align), f.ByteOrder)
146
+		if err != nil {
147
+			return nil, err
148
+		}
149
+		if b, err := findBuildID(notes); b != nil || err != nil {
150
+			return b, err
151
+		}
152
+	}
153
+	for _, s := range f.Sections {
154
+		if s.Type != elf.SHT_NOTE {
155
+			continue
156
+		}
157
+		notes, err := parseNotes(s.Open(), int(s.Addralign), f.ByteOrder)
158
+		if err != nil {
159
+			return nil, err
160
+		}
161
+		if b, err := findBuildID(notes); b != nil || err != nil {
162
+			return b, err
163
+		}
164
+	}
165
+	return nil, nil
166
+}
167
+
168
+// GetBase determines the base address to subtract from virtual
169
+// address to get symbol table address.  For an executable, the base
170
+// is 0.  Otherwise, it's a shared library, and the base is the
171
+// address where the mapping starts.  The kernel is special, and may
172
+// use the address of the _stext symbol as the mmap start.  _stext
173
+// offset can be obtained with `nm vmlinux | grep _stext`
174
+func GetBase(fh *elf.FileHeader, loadSegment *elf.ProgHeader, stextOffset *uint64, start, limit, offset uint64) (uint64, error) {
175
+	const pageSize = 4096
176
+
177
+	if start == 0 && offset == 0 &&
178
+		(limit == ^uint64(0) || limit == 0) {
179
+		// Some tools may introduce a fake mapping that spans the entire
180
+		// address space. Assume that the address has already been
181
+		// adjusted, so no additional base adjustment is necessary.
182
+		return 0, nil
183
+	}
184
+
185
+	switch fh.Type {
186
+	case elf.ET_EXEC:
187
+		if loadSegment == nil {
188
+			// Fixed-address executable, no adjustment.
189
+			return 0, nil
190
+		}
191
+		if start == 0 && limit != 0 {
192
+			// ChromeOS remaps its kernel to 0.  Nothing else should come
193
+			// down this path.  Empirical values:
194
+			//       VADDR=0xffffffff80200000
195
+			// stextOffset=0xffffffff80200198
196
+			if stextOffset != nil {
197
+				return -*stextOffset, nil
198
+			}
199
+			return -loadSegment.Vaddr, nil
200
+		}
201
+		if loadSegment.Vaddr-loadSegment.Off == start-offset {
202
+			return offset, nil
203
+		}
204
+		if loadSegment.Vaddr == start-offset {
205
+			return offset, nil
206
+		}
207
+		if start > loadSegment.Vaddr && limit > start && offset == 0 {
208
+			// Some kernels look like:
209
+			//       VADDR=0xffffffff80200000
210
+			// stextOffset=0xffffffff80200198
211
+			//       Start=0xffffffff83200000
212
+			//       Limit=0xffffffff84200000
213
+			//      Offset=0
214
+			// So the base should be:
215
+			if stextOffset != nil && (start%pageSize) == (*stextOffset%pageSize) {
216
+				// perf uses the address of _stext as start.  Some tools may
217
+				// adjust for this before calling GetBase, in which case the the page
218
+				// alignment should be different from that of stextOffset.
219
+				return start - *stextOffset, nil
220
+			}
221
+
222
+			return start - loadSegment.Vaddr, nil
223
+		} else if start < loadSegment.Vaddr && start%pageSize != 0 && stextOffset != nil && *stextOffset%pageSize == start%pageSize {
224
+			// ChromeOS remaps its kernel to 0 + start%pageSize.  Nothing
225
+			// else should come down this path.  Empirical values:
226
+			//       start=0x198 limit=0x2f9fffff offset=0
227
+			//       VADDR=0xffffffff81000000
228
+			// stextOffset=0xffffffff81000198
229
+			return -(*stextOffset - start), nil
230
+		}
231
+
232
+		return 0, fmt.Errorf("Don't know how to handle EXEC segment: %v start=0x%x limit=0x%x offset=0x%x", *loadSegment, start, limit, offset)
233
+	case elf.ET_REL:
234
+		if offset != 0 {
235
+			return 0, fmt.Errorf("Don't know how to handle mapping.Offset")
236
+		}
237
+		return start, nil
238
+	case elf.ET_DYN:
239
+		if offset != 0 {
240
+			return 0, fmt.Errorf("Don't know how to handle mapping.Offset")
241
+		}
242
+		if loadSegment == nil {
243
+			return start, nil
244
+		}
245
+		return start - loadSegment.Vaddr, nil
246
+	}
247
+	return 0, fmt.Errorf("Don't know how to handle FileHeader.Type %v", fh.Type)
248
+}

+ 87
- 0
src/internal/elfexec/elfexec_test.go Datei anzeigen

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
+
15
+package elfexec
16
+
17
+import (
18
+	"debug/elf"
19
+	"testing"
20
+)
21
+
22
+func TestGetBase(t *testing.T) {
23
+
24
+	fhExec := &elf.FileHeader{
25
+		Type: elf.ET_EXEC,
26
+	}
27
+	fhRel := &elf.FileHeader{
28
+		Type: elf.ET_REL,
29
+	}
30
+	fhDyn := &elf.FileHeader{
31
+		Type: elf.ET_DYN,
32
+	}
33
+	lsOffset := &elf.ProgHeader{
34
+		Vaddr: 0x400000,
35
+		Off:   0x200000,
36
+	}
37
+	kernelHeader := &elf.ProgHeader{
38
+		Vaddr: 0xffffffff81000000,
39
+	}
40
+
41
+	testcases := []struct {
42
+		label                string
43
+		fh                   *elf.FileHeader
44
+		loadSegment          *elf.ProgHeader
45
+		stextOffset          *uint64
46
+		start, limit, offset uint64
47
+		want                 uint64
48
+		wanterr              bool
49
+	}{
50
+		{"exec", fhExec, nil, nil, 0x400000, 0, 0, 0, false},
51
+		{"exec offset", fhExec, lsOffset, nil, 0x400000, 0x800000, 0, 0, false},
52
+		{"exec offset 2", fhExec, lsOffset, nil, 0x200000, 0x600000, 0, 0, false},
53
+		{"exec nomap", fhExec, nil, nil, 0, 0, 0, 0, false},
54
+		{"exec kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0xffffffff82000198, 0xffffffff83000198, 0, 0x1000000, false},
55
+		{"exec chromeos kernel", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10197, 0, 0x7efffe68, false},
56
+		{"exec chromeos kernel 2", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0, 0x10198, 0, 0x7efffe68, false},
57
+		{"exec chromeos kernel 3", fhExec, kernelHeader, uint64p(0xffffffff81000198), 0x198, 0x100000, 0, 0x7f000000, false},
58
+		{"exec chromeos kernel 4", fhExec, kernelHeader, uint64p(0xffffffff81200198), 0x198, 0x100000, 0, 0x7ee00000, false},
59
+		{"dyn", fhDyn, nil, nil, 0x200000, 0x300000, 0, 0x200000, false},
60
+		{"dyn offset", fhDyn, lsOffset, nil, 0x0, 0x300000, 0, 0xFFFFFFFFFFC00000, false},
61
+		{"dyn nomap", fhDyn, nil, nil, 0x0, 0x0, 0, 0, false},
62
+		{"rel", fhRel, nil, nil, 0x2000000, 0x3000000, 0, 0x2000000, false},
63
+		{"rel nomap", fhRel, nil, nil, 0x0, ^uint64(0), 0, 0, false},
64
+		{"rel offset", fhRel, nil, nil, 0x100000, 0x200000, 0x1, 0, true},
65
+	}
66
+
67
+	for _, tc := range testcases {
68
+		base, err := GetBase(tc.fh, tc.loadSegment, tc.stextOffset, tc.start, tc.limit, tc.offset)
69
+		if err != nil {
70
+			if !tc.wanterr {
71
+				t.Errorf("%s: want no error, got %v", tc.label, err)
72
+			}
73
+			continue
74
+		}
75
+		if tc.wanterr {
76
+			t.Errorf("%s: want error, got nil", tc.label)
77
+			continue
78
+		}
79
+		if base != tc.want {
80
+			t.Errorf("%s: want %x, got %x", tc.label, tc.want, base)
81
+		}
82
+	}
83
+}
84
+
85
+func uint64p(n uint64) *uint64 {
86
+	return &n
87
+}

+ 469
- 0
src/internal/graph/dotgraph.go Datei anzeigen

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
+
15
+package graph
16
+
17
+import (
18
+	"fmt"
19
+	"io"
20
+	"math"
21
+	"path/filepath"
22
+	"strings"
23
+
24
+	"internal/measurement"
25
+)
26
+
27
+// DotAttributes contains details about the graph itself, giving
28
+// insight into how its elements should be rendered.
29
+type DotAttributes struct {
30
+	Nodes map[*Node]*DotNodeAttributes // A map allowing each Node to have its own visualization option
31
+}
32
+
33
+// DotNodeAttributes contains Node specific visualization options.
34
+type DotNodeAttributes struct {
35
+	Shape       string                 // The optional shape of the node when rendered visually
36
+	Bold        bool                   // If the node should be bold or not
37
+	Peripheries int                    // An optional number of borders to place around a node
38
+	URL         string                 // An optional url link to add to a node
39
+	Formatter   func(*NodeInfo) string // An optional formatter for the node's label
40
+}
41
+
42
+// DotConfig contains attributes about how a graph should be
43
+// constructed and how it should look.
44
+type DotConfig struct {
45
+	Title  string   // The title of the DOT graph
46
+	Labels []string // The labels for the DOT's legend
47
+
48
+	FormatValue func(int64) string // A formatting function for values
49
+	Total       int64              // The total weight of the graph, used to compute percentages
50
+}
51
+
52
+// Compose creates and writes a in the DOT format to the writer, using
53
+// the configurations given.
54
+func ComposeDot(w io.Writer, g *Graph, a *DotAttributes, c *DotConfig) {
55
+	builder := &builder{w, a, c}
56
+
57
+	// Begin constructing DOT by adding a title and legend.
58
+	builder.start()
59
+	defer builder.finish()
60
+	builder.addLegend()
61
+
62
+	if len(g.Nodes) == 0 {
63
+		return
64
+	}
65
+
66
+	// Preprocess graph to get id map and find max flat.
67
+	nodeIDMap := make(map[*Node]int)
68
+	hasNodelets := make(map[*Node]bool)
69
+
70
+	maxFlat := float64(abs64(g.Nodes[0].Flat))
71
+	for i, n := range g.Nodes {
72
+		nodeIDMap[n] = i + 1
73
+		if float64(abs64(n.Flat)) > maxFlat {
74
+			maxFlat = float64(abs64(n.Flat))
75
+		}
76
+	}
77
+
78
+	edges := EdgeMap{}
79
+
80
+	// Add nodes and nodelets to DOT builder.
81
+	for _, n := range g.Nodes {
82
+		builder.addNode(n, nodeIDMap[n], maxFlat)
83
+		hasNodelets[n] = builder.addNodelets(n, nodeIDMap[n])
84
+
85
+		// Collect all edges. Use a fake node to support multiple incoming edges.
86
+		for _, e := range n.Out {
87
+			edges[&Node{}] = e
88
+		}
89
+	}
90
+
91
+	// Add edges to DOT builder. Sort edges by frequency as a hint to the graph layout engine.
92
+	for _, e := range edges.Sort() {
93
+		builder.addEdge(e, nodeIDMap[e.Src], nodeIDMap[e.Dest], hasNodelets[e.Src])
94
+	}
95
+}
96
+
97
+// builder wraps an io.Writer and understands how to compose DOT formatted elements.
98
+type builder struct {
99
+	io.Writer
100
+	attributes *DotAttributes
101
+	config     *DotConfig
102
+}
103
+
104
+// start generates a title and initial node in DOT format.
105
+func (b *builder) start() {
106
+	graphname := "unnamed"
107
+	if b.config.Title != "" {
108
+		graphname = b.config.Title
109
+	}
110
+	fmt.Fprintln(b, `digraph "`+graphname+`" {`)
111
+	fmt.Fprintln(b, `node [style=filled fillcolor="#f8f8f8"]`)
112
+}
113
+
114
+// finish closes the opening curly bracket in the constructed DOT buffer.
115
+func (b *builder) finish() {
116
+	fmt.Fprintln(b, "}")
117
+}
118
+
119
+// addLegend generates a legend in DOT format.
120
+func (b *builder) addLegend() {
121
+	labels := b.config.Labels
122
+	var title string
123
+	if len(labels) > 0 {
124
+		title = labels[0]
125
+	}
126
+	fmt.Fprintf(b, `subgraph cluster_L { "%s" [shape=box fontsize=16 label="%s\l"] }`+"\n", title, strings.Join(labels, `\l`))
127
+}
128
+
129
+// addNode generates a graph node in DOT format.
130
+func (b *builder) addNode(node *Node, nodeID int, maxFlat float64) {
131
+	flat, cum := node.Flat, node.Cum
132
+	attrs := b.attributes.Nodes[node]
133
+
134
+	// Populate label for node.
135
+	var label string
136
+	if attrs != nil && attrs.Formatter != nil {
137
+		label = attrs.Formatter(&node.Info)
138
+	} else {
139
+		label = multilinePrintableName(&node.Info)
140
+	}
141
+
142
+	flatValue := b.config.FormatValue(flat)
143
+	if flat != 0 {
144
+		label = label + fmt.Sprintf(`%s (%s)`,
145
+			flatValue,
146
+			strings.TrimSpace(percentage(flat, b.config.Total)))
147
+	} else {
148
+		label = label + "0"
149
+	}
150
+	cumValue := flatValue
151
+	if cum != flat {
152
+		if flat != 0 {
153
+			label = label + `\n`
154
+		} else {
155
+			label = label + " "
156
+		}
157
+		cumValue = b.config.FormatValue(cum)
158
+		label = label + fmt.Sprintf(`of %s (%s)`,
159
+			cumValue,
160
+			strings.TrimSpace(percentage(cum, b.config.Total)))
161
+	}
162
+
163
+	// Scale font sizes from 8 to 24 based on percentage of flat frequency.
164
+	// Use non linear growth to emphasize the size difference.
165
+	baseFontSize, maxFontGrowth := 8, 16.0
166
+	fontSize := baseFontSize
167
+	if maxFlat != 0 && flat != 0 && float64(abs64(flat)) <= maxFlat {
168
+		fontSize += int(math.Ceil(maxFontGrowth * math.Sqrt(float64(abs64(flat))/maxFlat)))
169
+	}
170
+
171
+	// Determine node shape.
172
+	shape := "box"
173
+	if attrs != nil && attrs.Shape != "" {
174
+		shape = attrs.Shape
175
+	}
176
+
177
+	// Create DOT attribute for node.
178
+	attr := fmt.Sprintf(`label="%s" fontsize=%d shape=%s tooltip="%s (%s)" color="%s" fillcolor="%s"`,
179
+		label, fontSize, shape, node.Info.PrintableName(), cumValue,
180
+		dotColor(float64(node.Cum)/float64(abs64(b.config.Total)), false),
181
+		dotColor(float64(node.Cum)/float64(abs64(b.config.Total)), true))
182
+
183
+	// Add on extra attributes if provided.
184
+	if attrs != nil {
185
+		// Make bold if specified.
186
+		if attrs.Bold {
187
+			attr += ` style="bold,filled"`
188
+		}
189
+
190
+		// Add peripheries if specified.
191
+		if attrs.Peripheries != 0 {
192
+			attr += fmt.Sprintf(` peripheries=%d`, attrs.Peripheries)
193
+		}
194
+
195
+		// Add URL if specified. target="_blank" forces the link to open in a new tab.
196
+		if attrs.URL != "" {
197
+			attr += fmt.Sprintf(` URL="%s" target="_blank"`, attrs.URL)
198
+		}
199
+	}
200
+
201
+	fmt.Fprintf(b, "N%d [%s]\n", nodeID, attr)
202
+}
203
+
204
+// addNodelets generates the DOT boxes for the node tags if they exist.
205
+func (b *builder) addNodelets(node *Node, nodeID int) bool {
206
+	const maxNodelets = 4    // Number of nodelets for alphanumeric labels
207
+	const maxNumNodelets = 4 // Number of nodelets for numeric labels
208
+	var nodelets string
209
+
210
+	// Populate two Tag slices, one for LabelTags and one for NumericTags.
211
+	var ts []*Tag
212
+	lnts := make(map[string][]*Tag, 0)
213
+	for _, t := range node.LabelTags {
214
+		ts = append(ts, t)
215
+	}
216
+	for l, tm := range node.NumericTags {
217
+		for _, t := range tm {
218
+			lnts[l] = append(lnts[l], t)
219
+		}
220
+	}
221
+
222
+	// For leaf nodes, print cumulative tags (includes weight from
223
+	// children that have been deleted).
224
+	// For internal nodes, print only flat tags.
225
+	flatTags := len(node.Out) > 0
226
+
227
+	// Select the top maxNodelets alphanumeric labels by weight.
228
+	SortTags(ts, flatTags)
229
+	if len(ts) > maxNodelets {
230
+		ts = ts[:maxNodelets]
231
+	}
232
+	for i, t := range ts {
233
+		w := t.Cum
234
+		if flatTags {
235
+			w = t.Flat
236
+		}
237
+		if w == 0 {
238
+			continue
239
+		}
240
+		weight := b.config.FormatValue(w)
241
+		nodelets += fmt.Sprintf(`N%d_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", nodeID, i, t.Name, weight)
242
+		nodelets += fmt.Sprintf(`N%d -> N%d_%d [label=" %s" weight=100 tooltip="%s" labeltooltip="%s"]`+"\n", nodeID, nodeID, i, weight, weight, weight)
243
+		if nts := lnts[t.Name]; nts != nil {
244
+			nodelets += b.numericNodelets(nts, maxNumNodelets, flatTags, fmt.Sprintf(`N%d_%d`, nodeID, i))
245
+		}
246
+	}
247
+
248
+	if nts := lnts[""]; nts != nil {
249
+		nodelets += b.numericNodelets(nts, maxNumNodelets, flatTags, fmt.Sprintf(`N%d`, nodeID))
250
+	}
251
+
252
+	fmt.Fprint(b, nodelets)
253
+	return nodelets != ""
254
+}
255
+
256
+func (b *builder) numericNodelets(nts []*Tag, maxNumNodelets int, flatTags bool, source string) string {
257
+	nodelets := ""
258
+
259
+	// Collapse numeric labels into maxNumNodelets buckets, of the form:
260
+	// 1MB..2MB, 3MB..5MB, ...
261
+	for j, t := range collapsedTags(nts, maxNumNodelets, flatTags) {
262
+		w, attr := t.Cum, ` style="dotted"`
263
+		if flatTags || t.Flat == t.Cum {
264
+			w, attr = t.Flat, ""
265
+		}
266
+		if w != 0 {
267
+			weight := b.config.FormatValue(w)
268
+			nodelets += fmt.Sprintf(`N%s_%d [label = "%s" fontsize=8 shape=box3d tooltip="%s"]`+"\n", source, j, t.Name, weight)
269
+			nodelets += fmt.Sprintf(`%s -> N%s_%d [label=" %s" weight=100 tooltip="%s" labeltooltip="%s"%s]`+"\n", source, source, j, weight, weight, weight, attr)
270
+		}
271
+	}
272
+	return nodelets
273
+}
274
+
275
+// addEdge generates a graph edge in DOT format.
276
+func (b *builder) addEdge(edge *Edge, from, to int, hasNodelets bool) {
277
+	var inline string
278
+	if edge.Inline {
279
+		inline = `\n (inline)`
280
+	}
281
+	w := b.config.FormatValue(edge.Weight)
282
+	attr := fmt.Sprintf(`label=" %s%s"`, w, inline)
283
+	if b.config.Total != 0 {
284
+		// Note: edge.weight > b.config.Total is possible for profile diffs.
285
+		if weight := 1 + int(min64(abs64(edge.Weight*100/b.config.Total), 100)); weight > 1 {
286
+			attr = fmt.Sprintf(`%s weight=%d`, attr, weight)
287
+		}
288
+		if width := 1 + int(min64(abs64(edge.Weight*5/b.config.Total), 5)); width > 1 {
289
+			attr = fmt.Sprintf(`%s penwidth=%d`, attr, width)
290
+		}
291
+		attr = fmt.Sprintf(`%s color="%s"`, attr,
292
+			dotColor(float64(edge.Weight)/float64(abs64(b.config.Total)), false))
293
+	}
294
+	arrow := "->"
295
+	if edge.Residual {
296
+		arrow = "..."
297
+	}
298
+	tooltip := fmt.Sprintf(`"%s %s %s (%s)"`,
299
+		edge.Src.Info.PrintableName(), arrow, edge.Dest.Info.PrintableName(), w)
300
+	attr = fmt.Sprintf(`%s tooltip=%s labeltooltip=%s`, attr, tooltip, tooltip)
301
+
302
+	if edge.Residual {
303
+		attr = attr + ` style="dotted"`
304
+	}
305
+
306
+	if hasNodelets {
307
+		// Separate children further if source has tags.
308
+		attr = attr + " minlen=2"
309
+	}
310
+
311
+	fmt.Fprintf(b, "N%d -> N%d [%s]\n", from, to, attr)
312
+}
313
+
314
+// dotColor returns a color for the given score (between -1.0 and
315
+// 1.0), with -1.0 colored red, 0.0 colored grey, and 1.0 colored
316
+// green.  If isBackground is true, then a light (low-saturation)
317
+// color is returned (suitable for use as a background color);
318
+// otherwise, a darker color is returned (suitable for use as a
319
+// foreground color).
320
+func dotColor(score float64, isBackground bool) string {
321
+	// A float between 0.0 and 1.0, indicating the extent to which
322
+	// colors should be shifted away from grey (to make positive and
323
+	// negative values easier to distinguish, and to make more use of
324
+	// the color range.)
325
+	const shift = 0.7
326
+
327
+	// Saturation and value (in hsv colorspace) for background colors.
328
+	const bgSaturation = 0.1
329
+	const bgValue = 0.93
330
+
331
+	// Saturation and value (in hsv colorspace) for foreground colors.
332
+	const fgSaturation = 1.0
333
+	const fgValue = 0.7
334
+
335
+	// Choose saturation and value based on isBackground.
336
+	var saturation float64
337
+	var value float64
338
+	if isBackground {
339
+		saturation = bgSaturation
340
+		value = bgValue
341
+	} else {
342
+		saturation = fgSaturation
343
+		value = fgValue
344
+	}
345
+
346
+	// Limit the score values to the range [-1.0, 1.0].
347
+	score = math.Max(-1.0, math.Min(1.0, score))
348
+
349
+	// Reduce saturation near score=0 (so it is colored grey, rather than yellow).
350
+	if math.Abs(score) < 0.2 {
351
+		saturation *= math.Abs(score) / 0.2
352
+	}
353
+
354
+	// Apply 'shift' to move scores away from 0.0 (grey).
355
+	if score > 0.0 {
356
+		score = math.Pow(score, (1.0 - shift))
357
+	}
358
+	if score < 0.0 {
359
+		score = -math.Pow(-score, (1.0 - shift))
360
+	}
361
+
362
+	var r, g, b float64 // red, green, blue
363
+	if score < 0.0 {
364
+		g = value
365
+		r = value * (1 + saturation*score)
366
+	} else {
367
+		r = value
368
+		g = value * (1 - saturation*score)
369
+	}
370
+	b = value * (1 - saturation)
371
+	return fmt.Sprintf("#%02x%02x%02x", uint8(r*255.0), uint8(g*255.0), uint8(b*255.0))
372
+}
373
+
374
+// percentage computes the percentage of total of a value, and encodes
375
+// it as a string. At least two digits of precision are printed.
376
+func percentage(value, total int64) string {
377
+	var ratio float64
378
+	if total != 0 {
379
+		ratio = math.Abs(float64(value)/float64(total)) * 100
380
+	}
381
+	switch {
382
+	case math.Abs(ratio) >= 99.95 && math.Abs(ratio) <= 100.05:
383
+		return "  100%"
384
+	case math.Abs(ratio) >= 1.0:
385
+		return fmt.Sprintf("%5.2f%%", ratio)
386
+	default:
387
+		return fmt.Sprintf("%5.2g%%", ratio)
388
+	}
389
+}
390
+
391
+func multilinePrintableName(info *NodeInfo) string {
392
+	infoCopy := *info
393
+	infoCopy.Name = strings.Replace(infoCopy.Name, "::", `\n`, -1)
394
+	infoCopy.Name = strings.Replace(infoCopy.Name, ".", `\n`, -1)
395
+	if infoCopy.File != "" {
396
+		infoCopy.File = filepath.Base(infoCopy.File)
397
+	}
398
+	return strings.Join(infoCopy.NameComponents(), `\n`) + `\n`
399
+}
400
+
401
+// collapsedTags trims and sorts a slice of tags.
402
+func collapsedTags(ts []*Tag, count int, flatTags bool) []*Tag {
403
+	ts = SortTags(ts, flatTags)
404
+	if len(ts) <= count {
405
+		return ts
406
+	}
407
+
408
+	tagGroups := make([][]*Tag, count)
409
+	for i, t := range (ts)[:count] {
410
+		tagGroups[i] = []*Tag{t}
411
+	}
412
+	for _, t := range (ts)[count:] {
413
+		g, d := 0, tagDistance(t, tagGroups[0][0])
414
+		for i := 1; i < count; i++ {
415
+			if nd := tagDistance(t, tagGroups[i][0]); nd < d {
416
+				g, d = i, nd
417
+			}
418
+		}
419
+		tagGroups[g] = append(tagGroups[g], t)
420
+	}
421
+
422
+	var nts []*Tag
423
+	for _, g := range tagGroups {
424
+		l, w, c := tagGroupLabel(g)
425
+		nts = append(nts, &Tag{
426
+			Name: l,
427
+			Flat: w,
428
+			Cum:  c,
429
+		})
430
+	}
431
+	return SortTags(nts, flatTags)
432
+}
433
+
434
+func tagDistance(t, u *Tag) float64 {
435
+	v, _ := measurement.Scale(u.Value, u.Unit, t.Unit)
436
+	if v < float64(t.Value) {
437
+		return float64(t.Value) - v
438
+	}
439
+	return v - float64(t.Value)
440
+}
441
+
442
+func tagGroupLabel(g []*Tag) (label string, flat, cum int64) {
443
+	if len(g) == 1 {
444
+		t := g[0]
445
+		return measurement.Label(t.Value, t.Unit), t.Flat, t.Cum
446
+	}
447
+	min := g[0]
448
+	max := g[0]
449
+	f := min.Flat
450
+	c := min.Cum
451
+	for _, t := range g[1:] {
452
+		if v, _ := measurement.Scale(t.Value, t.Unit, min.Unit); int64(v) < min.Value {
453
+			min = t
454
+		}
455
+		if v, _ := measurement.Scale(t.Value, t.Unit, max.Unit); int64(v) > max.Value {
456
+			max = t
457
+		}
458
+		f += t.Flat
459
+		c += t.Cum
460
+	}
461
+	return measurement.Label(min.Value, min.Unit) + ".." + measurement.Label(max.Value, max.Unit), f, c
462
+}
463
+
464
+func min64(a, b int64) int64 {
465
+	if a < b {
466
+		return a
467
+	}
468
+	return b
469
+}

+ 276
- 0
src/internal/graph/dotgraph_test.go Datei anzeigen

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
+
15
+package graph
16
+
17
+import (
18
+	"bytes"
19
+	"fmt"
20
+	"io/ioutil"
21
+	"reflect"
22
+	"strconv"
23
+	"strings"
24
+	"testing"
25
+
26
+	"internal/proftest"
27
+)
28
+
29
+const path = "testdata/"
30
+
31
+func TestComposeWithStandardGraph(t *testing.T) {
32
+	g := baseGraph()
33
+	a, c := baseAttrsAndConfig()
34
+
35
+	var buf bytes.Buffer
36
+	ComposeDot(&buf, g, a, c)
37
+
38
+	want, err := ioutil.ReadFile(path + "compose1.dot")
39
+	if err != nil {
40
+		t.Fatalf("error reading test file: %v", err)
41
+	}
42
+
43
+	compareGraphs(t, buf.Bytes(), want)
44
+}
45
+
46
+func TestComposeWithNodeAttributesAndZeroFlat(t *testing.T) {
47
+	g := baseGraph()
48
+	a, c := baseAttrsAndConfig()
49
+
50
+	// Set NodeAttributes for Node 1.
51
+	a.Nodes[g.Nodes[0]] = &DotNodeAttributes{
52
+		Shape:       "folder",
53
+		Bold:        true,
54
+		Peripheries: 2,
55
+		URL:         "www.google.com",
56
+		Formatter: func(ni *NodeInfo) string {
57
+			return strings.ToUpper(ni.Name)
58
+		},
59
+	}
60
+
61
+	// Set Flat value to zero on Node 2.
62
+	g.Nodes[1].Flat = 0
63
+
64
+	var buf bytes.Buffer
65
+	ComposeDot(&buf, g, a, c)
66
+
67
+	want, err := ioutil.ReadFile(path + "compose2.dot")
68
+	if err != nil {
69
+		t.Fatalf("error reading test file: %v", err)
70
+	}
71
+
72
+	compareGraphs(t, buf.Bytes(), want)
73
+}
74
+
75
+func TestComposeWithTagsAndResidualEdge(t *testing.T) {
76
+	g := baseGraph()
77
+	a, c := baseAttrsAndConfig()
78
+
79
+	// Add tags to Node 1.
80
+	g.Nodes[0].LabelTags["a"] = &Tag{
81
+		Name: "tag1",
82
+		Cum:  10,
83
+		Flat: 10,
84
+	}
85
+	g.Nodes[0].NumericTags[""] = TagMap{
86
+		"b": &Tag{
87
+			Name: "tag2",
88
+			Cum:  20,
89
+			Flat: 20,
90
+			Unit: "ms",
91
+		},
92
+	}
93
+
94
+	// Set edge to be Residual.
95
+	g.Nodes[0].Out[g.Nodes[1]].Residual = true
96
+
97
+	var buf bytes.Buffer
98
+	ComposeDot(&buf, g, a, c)
99
+
100
+	want, err := ioutil.ReadFile(path + "compose3.dot")
101
+	if err != nil {
102
+		t.Fatalf("error reading test file: %v", err)
103
+	}
104
+
105
+	compareGraphs(t, buf.Bytes(), want)
106
+}
107
+
108
+func TestComposeWithNestedTags(t *testing.T) {
109
+	g := baseGraph()
110
+	a, c := baseAttrsAndConfig()
111
+
112
+	// Add tags to Node 1.
113
+	g.Nodes[0].LabelTags["tag1"] = &Tag{
114
+		Name: "tag1",
115
+		Cum:  10,
116
+		Flat: 10,
117
+	}
118
+	g.Nodes[0].NumericTags["tag1"] = TagMap{
119
+		"tag2": &Tag{
120
+			Name: "tag2",
121
+			Cum:  20,
122
+			Flat: 20,
123
+			Unit: "ms",
124
+		},
125
+	}
126
+
127
+	var buf bytes.Buffer
128
+	ComposeDot(&buf, g, a, c)
129
+
130
+	want, err := ioutil.ReadFile(path + "compose5.dot")
131
+	if err != nil {
132
+		t.Fatalf("error reading test file: %v", err)
133
+	}
134
+
135
+	compareGraphs(t, buf.Bytes(), want)
136
+}
137
+
138
+func TestComposeWithEmptyGraph(t *testing.T) {
139
+	g := &Graph{}
140
+	a, c := baseAttrsAndConfig()
141
+
142
+	var buf bytes.Buffer
143
+	ComposeDot(&buf, g, a, c)
144
+
145
+	want, err := ioutil.ReadFile(path + "compose4.dot")
146
+	if err != nil {
147
+		t.Fatalf("error reading test file: %v", err)
148
+	}
149
+
150
+	compareGraphs(t, buf.Bytes(), want)
151
+}
152
+
153
+func baseGraph() *Graph {
154
+	src := &Node{
155
+		Info:        NodeInfo{Name: "src"},
156
+		Flat:        10,
157
+		Cum:         25,
158
+		In:          make(EdgeMap),
159
+		Out:         make(EdgeMap),
160
+		LabelTags:   make(TagMap),
161
+		NumericTags: make(map[string]TagMap),
162
+	}
163
+	dest := &Node{
164
+		Info:        NodeInfo{Name: "dest"},
165
+		Flat:        15,
166
+		Cum:         25,
167
+		In:          make(EdgeMap),
168
+		Out:         make(EdgeMap),
169
+		LabelTags:   make(TagMap),
170
+		NumericTags: make(map[string]TagMap),
171
+	}
172
+	edge := &Edge{
173
+		Src:    src,
174
+		Dest:   dest,
175
+		Weight: 10,
176
+	}
177
+	src.Out[dest] = edge
178
+	src.In[src] = edge
179
+	return &Graph{
180
+		Nodes: Nodes{
181
+			src,
182
+			dest,
183
+		},
184
+	}
185
+}
186
+
187
+func baseAttrsAndConfig() (*DotAttributes, *DotConfig) {
188
+	a := &DotAttributes{
189
+		Nodes: make(map[*Node]*DotNodeAttributes),
190
+	}
191
+	c := &DotConfig{
192
+		Title:  "testtitle",
193
+		Labels: []string{"label1", "label2"},
194
+		Total:  100,
195
+		FormatValue: func(v int64) string {
196
+			return strconv.FormatInt(v, 10)
197
+		},
198
+	}
199
+	return a, c
200
+}
201
+
202
+func compareGraphs(t *testing.T, got, want []byte) {
203
+	if string(got) != string(want) {
204
+		d, err := proftest.Diff(got, want)
205
+		if err != nil {
206
+			t.Fatalf("error finding diff: %v", err)
207
+		}
208
+		t.Errorf("Compose incorrectly wrote %s", string(d))
209
+	}
210
+}
211
+
212
+func TestMultilinePrintableName(t *testing.T) {
213
+	ni := &NodeInfo{
214
+		Name:    "test1.test2::test3",
215
+		File:    "src/file.cc",
216
+		Address: 123,
217
+		Lineno:  999,
218
+	}
219
+
220
+	want := fmt.Sprintf(`%016x\ntest1\ntest2\ntest3\nfile.cc:999\n`, 123)
221
+	if got := multilinePrintableName(ni); got != want {
222
+		t.Errorf("multilinePrintableName(%#v) == %q, want %q", ni, got, want)
223
+	}
224
+}
225
+
226
+func TestTagCollapse(t *testing.T) {
227
+	tagSource := []*Tag{
228
+		{"12mb", "mb", 12, 100, 100},
229
+		{"1kb", "kb", 1, 1, 1},
230
+		{"1mb", "mb", 1, 1000, 1000},
231
+		{"2048mb", "mb", 2048, 1000, 1000},
232
+		{"1b", "b", 1, 100, 100},
233
+		{"2b", "b", 2, 100, 100},
234
+		{"7b", "b", 7, 100, 100},
235
+	}
236
+
237
+	tagWant := [][]*Tag{
238
+		[]*Tag{
239
+			{"1B..2GB", "", 0, 2401, 2401},
240
+		},
241
+		[]*Tag{
242
+			{"2GB", "", 0, 1000, 1000},
243
+			{"1B..12MB", "", 0, 1401, 1401},
244
+		},
245
+		[]*Tag{
246
+			{"2GB", "", 0, 1000, 1000},
247
+			{"12MB", "", 0, 100, 100},
248
+			{"1B..1MB", "", 0, 1301, 1301},
249
+		},
250
+		[]*Tag{
251
+			{"2GB", "", 0, 1000, 1000},
252
+			{"1MB", "", 0, 1000, 1000},
253
+			{"2B..1kB", "", 0, 201, 201},
254
+			{"1B", "", 0, 100, 100},
255
+			{"12MB", "", 0, 100, 100},
256
+		},
257
+	}
258
+
259
+	for _, tc := range tagWant {
260
+		var got, want []*Tag
261
+		got = collapsedTags(tagSource, len(tc), true)
262
+		want = SortTags(tc, true)
263
+
264
+		if !reflect.DeepEqual(got, want) {
265
+			t.Errorf("collapse to %d, got:\n%v\nwant:\n%v", len(tc), tagString(got), tagString(want))
266
+		}
267
+	}
268
+}
269
+
270
+func tagString(t []*Tag) string {
271
+	var ret []string
272
+	for _, s := range t {
273
+		ret = append(ret, fmt.Sprintln(s))
274
+	}
275
+	return strings.Join(ret, ":")
276
+}

+ 859
- 0
src/internal/graph/graph.go Datei anzeigen

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
+
15
+// Package graph collects a set of samples into a directed graph.
16
+package graph
17
+
18
+import (
19
+	"fmt"
20
+	"math"
21
+	"path/filepath"
22
+	"sort"
23
+	"strings"
24
+
25
+	"profile"
26
+)
27
+
28
+// Graph summarizes a performance profile into a format that is
29
+// suitable for visualization.
30
+type Graph struct {
31
+	Nodes Nodes
32
+}
33
+
34
+// Options encodes the options for constructing a graph
35
+type Options struct {
36
+	SampleValue func(s []int64) int64      // Function to compute the value of a sample
37
+	FormatTag   func(int64, string) string // Function to format a sample tag value into a string
38
+	ObjNames    bool                       // Always preserve obj filename
39
+
40
+	CallTree     bool // Build a tree instead of a graph
41
+	DropNegative bool // Drop nodes with overall negative values
42
+
43
+	KeptNodes NodeSet // If non-nil, only use nodes in this set
44
+}
45
+
46
+// Nodes is an ordered collection of graph nodes.
47
+type Nodes []*Node
48
+
49
+// Node is an entry on a profiling report. It represents a unique
50
+// program location.
51
+type Node struct {
52
+	// Information associated to this entry.
53
+	Info NodeInfo
54
+
55
+	// values associated to this node.
56
+	// Flat is exclusive to this node, cum includes all descendents.
57
+	Flat, Cum int64
58
+
59
+	// in and out contains the nodes immediately reaching or reached by this nodes.
60
+	In, Out EdgeMap
61
+
62
+	// tags provide additional information about subsets of a sample.
63
+	LabelTags TagMap
64
+
65
+	// Numeric tags provide additional values for subsets of a sample.
66
+	// Numeric tags are optionally associated to a label tag. The key
67
+	// for NumericTags is the name of the LabelTag they are associated
68
+	// to, or "" for numeric tags not associated to a label tag.
69
+	NumericTags map[string]TagMap
70
+}
71
+
72
+// BumpWeight increases the weight of an edge between two nodes. If
73
+// there isn't such an edge one is created.
74
+func (n *Node) BumpWeight(to *Node, w int64, residual, inline bool) {
75
+	if n.Out[to] != to.In[n] {
76
+		panic(fmt.Errorf("asymmetric edges %v %v", *n, *to))
77
+	}
78
+
79
+	if e := n.Out[to]; e != nil {
80
+		e.Weight += w
81
+		if residual {
82
+			e.Residual = true
83
+		}
84
+		if !inline {
85
+			e.Inline = false
86
+		}
87
+		return
88
+	}
89
+
90
+	info := &Edge{Src: n, Dest: to, Weight: w, Residual: residual, Inline: inline}
91
+	n.Out[to] = info
92
+	to.In[n] = info
93
+}
94
+
95
+// NodeInfo contains the attributes for a node.
96
+type NodeInfo struct {
97
+	Name              string
98
+	OrigName          string
99
+	Address           uint64
100
+	File              string
101
+	StartLine, Lineno int
102
+	Objfile           string
103
+}
104
+
105
+// PrintableName calls the Node's Formatter function with a single space separator.
106
+func (i *NodeInfo) PrintableName() string {
107
+	return strings.Join(i.NameComponents(), " ")
108
+}
109
+
110
+// NameComponents returns the components of the printable name to be used for a node.
111
+func (i *NodeInfo) NameComponents() []string {
112
+	var name []string
113
+	if i.Address != 0 {
114
+		name = append(name, fmt.Sprintf("%016x", i.Address))
115
+	}
116
+	if fun := i.Name; fun != "" {
117
+		name = append(name, fun)
118
+	}
119
+
120
+	switch {
121
+	case i.Lineno != 0:
122
+		// User requested line numbers, provide what we have.
123
+		name = append(name, fmt.Sprintf("%s:%d", i.File, i.Lineno))
124
+	case i.File != "":
125
+		// User requested file name, provide it.
126
+		name = append(name, i.File)
127
+	case i.Name != "":
128
+		// User requested function name. It was already included.
129
+	case i.Objfile != "":
130
+		// Only binary name is available
131
+		name = append(name, "["+i.Objfile+"]")
132
+	default:
133
+		// Do not leave it empty if there is no information at all.
134
+		name = append(name, "<unknown>")
135
+	}
136
+	return name
137
+}
138
+
139
+// ExtendedNodeInfo extends the NodeInfo with a pointer to a parent node, to
140
+// identify nodes with identical information and different callers. This is
141
+// used when creating call trees.
142
+type ExtendedNodeInfo struct {
143
+	NodeInfo
144
+	parent *Node
145
+}
146
+
147
+// NodeMap maps from a node info struct to a node. It is used to merge
148
+// report entries with the same info.
149
+type NodeMap map[ExtendedNodeInfo]*Node
150
+
151
+// NodeSet maps is a collection of node info structs.
152
+type NodeSet map[NodeInfo]bool
153
+
154
+// FindOrInsertNode takes the info for a node and either returns a matching node
155
+// from the node map if one exists, or adds one to the map if one does not.
156
+// If parent is non-nil, return a match with the same parent.
157
+// If kept is non-nil, nodes are only added if they can be located on it.
158
+func (m NodeMap) FindOrInsertNode(info NodeInfo, parent *Node, kept NodeSet) *Node {
159
+	if kept != nil && !kept[info] {
160
+		return nil
161
+	}
162
+
163
+	extendedInfo := ExtendedNodeInfo{
164
+		info,
165
+		parent,
166
+	}
167
+
168
+	if n := m[extendedInfo]; n != nil {
169
+		return n
170
+	}
171
+
172
+	n := &Node{
173
+		Info:        info,
174
+		In:          make(EdgeMap),
175
+		Out:         make(EdgeMap),
176
+		LabelTags:   make(TagMap),
177
+		NumericTags: make(map[string]TagMap),
178
+	}
179
+	m[extendedInfo] = n
180
+	return n
181
+}
182
+
183
+// EdgeMap is used to represent the incoming/outgoing edges from a node.
184
+type EdgeMap map[*Node]*Edge
185
+
186
+// Edge contains any attributes to be represented about edges in a graph.
187
+type Edge struct {
188
+	Src, Dest *Node
189
+	// The summary weight of the edge
190
+	Weight int64
191
+	// residual edges connect nodes that were connected through a
192
+	// separate node, which has been removed from the report.
193
+	Residual bool
194
+	// An inline edge represents a call that was inlined into the caller.
195
+	Inline bool
196
+}
197
+
198
+// Tag represent sample annotations
199
+type Tag struct {
200
+	Name  string
201
+	Unit  string // Describe the value, "" for non-numeric tags
202
+	Value int64
203
+	Flat  int64
204
+	Cum   int64
205
+}
206
+
207
+// TagMap is a collection of tags, classified by their name.
208
+type TagMap map[string]*Tag
209
+
210
+// SortTags sorts a slice of tags based on their weight.
211
+func SortTags(t []*Tag, flat bool) []*Tag {
212
+	ts := tags{t, flat}
213
+	sort.Sort(ts)
214
+	return ts.t
215
+}
216
+
217
+// New summarizes performance data from a profile into a graph.
218
+func New(prof *profile.Profile, o *Options) (g *Graph) {
219
+	locations := NewLocInfo(prof, o.ObjNames)
220
+	nm := make(NodeMap)
221
+	for _, sample := range prof.Sample {
222
+		if sample.Location == nil {
223
+			continue
224
+		}
225
+
226
+		// Construct list of node names for sample.
227
+		// Keep track of the index on the Sample for each frame,
228
+		// to determine inlining status.
229
+
230
+		var stack []NodeInfo
231
+		var locIndex []int
232
+		for i, loc := range sample.Location {
233
+			id := loc.ID
234
+			stack = append(stack, locations[id]...)
235
+			for _ = range locations[id] {
236
+				locIndex = append(locIndex, i)
237
+			}
238
+		}
239
+
240
+		weight := o.SampleValue(sample.Value)
241
+		seenEdge := make(map[*Node]map[*Node]bool)
242
+		var nn *Node
243
+		nlocIndex := -1
244
+		residual := false
245
+		// Walk top-down over the frames in a sample, keeping track
246
+		// of the current parent if we're building a tree.
247
+		for i := len(stack); i > 0; i-- {
248
+			var parent *Node
249
+			if o.CallTree {
250
+				parent = nn
251
+			}
252
+			n := nm.FindOrInsertNode(stack[i-1], parent, o.KeptNodes)
253
+			if n == nil {
254
+				residual = true
255
+				continue
256
+			}
257
+			// Add flat weight to leaf node.
258
+			if i == 1 {
259
+				n.addSample(sample, weight, o.FormatTag, true)
260
+			}
261
+			// Add cum weight to all nodes in stack, avoiding double counting.
262
+			if seenEdge[n] == nil {
263
+				seenEdge[n] = make(map[*Node]bool)
264
+				n.addSample(sample, weight, o.FormatTag, false)
265
+			}
266
+			// Update edge weights for all edges in stack, avoiding double counting.
267
+			if nn != nil && n != nn && !seenEdge[n][nn] {
268
+				seenEdge[n][nn] = true
269
+				// This is an inlined edge if the caller and the callee
270
+				// correspond to the same entry in the sample.
271
+				nn.BumpWeight(n, weight, residual, locIndex[i-1] == nlocIndex)
272
+			}
273
+			nn = n
274
+			nlocIndex = locIndex[i-1]
275
+			residual = false
276
+		}
277
+	}
278
+
279
+	// Collect nodes into a graph.
280
+	ns := make(Nodes, 0, len(nm))
281
+	for _, n := range nm {
282
+		if o.DropNegative && isNegative(n) {
283
+			continue
284
+		}
285
+		ns = append(ns, n)
286
+	}
287
+
288
+	return &Graph{ns}
289
+}
290
+
291
+// isNegative returns true if the node is considered as "negative" for the
292
+// purposes of drop_negative.
293
+func isNegative(n *Node) bool {
294
+	switch {
295
+	case n.Flat < 0:
296
+		return true
297
+	case n.Flat == 0 && n.Cum < 0:
298
+		return true
299
+	default:
300
+		return false
301
+	}
302
+}
303
+
304
+// NewLocInfo creates a slice of formatted names for a location.
305
+func NewLocInfo(prof *profile.Profile, keepBinary bool) map[uint64][]NodeInfo {
306
+	locations := make(map[uint64][]NodeInfo)
307
+
308
+	for _, l := range prof.Location {
309
+		var objfile string
310
+
311
+		if m := l.Mapping; m != nil {
312
+			objfile = filepath.Base(m.File)
313
+		}
314
+
315
+		if len(l.Line) == 0 {
316
+			locations[l.ID] = []NodeInfo{
317
+				{
318
+					Address: l.Address,
319
+					Objfile: objfile,
320
+				},
321
+			}
322
+			continue
323
+		}
324
+		var info []NodeInfo
325
+		for _, line := range l.Line {
326
+			ni := NodeInfo{
327
+				Address: l.Address,
328
+				Lineno:  int(line.Line),
329
+			}
330
+
331
+			if line.Function != nil {
332
+				ni.Name = line.Function.Name
333
+				ni.OrigName = line.Function.SystemName
334
+				ni.File = line.Function.Filename
335
+				ni.StartLine = int(line.Function.StartLine)
336
+			}
337
+			if keepBinary || line.Function == nil {
338
+				ni.Objfile = objfile
339
+			}
340
+			info = append(info, ni)
341
+		}
342
+		locations[l.ID] = info
343
+	}
344
+	return locations
345
+}
346
+
347
+type tags struct {
348
+	t    []*Tag
349
+	flat bool
350
+}
351
+
352
+func (t tags) Len() int      { return len(t.t) }
353
+func (t tags) Swap(i, j int) { t.t[i], t.t[j] = t.t[j], t.t[i] }
354
+func (t tags) Less(i, j int) bool {
355
+	if !t.flat {
356
+		if t.t[i].Cum != t.t[j].Cum {
357
+			return abs64(t.t[i].Cum) > abs64(t.t[j].Cum)
358
+		}
359
+	}
360
+	if t.t[i].Flat != t.t[j].Flat {
361
+		return abs64(t.t[i].Flat) > abs64(t.t[j].Flat)
362
+	}
363
+	return t.t[i].Name < t.t[j].Name
364
+}
365
+
366
+// Sum adds the Flat and sum values on a report.
367
+func (ns Nodes) Sum() (flat int64, cum int64) {
368
+	for _, n := range ns {
369
+		flat += n.Flat
370
+		cum += n.Cum
371
+	}
372
+	return
373
+}
374
+
375
+func (n *Node) addSample(s *profile.Sample, value int64, format func(int64, string) string, flat bool) {
376
+	// Update sample value
377
+	if flat {
378
+		n.Flat += value
379
+	} else {
380
+		n.Cum += value
381
+	}
382
+
383
+	// Add string tags
384
+	var labels []string
385
+	for key, vals := range s.Label {
386
+		for _, v := range vals {
387
+			labels = append(labels, key+":"+v)
388
+		}
389
+	}
390
+	var joinedLabels string
391
+	if len(labels) > 0 {
392
+		sort.Strings(labels)
393
+		joinedLabels = strings.Join(labels, `\n`)
394
+		t := n.LabelTags.findOrAddTag(joinedLabels, "", 0)
395
+		if flat {
396
+			t.Flat += value
397
+		} else {
398
+			t.Cum += value
399
+		}
400
+	}
401
+
402
+	numericTags := n.NumericTags[joinedLabels]
403
+	if numericTags == nil {
404
+		numericTags = TagMap{}
405
+		n.NumericTags[joinedLabels] = numericTags
406
+	}
407
+	// Add numeric tags
408
+	for key, nvals := range s.NumLabel {
409
+		for _, v := range nvals {
410
+			var label string
411
+			if format != nil {
412
+				label = format(v, key)
413
+			} else {
414
+				label = fmt.Sprintf("%d", v)
415
+			}
416
+			t := numericTags.findOrAddTag(label, key, v)
417
+			if flat {
418
+				t.Flat += value
419
+			} else {
420
+				t.Cum += value
421
+			}
422
+		}
423
+	}
424
+}
425
+
426
+func (m TagMap) findOrAddTag(label, unit string, value int64) *Tag {
427
+	l := m[label]
428
+	if l == nil {
429
+		l = &Tag{
430
+			Name:  label,
431
+			Unit:  unit,
432
+			Value: value,
433
+		}
434
+		m[label] = l
435
+	}
436
+	return l
437
+}
438
+
439
+// String returns a text representation of a graph, for debugging purposes.
440
+func (g *Graph) String() string {
441
+	var s []string
442
+
443
+	nodeIndex := make(map[*Node]int, len(g.Nodes))
444
+
445
+	for i, n := range g.Nodes {
446
+		nodeIndex[n] = i + 1
447
+	}
448
+
449
+	for i, n := range g.Nodes {
450
+		name := n.Info.PrintableName()
451
+		var in, out []int
452
+
453
+		for _, from := range n.In {
454
+			in = append(in, nodeIndex[from.Src])
455
+		}
456
+		for _, to := range n.Out {
457
+			out = append(out, nodeIndex[to.Dest])
458
+		}
459
+		s = append(s, fmt.Sprintf("%d: %s[flat=%d cum=%d] %x -> %v ", i+1, name, n.Flat, n.Cum, in, out))
460
+	}
461
+	return strings.Join(s, "\n")
462
+}
463
+
464
+// DiscardLowFrequencyNodes returns a set of the nodes at or over a
465
+// specific cum value cutoff.
466
+func (g *Graph) DiscardLowFrequencyNodes(nodeCutoff int64) NodeSet {
467
+	return makeNodeSet(g.Nodes, nodeCutoff)
468
+}
469
+
470
+func makeNodeSet(nodes Nodes, nodeCutoff int64) NodeSet {
471
+	kept := make(NodeSet, len(nodes))
472
+	for _, n := range nodes {
473
+		if abs64(n.Cum) < nodeCutoff {
474
+			continue
475
+		}
476
+		kept[n.Info] = true
477
+	}
478
+	return kept
479
+}
480
+
481
+// TrimLowFrequencyTags removes tags that have less than
482
+// the specified weight.
483
+func (g *Graph) TrimLowFrequencyTags(tagCutoff int64) {
484
+	// Remove nodes with value <= total*nodeFraction
485
+	for _, n := range g.Nodes {
486
+		n.LabelTags = trimLowFreqTags(n.LabelTags, tagCutoff)
487
+		for s, nt := range n.NumericTags {
488
+			n.NumericTags[s] = trimLowFreqTags(nt, tagCutoff)
489
+		}
490
+	}
491
+}
492
+
493
+func trimLowFreqTags(tags TagMap, minValue int64) TagMap {
494
+	kept := TagMap{}
495
+	for s, t := range tags {
496
+		if abs64(t.Flat) >= minValue || abs64(t.Cum) >= minValue {
497
+			kept[s] = t
498
+		}
499
+	}
500
+	return kept
501
+}
502
+
503
+// TrimLowFrequencyEdges removes edges that have less than
504
+// the specified weight. Returns the number of edges removed
505
+func (g *Graph) TrimLowFrequencyEdges(edgeCutoff int64) int {
506
+	var droppedEdges int
507
+	for _, n := range g.Nodes {
508
+		for src, e := range n.In {
509
+			if abs64(e.Weight) < edgeCutoff {
510
+				delete(n.In, src)
511
+				delete(src.Out, n)
512
+				droppedEdges++
513
+			}
514
+		}
515
+	}
516
+	return droppedEdges
517
+}
518
+
519
+// SortNodes sorts the nodes in a graph based on a specific heuristic.
520
+func (g *Graph) SortNodes(cum bool, visualMode bool) {
521
+	// Sort nodes based on requested mode
522
+	switch {
523
+	case visualMode:
524
+		// Specialized sort to produce a more visually-interesting graph
525
+		g.Nodes.Sort(EntropyOrder)
526
+	case cum:
527
+		g.Nodes.Sort(CumNameOrder)
528
+	default:
529
+		g.Nodes.Sort(FlatNameOrder)
530
+	}
531
+}
532
+
533
+// SelectTopNodes returns a set of the top maxNodes nodes in a graph.
534
+func (g *Graph) SelectTopNodes(maxNodes int, visualMode bool) NodeSet {
535
+	if maxNodes > 0 {
536
+		if visualMode {
537
+			var count int
538
+			// If generating a visual graph, count tags as nodes. Update
539
+			// maxNodes to account for them.
540
+			for i, n := range g.Nodes {
541
+				if count += countTags(n) + 1; count >= maxNodes {
542
+					maxNodes = i + 1
543
+					break
544
+				}
545
+			}
546
+		}
547
+	}
548
+	if maxNodes > len(g.Nodes) {
549
+		maxNodes = len(g.Nodes)
550
+	}
551
+	return makeNodeSet(g.Nodes[:maxNodes], 0)
552
+}
553
+
554
+// countTags counts the tags with flat count. This underestimates the
555
+// number of tags being displayed, but in practice is close enough.
556
+func countTags(n *Node) int {
557
+	count := 0
558
+	for _, e := range n.LabelTags {
559
+		if e.Flat != 0 {
560
+			count++
561
+		}
562
+	}
563
+	for _, t := range n.NumericTags {
564
+		for _, e := range t {
565
+			if e.Flat != 0 {
566
+				count++
567
+			}
568
+		}
569
+	}
570
+	return count
571
+}
572
+
573
+// countEdges counts the number of edges below the specified cutoff.
574
+func countEdges(el EdgeMap, cutoff int64) int {
575
+	count := 0
576
+	for _, e := range el {
577
+		if e.Weight > cutoff {
578
+			count++
579
+		}
580
+	}
581
+	return count
582
+}
583
+
584
+// RemoveRedundantEdges removes residual edges if the destination can
585
+// be reached through another path. This is done to simplify the graph
586
+// while preserving connectivity.
587
+func (g *Graph) RemoveRedundantEdges() {
588
+	// Walk the nodes and outgoing edges in reverse order to prefer
589
+	// removing edges with the lowest weight.
590
+	for i := len(g.Nodes); i > 0; i-- {
591
+		n := g.Nodes[i-1]
592
+		in := n.In.Sort()
593
+		for j := len(in); j > 0; j-- {
594
+			e := in[j-1]
595
+			if !e.Residual {
596
+				// Do not remove edges heavier than a non-residual edge, to
597
+				// avoid potential confusion.
598
+				break
599
+			}
600
+			if isRedundant(e) {
601
+				delete(e.Src.Out, e.Dest)
602
+				delete(e.Dest.In, e.Src)
603
+			}
604
+		}
605
+	}
606
+}
607
+
608
+// isRedundant determines if an edge can be removed without impacting
609
+// connectivity of the whole graph. This is implemented by checking if the
610
+// nodes have a common ancestor after removing the edge.
611
+func isRedundant(e *Edge) bool {
612
+	destPred := predecessors(e, e.Dest)
613
+	if len(destPred) == 1 {
614
+		return false
615
+	}
616
+	srcPred := predecessors(e, e.Src)
617
+
618
+	for n := range srcPred {
619
+		if destPred[n] && n != e.Dest {
620
+			return true
621
+		}
622
+	}
623
+	return false
624
+}
625
+
626
+// predecessors collects all the predecessors to node n, excluding edge e.
627
+func predecessors(e *Edge, n *Node) map[*Node]bool {
628
+	seen := map[*Node]bool{n: true}
629
+	queue := []*Node{n}
630
+	for len(queue) > 0 {
631
+		n := queue[0]
632
+		queue = queue[1:]
633
+		for _, ie := range n.In {
634
+			if e == ie || seen[ie.Src] {
635
+				continue
636
+			}
637
+			seen[ie.Src] = true
638
+			queue = append(queue, ie.Src)
639
+		}
640
+	}
641
+	return seen
642
+}
643
+
644
+// nodeSorter is a mechanism used to allow a report to be sorted
645
+// in different ways.
646
+type nodeSorter struct {
647
+	rs   Nodes
648
+	less func(l, r *Node) bool
649
+}
650
+
651
+func (s nodeSorter) Len() int           { return len(s.rs) }
652
+func (s nodeSorter) Swap(i, j int)      { s.rs[i], s.rs[j] = s.rs[j], s.rs[i] }
653
+func (s nodeSorter) Less(i, j int) bool { return s.less(s.rs[i], s.rs[j]) }
654
+
655
+// Sort reorders a slice of nodes based on the specified ordering
656
+// criteria. The result is sorted in decreasing order for (absolute)
657
+// numeric quantities, alphabetically for text, and increasing for
658
+// addresses.
659
+func (ns Nodes) Sort(o NodeOrder) error {
660
+	var s nodeSorter
661
+
662
+	switch o {
663
+	case FlatNameOrder:
664
+		s = nodeSorter{ns,
665
+			func(l, r *Node) bool {
666
+				if iv, jv := l.Flat, r.Flat; iv != jv {
667
+					return abs64(iv) > abs64(jv)
668
+				}
669
+				if l.Info.PrintableName() != r.Info.PrintableName() {
670
+					return l.Info.PrintableName() < r.Info.PrintableName()
671
+				}
672
+				iv, jv := l.Cum, r.Cum
673
+				return abs64(iv) > abs64(jv)
674
+			},
675
+		}
676
+	case FlatCumNameOrder:
677
+		s = nodeSorter{ns,
678
+			func(l, r *Node) bool {
679
+				if iv, jv := l.Flat, r.Flat; iv != jv {
680
+					return abs64(iv) > abs64(jv)
681
+				}
682
+				if iv, jv := l.Cum, r.Cum; iv != jv {
683
+					return abs64(iv) > abs64(jv)
684
+				}
685
+				return l.Info.PrintableName() < r.Info.PrintableName()
686
+			},
687
+		}
688
+	case NameOrder:
689
+		s = nodeSorter{ns,
690
+			func(l, r *Node) bool {
691
+				return l.Info.Name < r.Info.Name
692
+			},
693
+		}
694
+	case FileOrder:
695
+		s = nodeSorter{ns,
696
+			func(l, r *Node) bool {
697
+				return l.Info.File < r.Info.File
698
+			},
699
+		}
700
+	case AddressOrder:
701
+		s = nodeSorter{ns,
702
+			func(l, r *Node) bool {
703
+				return l.Info.Address < r.Info.Address
704
+			},
705
+		}
706
+	case CumNameOrder, EntropyOrder:
707
+		// Hold scoring for score-based ordering
708
+		var score map[*Node]int64
709
+		scoreOrder := func(l, r *Node) bool {
710
+			if is, js := score[l], score[r]; is != js {
711
+				return abs64(is) > abs64(js)
712
+			}
713
+			if l.Info.PrintableName() != r.Info.PrintableName() {
714
+				return l.Info.PrintableName() < r.Info.PrintableName()
715
+			}
716
+			return abs64(l.Flat) > abs64(r.Flat)
717
+		}
718
+
719
+		switch o {
720
+		case CumNameOrder:
721
+			score = make(map[*Node]int64, len(ns))
722
+			for _, n := range ns {
723
+				score[n] = n.Cum
724
+			}
725
+			s = nodeSorter{ns, scoreOrder}
726
+		case EntropyOrder:
727
+			score = make(map[*Node]int64, len(ns))
728
+			for _, n := range ns {
729
+				score[n] = entropyScore(n)
730
+			}
731
+			s = nodeSorter{ns, scoreOrder}
732
+		}
733
+	default:
734
+		return fmt.Errorf("report: unrecognized sort ordering: %d", o)
735
+	}
736
+	sort.Sort(s)
737
+	return nil
738
+}
739
+
740
+// entropyScore computes a score for a node representing how important
741
+// it is to include this node on a graph visualization. It is used to
742
+// sort the nodes and select which ones to display if we have more
743
+// nodes than desired in the graph. This number is computed by looking
744
+// at the flat and cum weights of the node and the incoming/outgoing
745
+// edges. The fundamental idea is to penalize nodes that have a simple
746
+// fallthrough from their incoming to the outgoing edge.
747
+func entropyScore(n *Node) int64 {
748
+	score := float64(0)
749
+
750
+	if len(n.In) == 0 {
751
+		score++ // Favor entry nodes
752
+	} else {
753
+		score += edgeEntropyScore(n, n.In, 0)
754
+	}
755
+
756
+	if len(n.Out) == 0 {
757
+		score++ // Favor leaf nodes
758
+	} else {
759
+		score += edgeEntropyScore(n, n.Out, n.Flat)
760
+	}
761
+
762
+	return int64(score*float64(n.Cum)) + n.Flat
763
+}
764
+
765
+// edgeEntropyScore computes the entropy value for a set of edges
766
+// coming in or out of a node. Entropy (as defined in information
767
+// theory) refers to the amount of information encoded by the set of
768
+// edges. A set of edges that have a more interesting distribution of
769
+// samples gets a higher score.
770
+func edgeEntropyScore(n *Node, edges EdgeMap, self int64) float64 {
771
+	score := float64(0)
772
+	total := self
773
+	for _, e := range edges {
774
+		if e.Weight > 0 {
775
+			total += abs64(e.Weight)
776
+		}
777
+	}
778
+	if total != 0 {
779
+		for _, e := range edges {
780
+			frac := float64(abs64(e.Weight)) / float64(total)
781
+			score += -frac * math.Log2(frac)
782
+		}
783
+		if self > 0 {
784
+			frac := float64(abs64(self)) / float64(total)
785
+			score += -frac * math.Log2(frac)
786
+		}
787
+	}
788
+	return score
789
+}
790
+
791
+// NodeOrder sets the ordering for a Sort operation
792
+type NodeOrder int
793
+
794
+// Sorting options for node sort.
795
+const (
796
+	FlatNameOrder NodeOrder = iota
797
+	FlatCumNameOrder
798
+	CumNameOrder
799
+	NameOrder
800
+	FileOrder
801
+	AddressOrder
802
+	EntropyOrder
803
+)
804
+
805
+// Sort returns a slice of the edges in the map, in a consistent
806
+// order. The sort order is first based on the edge weight
807
+// (higher-to-lower) and then by the node names to avoid flakiness.
808
+func (e EdgeMap) Sort() []*Edge {
809
+	el := make(edgeList, 0, len(e))
810
+	for _, w := range e {
811
+		el = append(el, w)
812
+	}
813
+
814
+	sort.Sort(el)
815
+	return el
816
+}
817
+
818
+// Sum returns the total weight for a set of nodes.
819
+func (e EdgeMap) Sum() int64 {
820
+	var ret int64
821
+	for _, edge := range e {
822
+		ret += edge.Weight
823
+	}
824
+	return ret
825
+}
826
+
827
+type edgeList []*Edge
828
+
829
+func (el edgeList) Len() int {
830
+	return len(el)
831
+}
832
+
833
+func (el edgeList) Less(i, j int) bool {
834
+	if el[i].Weight != el[j].Weight {
835
+		return abs64(el[i].Weight) > abs64(el[j].Weight)
836
+	}
837
+
838
+	from1 := el[i].Src.Info.PrintableName()
839
+	from2 := el[j].Src.Info.PrintableName()
840
+	if from1 != from2 {
841
+		return from1 < from2
842
+	}
843
+
844
+	to1 := el[i].Dest.Info.PrintableName()
845
+	to2 := el[j].Dest.Info.PrintableName()
846
+
847
+	return to1 < to2
848
+}
849
+
850
+func (el edgeList) Swap(i, j int) {
851
+	el[i], el[j] = el[j], el[i]
852
+}
853
+
854
+func abs64(i int64) int64 {
855
+	if i < 0 {
856
+		return -i
857
+	}
858
+	return i
859
+}

+ 7
- 0
src/internal/graph/testdata/compose1.dot Datei anzeigen

1
+digraph "testtitle" {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\l"] }
4
+N1 [label="src\n10 (10.00%)\nof 25 (25.00%)" fontsize=22 shape=box tooltip="src (25)" color="#b23c00" fillcolor="#edddd5"]
5
+N2 [label="dest\n15 (15.00%)\nof 25 (25.00%)" fontsize=24 shape=box tooltip="dest (25)" color="#b23c00" fillcolor="#edddd5"]
6
+N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src -> dest (10)" labeltooltip="src -> dest (10)"]
7
+}

+ 7
- 0
src/internal/graph/testdata/compose2.dot Datei anzeigen

1
+digraph "testtitle" {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\l"] }
4
+N1 [label="SRC10 (10.00%)\nof 25 (25.00%)" fontsize=24 shape=folder tooltip="src (25)" color="#b23c00" fillcolor="#edddd5" style="bold,filled" peripheries=2 URL="www.google.com" target="_blank"]
5
+N2 [label="dest\n0 of 25 (25.00%)" fontsize=8 shape=box tooltip="dest (25)" color="#b23c00" fillcolor="#edddd5"]
6
+N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src -> dest (10)" labeltooltip="src -> dest (10)"]
7
+}

+ 11
- 0
src/internal/graph/testdata/compose3.dot Datei anzeigen

1
+digraph "testtitle" {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\l"] }
4
+N1 [label="src\n10 (10.00%)\nof 25 (25.00%)" fontsize=22 shape=box tooltip="src (25)" color="#b23c00" fillcolor="#edddd5"]
5
+N1_0 [label = "tag1" fontsize=8 shape=box3d tooltip="10"]
6
+N1 -> N1_0 [label=" 10" weight=100 tooltip="10" labeltooltip="10"]
7
+NN1_0 [label = "tag2" fontsize=8 shape=box3d tooltip="20"]
8
+N1 -> NN1_0 [label=" 20" weight=100 tooltip="20" labeltooltip="20"]
9
+N2 [label="dest\n15 (15.00%)\nof 25 (25.00%)" fontsize=24 shape=box tooltip="dest (25)" color="#b23c00" fillcolor="#edddd5"]
10
+N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src ... dest (10)" labeltooltip="src ... dest (10)" style="dotted" minlen=2]
11
+}

+ 4
- 0
src/internal/graph/testdata/compose4.dot Datei anzeigen

1
+digraph "testtitle" {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\l"] }
4
+}

+ 11
- 0
src/internal/graph/testdata/compose5.dot Datei anzeigen

1
+digraph "testtitle" {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "label1" [shape=box fontsize=16 label="label1\llabel2\l"] }
4
+N1 [label="src\n10 (10.00%)\nof 25 (25.00%)" fontsize=22 shape=box tooltip="src (25)" color="#b23c00" fillcolor="#edddd5"]
5
+N1_0 [label = "tag1" fontsize=8 shape=box3d tooltip="10"]
6
+N1 -> N1_0 [label=" 10" weight=100 tooltip="10" labeltooltip="10"]
7
+NN1_0_0 [label = "tag2" fontsize=8 shape=box3d tooltip="20"]
8
+N1_0 -> NN1_0_0 [label=" 20" weight=100 tooltip="20" labeltooltip="20"]
9
+N2 [label="dest\n15 (15.00%)\nof 25 (25.00%)" fontsize=24 shape=box tooltip="dest (25)" color="#b23c00" fillcolor="#edddd5"]
10
+N1 -> N2 [label=" 10" weight=11 color="#b28559" tooltip="src -> dest (10)" labeltooltip="src -> dest (10)" minlen=2]
11
+}

+ 299
- 0
src/internal/measurement/measurement.go Datei anzeigen

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
+
15
+// Package measurement export utility functions to manipulate/format performance profile sample values.
16
+package measurement
17
+
18
+import (
19
+	"fmt"
20
+	"strings"
21
+	"time"
22
+
23
+	"profile"
24
+)
25
+
26
+// ScaleProfiles updates the units in a set of profiles to make them
27
+// compatible. It scales the profiles to the smallest unit to preserve
28
+// data.
29
+func ScaleProfiles(profiles []*profile.Profile) error {
30
+	if len(profiles) == 0 {
31
+		return nil
32
+	}
33
+	periodTypes := make([]*profile.ValueType, 0, len(profiles))
34
+	for _, p := range profiles {
35
+		if p.PeriodType != nil {
36
+			periodTypes = append(periodTypes, p.PeriodType)
37
+		}
38
+	}
39
+	periodType, err := CommonValueType(periodTypes)
40
+	if err != nil {
41
+		return fmt.Errorf("period type: %v", err)
42
+	}
43
+
44
+	// Identify common sample types
45
+	numSampleTypes := len(profiles[0].SampleType)
46
+	for _, p := range profiles[1:] {
47
+		if numSampleTypes != len(p.SampleType) {
48
+			return fmt.Errorf("inconsistent samples type count: %d != %d", numSampleTypes, len(p.SampleType))
49
+		}
50
+	}
51
+	sampleType := make([]*profile.ValueType, numSampleTypes)
52
+	for i := 0; i < numSampleTypes; i++ {
53
+		sampleTypes := make([]*profile.ValueType, len(profiles))
54
+		for j, p := range profiles {
55
+			sampleTypes[j] = p.SampleType[i]
56
+		}
57
+		sampleType[i], err = CommonValueType(sampleTypes)
58
+		if err != nil {
59
+			return fmt.Errorf("sample types: %v", err)
60
+		}
61
+	}
62
+
63
+	for _, p := range profiles {
64
+		if p.PeriodType != nil && periodType != nil {
65
+			period, _ := Scale(p.Period, p.PeriodType.Unit, periodType.Unit)
66
+			p.Period, p.PeriodType.Unit = int64(period), periodType.Unit
67
+		}
68
+		ratios := make([]float64, len(p.SampleType))
69
+		for i, st := range p.SampleType {
70
+			if sampleType[i] == nil {
71
+				ratios[i] = 1
72
+				continue
73
+			}
74
+			ratios[i], _ = Scale(1, st.Unit, sampleType[i].Unit)
75
+			p.SampleType[i].Unit = sampleType[i].Unit
76
+		}
77
+		if err := p.ScaleN(ratios); err != nil {
78
+			return fmt.Errorf("scale: %v", err)
79
+		}
80
+	}
81
+	return nil
82
+}
83
+
84
+// CommonValueType returns the finest type from a set of compatible
85
+// types.
86
+func CommonValueType(ts []*profile.ValueType) (*profile.ValueType, error) {
87
+	if len(ts) <= 1 {
88
+		return nil, nil
89
+	}
90
+	minType := ts[0]
91
+	for _, t := range ts[1:] {
92
+		if !compatibleValueTypes(minType, t) {
93
+			return nil, fmt.Errorf("incompatible types: %v %v", *minType, *t)
94
+		}
95
+		if ratio, _ := Scale(1, t.Unit, minType.Unit); ratio < 1 {
96
+			minType = t
97
+		}
98
+	}
99
+	rcopy := *minType
100
+	return &rcopy, nil
101
+}
102
+
103
+func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
104
+	if v1 == nil || v2 == nil {
105
+		return true // No grounds to disqualify.
106
+	}
107
+	// Remove trailing 's' to permit minor mismatches.
108
+	if t1, t2 := strings.TrimSuffix(v1.Type, "s"), strings.TrimSuffix(v2.Type, "s"); t1 != t2 {
109
+		return false
110
+	}
111
+
112
+	return v1.Unit == v2.Unit ||
113
+		(isTimeUnit(v1.Unit) && isTimeUnit(v2.Unit)) ||
114
+		(isMemoryUnit(v1.Unit) && isMemoryUnit(v2.Unit))
115
+}
116
+
117
+// Scale a measurement from an unit to a different unit and returns
118
+// the scaled value and the target unit.  The returned target unit
119
+// will be empty if uninteresting (could be skipped).
120
+func Scale(value int64, fromUnit, toUnit string) (float64, string) {
121
+	// Avoid infinite recursion on overflow.
122
+	if value < 0 && -value > 0 {
123
+		v, u := Scale(-value, fromUnit, toUnit)
124
+		return -v, u
125
+	}
126
+	if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok {
127
+		return m, u
128
+	}
129
+	if t, u, ok := timeLabel(value, fromUnit, toUnit); ok {
130
+		return t, u
131
+	}
132
+	// Skip non-interesting units.
133
+	switch toUnit {
134
+	case "count", "sample", "unit", "minimum", "auto":
135
+		return float64(value), ""
136
+	default:
137
+		return float64(value), toUnit
138
+	}
139
+}
140
+
141
+// Label returns the label used to describe a certain measurement.
142
+func Label(value int64, unit string) string {
143
+	return ScaledLabel(value, unit, "auto")
144
+}
145
+
146
+// ScaledLabel scales the passed-in measurement (if necessary) and
147
+// returns the label used to describe a float measurement.
148
+func ScaledLabel(value int64, fromUnit, toUnit string) string {
149
+	v, u := Scale(value, fromUnit, toUnit)
150
+	sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
151
+	if sv == "0" || sv == "-0" {
152
+		return "0"
153
+	}
154
+	return sv + u
155
+}
156
+
157
+// isMemoryUnit returns whether a name is recognized as a memory size
158
+// unit.
159
+func isMemoryUnit(unit string) bool {
160
+	switch strings.TrimSuffix(strings.ToLower(unit), "s") {
161
+	case "byte", "b", "kilobyte", "kb", "megabyte", "mb", "gigabyte", "gb":
162
+		return true
163
+	}
164
+	return false
165
+}
166
+
167
+func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
168
+	fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s")
169
+	toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s")
170
+
171
+	switch fromUnit {
172
+	case "byte", "b":
173
+	case "kilobyte", "kb":
174
+		value *= 1024
175
+	case "megabyte", "mb":
176
+		value *= 1024 * 1024
177
+	case "gigabyte", "gb":
178
+		value *= 1024 * 1024 * 1024
179
+	default:
180
+		return 0, "", false
181
+	}
182
+
183
+	if toUnit == "minimum" || toUnit == "auto" {
184
+		switch {
185
+		case value < 1024:
186
+			toUnit = "b"
187
+		case value < 1024*1024:
188
+			toUnit = "kb"
189
+		case value < 1024*1024*1024:
190
+			toUnit = "mb"
191
+		default:
192
+			toUnit = "gb"
193
+		}
194
+	}
195
+
196
+	var output float64
197
+	switch toUnit {
198
+	default:
199
+		output, toUnit = float64(value), "B"
200
+	case "kb", "kbyte", "kilobyte":
201
+		output, toUnit = float64(value)/1024, "kB"
202
+	case "mb", "mbyte", "megabyte":
203
+		output, toUnit = float64(value)/(1024*1024), "MB"
204
+	case "gb", "gbyte", "gigabyte":
205
+		output, toUnit = float64(value)/(1024*1024*1024), "GB"
206
+	}
207
+	return output, toUnit, true
208
+}
209
+
210
+// isTimeUnit returns whether a name is recognized as a time unit.
211
+func isTimeUnit(unit string) bool {
212
+	unit = strings.ToLower(unit)
213
+	if len(unit) > 2 {
214
+		unit = strings.TrimSuffix(unit, "s")
215
+	}
216
+
217
+	switch unit {
218
+	case "nanosecond", "ns", "microsecond", "millisecond", "ms", "s", "second", "sec", "hr", "day", "week", "year":
219
+		return true
220
+	}
221
+	return false
222
+}
223
+
224
+func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
225
+	fromUnit = strings.ToLower(fromUnit)
226
+	if len(fromUnit) > 2 {
227
+		fromUnit = strings.TrimSuffix(fromUnit, "s")
228
+	}
229
+
230
+	toUnit = strings.ToLower(toUnit)
231
+	if len(toUnit) > 2 {
232
+		toUnit = strings.TrimSuffix(toUnit, "s")
233
+	}
234
+
235
+	var d time.Duration
236
+	switch fromUnit {
237
+	case "nanosecond", "ns":
238
+		d = time.Duration(value) * time.Nanosecond
239
+	case "microsecond":
240
+		d = time.Duration(value) * time.Microsecond
241
+	case "millisecond", "ms":
242
+		d = time.Duration(value) * time.Millisecond
243
+	case "second", "sec", "s":
244
+		d = time.Duration(value) * time.Second
245
+	case "cycle":
246
+		return float64(value), "", true
247
+	default:
248
+		return 0, "", false
249
+	}
250
+
251
+	if toUnit == "minimum" || toUnit == "auto" {
252
+		switch {
253
+		case d < 1*time.Microsecond:
254
+			toUnit = "ns"
255
+		case d < 1*time.Millisecond:
256
+			toUnit = "us"
257
+		case d < 1*time.Second:
258
+			toUnit = "ms"
259
+		case d < 1*time.Minute:
260
+			toUnit = "sec"
261
+		case d < 1*time.Hour:
262
+			toUnit = "min"
263
+		case d < 24*time.Hour:
264
+			toUnit = "hour"
265
+		case d < 15*24*time.Hour:
266
+			toUnit = "day"
267
+		case d < 120*24*time.Hour:
268
+			toUnit = "week"
269
+		default:
270
+			toUnit = "year"
271
+		}
272
+	}
273
+
274
+	var output float64
275
+	dd := float64(d)
276
+	switch toUnit {
277
+	case "ns", "nanosecond":
278
+		output, toUnit = dd/float64(time.Nanosecond), "ns"
279
+	case "us", "microsecond":
280
+		output, toUnit = dd/float64(time.Microsecond), "us"
281
+	case "ms", "millisecond":
282
+		output, toUnit = dd/float64(time.Millisecond), "ms"
283
+	case "min", "minute":
284
+		output, toUnit = dd/float64(time.Minute), "mins"
285
+	case "hour", "hr":
286
+		output, toUnit = dd/float64(time.Hour), "hrs"
287
+	case "day":
288
+		output, toUnit = dd/float64(24*time.Hour), "days"
289
+	case "week", "wk":
290
+		output, toUnit = dd/float64(7*24*time.Hour), "wks"
291
+	case "year", "yr":
292
+		output, toUnit = dd/float64(365*7*24*time.Hour), "yrs"
293
+	default:
294
+		fallthrough
295
+	case "sec", "second", "s":
296
+		output, toUnit = dd/float64(time.Second), "s"
297
+	}
298
+	return output, toUnit, true
299
+}

+ 186
- 0
src/internal/plugin/plugin.go Datei anzeigen

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
+
15
+// Package plugin defines the plugin implementations that the main pprof driver requires.
16
+package plugin
17
+
18
+import (
19
+	"io"
20
+	"regexp"
21
+	"time"
22
+
23
+	"profile"
24
+)
25
+
26
+// Options groups all the optional plugins into pprof.
27
+type Options struct {
28
+	Writer  Writer
29
+	Flagset FlagSet
30
+	Fetch   Fetcher
31
+	Sym     Symbolizer
32
+	Obj     ObjTool
33
+	UI      UI
34
+}
35
+
36
+// Writer provides a mechanism to write data under a certain name,
37
+// typically a filename.
38
+type Writer interface {
39
+	Open(name string) (io.WriteCloser, error)
40
+}
41
+
42
+// A FlagSet creates and parses command-line flags.
43
+// It is similar to the standard flag.FlagSet.
44
+type FlagSet interface {
45
+	// Bool, Int, Float64, and String define new flags,
46
+	// like the functions of the same name in package flag.
47
+	Bool(name string, def bool, usage string) *bool
48
+	Int(name string, def int, usage string) *int
49
+	Float64(name string, def float64, usage string) *float64
50
+	String(name string, def string, usage string) *string
51
+
52
+	// BoolVar, IntVar, Float64Var, and StringVar define new flags referencing
53
+	// a given pointer, like the functions of the same name in package flag.
54
+	BoolVar(pointer *bool, name string, def bool, usage string)
55
+	IntVar(pointer *int, name string, def int, usage string)
56
+	Float64Var(pointer *float64, name string, def float64, usage string)
57
+	StringVar(pointer *string, name string, def string, usage string)
58
+
59
+	// StringList is similar to String but allows multiple values for a
60
+	// single flag
61
+	StringList(name string, def string, usage string) *[]*string
62
+
63
+	// ExtraUsage returns any additional text that should be
64
+	// printed after the standard usage message.
65
+	// The typical use of ExtraUsage is to show any custom flags
66
+	// defined by the specific pprof plugins being used.
67
+	ExtraUsage() string
68
+
69
+	// Parse initializes the flags with their values for this run
70
+	// and returns the non-flag command line arguments.
71
+	// If an unknown flag is encountered or there are no arguments,
72
+	// Parse should call usage and return nil.
73
+	Parse(usage func()) []string
74
+}
75
+
76
+// A Fetcher reads and returns the profile named by src.  src can be a
77
+// local file path or a URL. duration and timeout are units specified
78
+// by the end user, or 0 by default. duration refers to the length of
79
+// the profile collection, if applicable, and timeout is the amount of
80
+// time to wait for a profile before returning an error. Returns the
81
+// fetched profile, the URL of the actual source of the profile, or an
82
+// error.
83
+type Fetcher interface {
84
+	Fetch(src string, duration, timeout time.Duration) (*profile.Profile, string, error)
85
+}
86
+
87
+// A Symbolizer introduces symbol information into a profile.
88
+type Symbolizer interface {
89
+	Symbolize(mode string, srcs MappingSources, prof *profile.Profile) error
90
+}
91
+
92
+// MappingSources map each profile.Mapping to the source of the profile.
93
+// The key is either Mapping.File or Mapping.BuildId.
94
+type MappingSources map[string][]struct {
95
+	Source string // URL of the source the mapping was collected from
96
+	Start  uint64 // delta applied to addresses from this source (to represent Merge adjustments)
97
+}
98
+
99
+// An ObjTool inspects shared libraries and executable files.
100
+type ObjTool interface {
101
+	// Open opens the named object file.  If the object is a shared
102
+	// library, start/limit/offset are the addresses where it is mapped
103
+	// into memory in the address space being inspected.
104
+	Open(file string, start, limit, offset uint64) (ObjFile, error)
105
+
106
+	// Disasm disassembles the named object file, starting at
107
+	// the start address and stopping at (before) the end address.
108
+	Disasm(file string, start, end uint64) ([]Inst, error)
109
+}
110
+
111
+// An Inst is a single instruction in an assembly listing.
112
+type Inst struct {
113
+	Addr uint64 // virtual address of instruction
114
+	Text string // instruction text
115
+	File string // source file
116
+	Line int    // source line
117
+}
118
+
119
+// An ObjFile is a single object file: a shared library or executable.
120
+type ObjFile interface {
121
+	// Name returns the underlyinf file name, if available
122
+	Name() string
123
+
124
+	// Base returns the base address to use when looking up symbols in the file.
125
+	Base() uint64
126
+
127
+	// BuildID returns the GNU build ID of the file, or an empty string.
128
+	BuildID() string
129
+
130
+	// SourceLine reports the source line information for a given
131
+	// address in the file. Due to inlining, the source line information
132
+	// is in general a list of positions representing a call stack,
133
+	// with the leaf function first.
134
+	SourceLine(addr uint64) ([]Frame, error)
135
+
136
+	// Symbols returns a list of symbols in the object file.
137
+	// If r is not nil, Symbols restricts the list to symbols
138
+	// with names matching the regular expression.
139
+	// If addr is not zero, Symbols restricts the list to symbols
140
+	// containing that address.
141
+	Symbols(r *regexp.Regexp, addr uint64) ([]*Sym, error)
142
+
143
+	// Close closes the file, releasing associated resources.
144
+	Close() error
145
+}
146
+
147
+// A Frame describes a single line in a source file.
148
+type Frame struct {
149
+	Func string // name of function
150
+	File string // source file name
151
+	Line int    // line in file
152
+}
153
+
154
+// A Sym describes a single symbol in an object file.
155
+type Sym struct {
156
+	Name  []string // names of symbol (many if symbol was dedup'ed)
157
+	File  string   // object file containing symbol
158
+	Start uint64   // start virtual address
159
+	End   uint64   // virtual address of last byte in sym (Start+size-1)
160
+}
161
+
162
+// A UI manages user interactions.
163
+type UI interface {
164
+	// Read returns a line of text (a command) read from the user.
165
+	// prompt is printed before reading the command.
166
+	ReadLine(prompt string) (string, error)
167
+
168
+	// Print shows a message to the user.
169
+	// It formats the text as fmt.Print would and adds a final \n if not already present.
170
+	// For line-based UI, Print writes to standard error.
171
+	// (Standard output is reserved for report data.)
172
+	Print(...interface{})
173
+
174
+	// PrintErr shows an error message to the user.
175
+	// It formats the text as fmt.Print would and adds a final \n if not already present.
176
+	// For line-based UI, PrintErr writes to standard error.
177
+	PrintErr(...interface{})
178
+
179
+	// IsTerminal returns whether the UI is known to be tied to an
180
+	// interactive terminal (as opposed to being redirected to a file).
181
+	IsTerminal() bool
182
+
183
+	// SetAutoComplete instructs the UI to call complete(cmd) to obtain
184
+	// the auto-completion of cmd, if the UI supports auto-completion at all.
185
+	SetAutoComplete(complete func(string) string)
186
+}

+ 102
- 0
src/internal/proftest/proftest.go Datei anzeigen

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
+
15
+// Package proftest provides some utility routines to test other
16
+// packages related to profiles.
17
+package proftest
18
+
19
+import (
20
+	"encoding/json"
21
+	"fmt"
22
+	"io/ioutil"
23
+	"os"
24
+	"os/exec"
25
+	"testing"
26
+)
27
+
28
+// Diff compares two byte arrays using the diff tool to highlight the
29
+// differences. It is meant for testing purposes to display the
30
+// differences between expected and actual output.
31
+func Diff(b1, b2 []byte) (data []byte, err error) {
32
+	f1, err := ioutil.TempFile("", "proto_test")
33
+	if err != nil {
34
+		return nil, err
35
+	}
36
+	defer os.Remove(f1.Name())
37
+	defer f1.Close()
38
+
39
+	f2, err := ioutil.TempFile("", "proto_test")
40
+	if err != nil {
41
+		return nil, err
42
+	}
43
+	defer os.Remove(f2.Name())
44
+	defer f2.Close()
45
+
46
+	f1.Write(b1)
47
+	f2.Write(b2)
48
+
49
+	data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
50
+	if len(data) > 0 {
51
+		// diff exits with a non-zero status when the files don't match.
52
+		// Ignore that failure as long as we get output.
53
+		err = nil
54
+	}
55
+	return
56
+}
57
+
58
+// EncodeJSON encodes a value into a byte array. This is intended for
59
+// testing purposes.
60
+func EncodeJSON(x interface{}) []byte {
61
+	data, err := json.MarshalIndent(x, "", "    ")
62
+	if err != nil {
63
+		panic(err)
64
+	}
65
+	data = append(data, '\n')
66
+	return data
67
+}
68
+
69
+// TestUI implements the plugin.UI interface, triggering test failures
70
+// if more than Ignore errors are printed.
71
+type TestUI struct {
72
+	T      *testing.T
73
+	Ignore int
74
+}
75
+
76
+// ReadLine returns no input, as no input is expected during testing.
77
+func (ui *TestUI) ReadLine(_ string) (string, error) {
78
+	return "", fmt.Errorf("no input")
79
+}
80
+
81
+// Print messages are discarded by the test UI.
82
+func (ui *TestUI) Print(args ...interface{}) {
83
+}
84
+
85
+// PrintErr messages may trigger an error failure. A fixed number of
86
+// error messages are permitted when appropriate.
87
+func (ui *TestUI) PrintErr(args ...interface{}) {
88
+	if ui.Ignore > 0 {
89
+		ui.Ignore--
90
+		return
91
+	}
92
+	ui.T.Error(args)
93
+}
94
+
95
+// IsTerminal indicates if the UI is an interactive terminal.
96
+func (ui *TestUI) IsTerminal() bool {
97
+	return false
98
+}
99
+
100
+// SetAutoComplete is not supported by the test UI.
101
+func (ui *TestUI) SetAutoComplete(_ func(string) string) {
102
+}

+ 947
- 0
src/internal/report/report.go Datei anzeigen

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

+ 210
- 0
src/internal/report/report_test.go Datei anzeigen

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
+
15
+package report
16
+
17
+import (
18
+	"bytes"
19
+	"io/ioutil"
20
+	"path/filepath"
21
+	"regexp"
22
+	"testing"
23
+
24
+	"internal/binutils"
25
+	"profile"
26
+	"internal/proftest"
27
+)
28
+
29
+type testcase struct {
30
+	rpt  *Report
31
+	want string
32
+}
33
+
34
+func TestSource(t *testing.T) {
35
+	const path = "testdata/"
36
+
37
+	sampleValue1 := func(v []int64) int64 {
38
+		return v[1]
39
+	}
40
+
41
+	for _, tc := range []testcase{
42
+		{
43
+			rpt: New(
44
+				testProfile.Copy(),
45
+				&Options{
46
+					OutputFormat: List,
47
+					Symbol:       regexp.MustCompile(`.`),
48
+					Title:        filepath.Base(testProfile.Mapping[0].File),
49
+
50
+					SampleValue: sampleValue1,
51
+					SampleUnit:  testProfile.SampleType[1].Unit,
52
+				},
53
+			),
54
+			want: path + "source.rpt",
55
+		},
56
+		{
57
+			rpt: New(
58
+				testProfile.Copy(),
59
+				&Options{
60
+					OutputFormat: Dot,
61
+					CallTree:     true,
62
+					Symbol:       regexp.MustCompile(`.`),
63
+					Title:        filepath.Base(testProfile.Mapping[0].File),
64
+
65
+					SampleValue: sampleValue1,
66
+					SampleUnit:  testProfile.SampleType[1].Unit,
67
+				},
68
+			),
69
+			want: path + "source.dot",
70
+		},
71
+	} {
72
+		b := bytes.NewBuffer(nil)
73
+		if err := Generate(b, tc.rpt, &binutils.Binutils{}); err != nil {
74
+			t.Fatalf("%s: %v", tc.want, err)
75
+		}
76
+
77
+		gold, err := ioutil.ReadFile(tc.want)
78
+		if err != nil {
79
+			t.Fatalf("%s: %v", tc.want, err)
80
+		}
81
+		if string(b.String()) != string(gold) {
82
+			d, err := proftest.Diff(gold, b.Bytes())
83
+			if err != nil {
84
+				t.Fatalf("%s: %v", "source", err)
85
+			}
86
+			t.Error("source" + "\n" + string(d) + "\n" + "gold:\n" + tc.want)
87
+		}
88
+	}
89
+}
90
+
91
+var testM = []*profile.Mapping{
92
+	{
93
+		ID:              1,
94
+		HasFunctions:    true,
95
+		HasFilenames:    true,
96
+		HasLineNumbers:  true,
97
+		HasInlineFrames: true,
98
+	},
99
+}
100
+
101
+var testF = []*profile.Function{
102
+	{
103
+		ID:       1,
104
+		Name:     "main",
105
+		Filename: "testdata/source1",
106
+	},
107
+	{
108
+		ID:       2,
109
+		Name:     "foo",
110
+		Filename: "testdata/source1",
111
+	},
112
+	{
113
+		ID:       3,
114
+		Name:     "bar",
115
+		Filename: "testdata/source1",
116
+	},
117
+	{
118
+		ID:       4,
119
+		Name:     "tee",
120
+		Filename: "testdata/source2",
121
+	},
122
+}
123
+
124
+var testL = []*profile.Location{
125
+	{
126
+		ID:      1,
127
+		Mapping: testM[0],
128
+		Line: []profile.Line{
129
+			{
130
+				Function: testF[0],
131
+				Line:     2,
132
+			},
133
+		},
134
+	},
135
+	{
136
+		ID:      2,
137
+		Mapping: testM[0],
138
+		Line: []profile.Line{
139
+			{
140
+				Function: testF[1],
141
+				Line:     4,
142
+			},
143
+		},
144
+	},
145
+	{
146
+		ID:      3,
147
+		Mapping: testM[0],
148
+		Line: []profile.Line{
149
+			{
150
+				Function: testF[2],
151
+				Line:     10,
152
+			},
153
+		},
154
+	},
155
+	{
156
+		ID:      4,
157
+		Mapping: testM[0],
158
+		Line: []profile.Line{
159
+			{
160
+				Function: testF[3],
161
+				Line:     2,
162
+			},
163
+		},
164
+	},
165
+	{
166
+		ID:      5,
167
+		Mapping: testM[0],
168
+		Line: []profile.Line{
169
+			{
170
+				Function: testF[3],
171
+				Line:     8,
172
+			},
173
+		},
174
+	},
175
+}
176
+
177
+var testProfile = &profile.Profile{
178
+	PeriodType:    &profile.ValueType{Type: "cpu", Unit: "millisecond"},
179
+	Period:        10,
180
+	DurationNanos: 10e9,
181
+	SampleType: []*profile.ValueType{
182
+		{Type: "samples", Unit: "count"},
183
+		{Type: "cpu", Unit: "cycles"},
184
+	},
185
+	Sample: []*profile.Sample{
186
+		{
187
+			Location: []*profile.Location{testL[0]},
188
+			Value:    []int64{1, 1},
189
+		},
190
+		{
191
+			Location: []*profile.Location{testL[2], testL[1], testL[0]},
192
+			Value:    []int64{1, 10},
193
+		},
194
+		{
195
+			Location: []*profile.Location{testL[4], testL[2], testL[0]},
196
+			Value:    []int64{1, 100},
197
+		},
198
+		{
199
+			Location: []*profile.Location{testL[3], testL[0]},
200
+			Value:    []int64{1, 1000},
201
+		},
202
+		{
203
+			Location: []*profile.Location{testL[4], testL[3], testL[0]},
204
+			Value:    []int64{1, 10000},
205
+		},
206
+	},
207
+	Location: testL,
208
+	Function: testF,
209
+	Mapping:  testM,
210
+}

+ 457
- 0
src/internal/report/source.go Datei anzeigen

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
+
15
+package report
16
+
17
+// This file contains routines related to the generation of annotated
18
+// source listings.
19
+
20
+import (
21
+	"bufio"
22
+	"fmt"
23
+	"html/template"
24
+	"io"
25
+	"os"
26
+	"path/filepath"
27
+	"sort"
28
+	"strconv"
29
+	"strings"
30
+
31
+	"internal/graph"
32
+	"internal/plugin"
33
+)
34
+
35
+// printSource prints an annotated source listing, include all
36
+// functions with samples that match the regexp rpt.options.symbol.
37
+// The sources are sorted by function name and then by filename to
38
+// eliminate potential nondeterminism.
39
+func printSource(w io.Writer, rpt *Report) error {
40
+	o := rpt.options
41
+	g := rpt.newGraph(nil)
42
+
43
+	// Identify all the functions that match the regexp provided.
44
+	// Group nodes for each matching function.
45
+	var functions graph.Nodes
46
+	functionNodes := make(map[string]graph.Nodes)
47
+	for _, n := range g.Nodes {
48
+		if !o.Symbol.MatchString(n.Info.Name) {
49
+			continue
50
+		}
51
+		if functionNodes[n.Info.Name] == nil {
52
+			functions = append(functions, n)
53
+		}
54
+		functionNodes[n.Info.Name] = append(functionNodes[n.Info.Name], n)
55
+	}
56
+	functions.Sort(graph.NameOrder)
57
+
58
+	fmt.Fprintf(w, "Total: %s\n", rpt.formatValue(rpt.total))
59
+	for _, fn := range functions {
60
+		name := fn.Info.Name
61
+
62
+		// Identify all the source files associated to this function.
63
+		// Group nodes for each source file.
64
+		var sourceFiles graph.Nodes
65
+		fileNodes := make(map[string]graph.Nodes)
66
+		for _, n := range functionNodes[name] {
67
+			if n.Info.File == "" {
68
+				continue
69
+			}
70
+			if fileNodes[n.Info.File] == nil {
71
+				sourceFiles = append(sourceFiles, n)
72
+			}
73
+			fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
74
+		}
75
+
76
+		if len(sourceFiles) == 0 {
77
+			fmt.Fprintf(w, "No source information for %s\n", name)
78
+			continue
79
+		}
80
+
81
+		sourceFiles.Sort(graph.FileOrder)
82
+
83
+		// Print each file associated with this function.
84
+		for _, fl := range sourceFiles {
85
+			filename := fl.Info.File
86
+			fns := fileNodes[filename]
87
+			flatSum, cumSum := fns.Sum()
88
+
89
+			fnodes, path, err := getFunctionSource(name, filename, fns, 0, 0)
90
+			fmt.Fprintf(w, "ROUTINE ======================== %s in %s\n", name, path)
91
+			fmt.Fprintf(w, "%10s %10s (flat, cum) %s of Total\n",
92
+				rpt.formatValue(flatSum), rpt.formatValue(cumSum),
93
+				percentage(cumSum, rpt.total))
94
+
95
+			if err != nil {
96
+				fmt.Fprintf(w, " Error: %v\n", err)
97
+				continue
98
+			}
99
+
100
+			for _, fn := range fnodes {
101
+				fmt.Fprintf(w, "%10s %10s %6d:%s\n", valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt), fn.Info.Lineno, fn.Info.Name)
102
+			}
103
+		}
104
+	}
105
+	return nil
106
+}
107
+
108
+// printWebSource prints an annotated source listing, include all
109
+// functions with samples that match the regexp rpt.options.symbol.
110
+func printWebSource(w io.Writer, rpt *Report, obj plugin.ObjTool) error {
111
+	o := rpt.options
112
+	g := rpt.newGraph(nil)
113
+
114
+	// If the regexp source can be parsed as an address, also match
115
+	// functions that land on that address.
116
+	var address *uint64
117
+	if hex, err := strconv.ParseUint(o.Symbol.String(), 0, 64); err == nil {
118
+		address = &hex
119
+	}
120
+
121
+	// Extract interesting symbols from binary files in the profile and
122
+	// classify samples per symbol.
123
+	symbols := symbolsFromBinaries(rpt.prof, g, o.Symbol, address, obj)
124
+	symNodes := nodesPerSymbol(g.Nodes, symbols)
125
+
126
+	// Sort symbols for printing.
127
+	var syms objSymbols
128
+	for s := range symNodes {
129
+		syms = append(syms, s)
130
+	}
131
+	sort.Sort(syms)
132
+
133
+	if len(syms) == 0 {
134
+		return fmt.Errorf("no samples found on routines matching: %s", o.Symbol.String())
135
+	}
136
+
137
+	printHeader(w, rpt)
138
+	for _, s := range syms {
139
+		name := s.sym.Name[0]
140
+		// Identify sources associated to a symbol by examining
141
+		// symbol samples. Classify samples per source file.
142
+		var sourceFiles graph.Nodes
143
+		fileNodes := make(map[string]graph.Nodes)
144
+		for _, n := range symNodes[s] {
145
+			if n.Info.File == "" {
146
+				continue
147
+			}
148
+			if fileNodes[n.Info.File] == nil {
149
+				sourceFiles = append(sourceFiles, n)
150
+			}
151
+			fileNodes[n.Info.File] = append(fileNodes[n.Info.File], n)
152
+		}
153
+
154
+		if len(sourceFiles) == 0 {
155
+			fmt.Fprintf(w, "No source information for %s\n", name)
156
+			continue
157
+		}
158
+
159
+		sourceFiles.Sort(graph.FileOrder)
160
+
161
+		// Print each file associated with this function.
162
+		for _, fl := range sourceFiles {
163
+			filename := fl.Info.File
164
+			fns := fileNodes[filename]
165
+
166
+			asm := assemblyPerSourceLine(symbols, fns, filename, obj)
167
+			start, end := sourceCoordinates(asm)
168
+
169
+			fnodes, path, err := getFunctionSource(name, filename, fns, start, end)
170
+			if err != nil {
171
+				fnodes, path = getMissingFunctionSource(filename, asm, start, end)
172
+			}
173
+
174
+			flatSum, cumSum := fnodes.Sum()
175
+			printFunctionHeader(w, name, path, flatSum, cumSum, rpt)
176
+			for _, fn := range fnodes {
177
+				printFunctionSourceLine(w, fn, asm[fn.Info.Lineno], rpt)
178
+			}
179
+			printFunctionClosing(w)
180
+		}
181
+	}
182
+	printPageClosing(w)
183
+	return nil
184
+}
185
+
186
+// sourceCoordinates returns the lowest and highest line numbers from
187
+// a set of assembly statements.
188
+func sourceCoordinates(asm map[int]graph.Nodes) (start, end int) {
189
+	for l := range asm {
190
+		if start == 0 || l < start {
191
+			start = l
192
+		}
193
+		if end == 0 || l > end {
194
+			end = l
195
+		}
196
+	}
197
+	return start, end
198
+}
199
+
200
+// assemblyPerSourceLine disassembles the binary containing a symbol
201
+// and classifies the assembly instructions according to its
202
+// corresponding source line, annotating them with a set of samples.
203
+func assemblyPerSourceLine(objSyms []*objSymbol, rs graph.Nodes, src string, obj plugin.ObjTool) map[int]graph.Nodes {
204
+	assembly := make(map[int]graph.Nodes)
205
+	// Identify symbol to use for this collection of samples.
206
+	o := findMatchingSymbol(objSyms, rs)
207
+	if o == nil {
208
+		return assembly
209
+	}
210
+
211
+	// Extract assembly for matched symbol
212
+	insns, err := obj.Disasm(o.sym.File, o.sym.Start, o.sym.End)
213
+	if err != nil {
214
+		return assembly
215
+	}
216
+
217
+	srcBase := filepath.Base(src)
218
+	anodes := annotateAssembly(insns, rs, o.base)
219
+	var lineno = 0
220
+	for _, an := range anodes {
221
+		if filepath.Base(an.Info.File) == srcBase {
222
+			lineno = an.Info.Lineno
223
+		}
224
+		if lineno != 0 {
225
+			assembly[lineno] = append(assembly[lineno], an)
226
+		}
227
+	}
228
+
229
+	return assembly
230
+}
231
+
232
+// findMatchingSymbol looks for the symbol that corresponds to a set
233
+// of samples, by comparing their addresses.
234
+func findMatchingSymbol(objSyms []*objSymbol, ns graph.Nodes) *objSymbol {
235
+	for _, n := range ns {
236
+		for _, o := range objSyms {
237
+			if filepath.Base(o.sym.File) == n.Info.Objfile &&
238
+				o.sym.Start <= n.Info.Address-o.base &&
239
+				n.Info.Address-o.base <= o.sym.End {
240
+				return o
241
+			}
242
+		}
243
+	}
244
+	return nil
245
+}
246
+
247
+// printHeader prints the page header for a weblist report.
248
+func printHeader(w io.Writer, rpt *Report) {
249
+	fmt.Fprintln(w, weblistPageHeader)
250
+
251
+	var labels []string
252
+	for _, l := range ProfileLabels(rpt) {
253
+		labels = append(labels, template.HTMLEscapeString(l))
254
+	}
255
+
256
+	fmt.Fprintf(w, `<div class="legend">%s<br>Total: %s</div>`,
257
+		strings.Join(labels, "<br>\n"),
258
+		rpt.formatValue(rpt.total),
259
+	)
260
+}
261
+
262
+// printFunctionHeader prints a function header for a weblist report.
263
+func printFunctionHeader(w io.Writer, name, path string, flatSum, cumSum int64, rpt *Report) {
264
+	fmt.Fprintf(w, `<h1>%s</h1>%s
265
+<pre onClick="pprof_toggle_asm()">
266
+  Total:  %10s %10s (flat, cum) %s
267
+`,
268
+		template.HTMLEscapeString(name), template.HTMLEscapeString(path),
269
+		rpt.formatValue(flatSum), rpt.formatValue(cumSum),
270
+		percentage(cumSum, rpt.total))
271
+}
272
+
273
+// printFunctionSourceLine prints a source line and the corresponding assembly.
274
+func printFunctionSourceLine(w io.Writer, fn *graph.Node, assembly graph.Nodes, rpt *Report) {
275
+	if len(assembly) == 0 {
276
+		fmt.Fprintf(w,
277
+			"<span class=line> %6d</span> <span class=nop>  %10s %10s %s </span>\n",
278
+			fn.Info.Lineno,
279
+			valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
280
+			template.HTMLEscapeString(fn.Info.Name))
281
+		return
282
+	}
283
+
284
+	fmt.Fprintf(w,
285
+		"<span class=line> %6d</span> <span class=deadsrc>  %10s %10s %s </span>",
286
+		fn.Info.Lineno,
287
+		valueOrDot(fn.Flat, rpt), valueOrDot(fn.Cum, rpt),
288
+		template.HTMLEscapeString(fn.Info.Name))
289
+	fmt.Fprint(w, "<span class=asm>")
290
+	for _, an := range assembly {
291
+		var fileline string
292
+		class := "disasmloc"
293
+		if an.Info.File != "" {
294
+			fileline = fmt.Sprintf("%s:%d", template.HTMLEscapeString(an.Info.File), an.Info.Lineno)
295
+			if an.Info.Lineno != fn.Info.Lineno {
296
+				class = "unimportant"
297
+			}
298
+		}
299
+		fmt.Fprintf(w, " %8s %10s %10s %8x: %-48s <span class=%s>%s</span>\n", "",
300
+			valueOrDot(an.Flat, rpt), valueOrDot(an.Cum, rpt),
301
+			an.Info.Address,
302
+			template.HTMLEscapeString(an.Info.Name),
303
+			class,
304
+			template.HTMLEscapeString(fileline))
305
+	}
306
+	fmt.Fprintln(w, "</span>")
307
+}
308
+
309
+// printFunctionClosing prints the end of a function in a weblist report.
310
+func printFunctionClosing(w io.Writer) {
311
+	fmt.Fprintln(w, "</pre>")
312
+}
313
+
314
+// printPageClosing prints the end of the page in a weblist report.
315
+func printPageClosing(w io.Writer) {
316
+	fmt.Fprintln(w, weblistPageClosing)
317
+}
318
+
319
+// getFunctionSource collects the sources of a function from a source
320
+// file and annotates it with the samples in fns. Returns the sources
321
+// as nodes, using the info.name field to hold the source code.
322
+func getFunctionSource(fun, file string, fns graph.Nodes, start, end int) (graph.Nodes, string, error) {
323
+	f, file, err := adjustSourcePath(file)
324
+	if err != nil {
325
+		return nil, file, err
326
+	}
327
+
328
+	lineNodes := make(map[int]graph.Nodes)
329
+	// Collect source coordinates from profile.
330
+	const margin = 5 // Lines before first/after last sample.
331
+	if start == 0 {
332
+		if fns[0].Info.StartLine != 0 {
333
+			start = fns[0].Info.StartLine
334
+		} else {
335
+			start = fns[0].Info.Lineno - margin
336
+		}
337
+	} else {
338
+		start -= margin
339
+	}
340
+	if end == 0 {
341
+		end = fns[0].Info.Lineno
342
+	}
343
+	end += margin
344
+	for _, n := range fns {
345
+		lineno := n.Info.Lineno
346
+		nodeStart := n.Info.StartLine
347
+		if nodeStart == 0 {
348
+			nodeStart = lineno - margin
349
+		}
350
+		nodeEnd := lineno + margin
351
+		if nodeStart < start {
352
+			start = nodeStart
353
+		} else if nodeEnd > end {
354
+			end = nodeEnd
355
+		}
356
+		lineNodes[lineno] = append(lineNodes[lineno], n)
357
+	}
358
+
359
+	var src graph.Nodes
360
+	buf := bufio.NewReader(f)
361
+	lineno := 1
362
+	for {
363
+		line, err := buf.ReadString('\n')
364
+		if err != nil {
365
+			if err != io.EOF {
366
+				return nil, file, err
367
+			}
368
+			if line == "" {
369
+				break
370
+			}
371
+		}
372
+		if lineno >= start {
373
+			flat, cum := lineNodes[lineno].Sum()
374
+
375
+			src = append(src, &graph.Node{
376
+				Info: graph.NodeInfo{
377
+					Name:   strings.TrimRight(line, "\n"),
378
+					Lineno: lineno,
379
+				},
380
+				Flat: flat,
381
+				Cum:  cum,
382
+			})
383
+		}
384
+		lineno++
385
+		if lineno > end {
386
+			break
387
+		}
388
+	}
389
+	return src, file, nil
390
+}
391
+
392
+// getMissingFunctionSource creates a dummy function body to point to
393
+// the source file and annotates it with the samples in asm.
394
+func getMissingFunctionSource(filename string, asm map[int]graph.Nodes, start, end int) (graph.Nodes, string) {
395
+	var fnodes graph.Nodes
396
+	for i := start; i <= end; i++ {
397
+		lrs := asm[i]
398
+		if len(lrs) == 0 {
399
+			continue
400
+		}
401
+		flat, cum := lrs.Sum()
402
+		fnodes = append(fnodes, &graph.Node{
403
+			Info: graph.NodeInfo{
404
+				Name:   "???",
405
+				Lineno: i,
406
+			},
407
+			Flat: flat,
408
+			Cum:  cum,
409
+		})
410
+	}
411
+	return fnodes, filename
412
+}
413
+
414
+// adjustSourcePath adjusts the path for a source file by trimmming
415
+// known prefixes and searching for the file on all parents of the
416
+// current working dir.
417
+func adjustSourcePath(path string) (*os.File, string, error) {
418
+	path = trimPath(path)
419
+	f, err := os.Open(path)
420
+	if err == nil {
421
+		return f, path, nil
422
+	}
423
+
424
+	if dir, wderr := os.Getwd(); wderr == nil {
425
+		for {
426
+			parent := filepath.Dir(dir)
427
+			if parent == dir {
428
+				break
429
+			}
430
+			if f, err := os.Open(filepath.Join(parent, path)); err == nil {
431
+				return f, filepath.Join(parent, path), nil
432
+			}
433
+
434
+			dir = parent
435
+		}
436
+	}
437
+
438
+	return nil, path, err
439
+}
440
+
441
+// trimPath cleans up a path by removing prefixes that are commonly
442
+// found on profiles.
443
+func trimPath(path string) string {
444
+	basePaths := []string{
445
+		"/proc/self/cwd/./",
446
+		"/proc/self/cwd/",
447
+	}
448
+
449
+	sPath := filepath.ToSlash(path)
450
+
451
+	for _, base := range basePaths {
452
+		if strings.HasPrefix(sPath, base) {
453
+			return filepath.FromSlash(sPath[len(base):])
454
+		}
455
+	}
456
+	return path
457
+}

+ 87
- 0
src/internal/report/source_html.go Datei anzeigen

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
+
15
+package report
16
+
17
+const weblistPageHeader = `
18
+<!DOCTYPE html>
19
+<html>
20
+<head>
21
+<title>Pprof listing</title>
22
+<style type="text/css">
23
+body {
24
+font-family: sans-serif;
25
+}
26
+h1 {
27
+  font-size: 1.5em;
28
+  margin-bottom: 4px;
29
+}
30
+.legend {
31
+  font-size: 1.25em;
32
+}
33
+.line {
34
+color: #aaaaaa;
35
+}
36
+.nop {
37
+color: #aaaaaa;
38
+}
39
+.unimportant {
40
+color: #cccccc;
41
+}
42
+.disasmloc {
43
+color: #000000;
44
+}
45
+.deadsrc {
46
+cursor: pointer;
47
+}
48
+.deadsrc:hover {
49
+background-color: #eeeeee;
50
+}
51
+.livesrc {
52
+color: #0000ff;
53
+cursor: pointer;
54
+}
55
+.livesrc:hover {
56
+background-color: #eeeeee;
57
+}
58
+.asm {
59
+color: #008800;
60
+display: none;
61
+}
62
+</style>
63
+<script type="text/javascript">
64
+function pprof_toggle_asm(e) {
65
+  var target;
66
+  if (!e) e = window.event;
67
+  if (e.target) target = e.target;
68
+  else if (e.srcElement) target = e.srcElement;
69
+
70
+  if (target) {
71
+    var asm = target.nextSibling;
72
+    if (asm && asm.className == "asm") {
73
+      asm.style.display = (asm.style.display == "block" ? "" : "block");
74
+      e.preventDefault();
75
+      return false;
76
+    }
77
+  }
78
+}
79
+</script>
80
+</head>
81
+<body>
82
+`
83
+
84
+const weblistPageClosing = `
85
+</body>
86
+</html>
87
+`

+ 17
- 0
src/internal/report/testdata/source.dot Datei anzeigen

1
+digraph "." {
2
+node [style=filled fillcolor="#f8f8f8"]
3
+subgraph cluster_L { "Duration: 10s, Total samples = 11111 " [shape=box fontsize=16 label="Duration: 10s, Total samples = 11111 \lShowing nodes accounting for 11111, 100% of 11111 total\l"] }
4
+N1 [label="tee\nsource2:8\n10000 (90.00%)" fontsize=24 shape=box tooltip="tee testdata/source2:8 (10000)" color="#b20500" fillcolor="#edd6d5"]
5
+N2 [label="main\nsource1:2\n1 (0.009%)\nof 11111 (100%)" fontsize=9 shape=box tooltip="main testdata/source1:2 (11111)" color="#b20000" fillcolor="#edd5d5"]
6
+N3 [label="tee\nsource2:2\n1000 (9.00%)\nof 11000 (99.00%)" fontsize=14 shape=box tooltip="tee testdata/source2:2 (11000)" color="#b20000" fillcolor="#edd5d5"]
7
+N4 [label="tee\nsource2:8\n100 (0.9%)" fontsize=10 shape=box tooltip="tee testdata/source2:8 (100)" color="#b2b0aa" fillcolor="#edecec"]
8
+N5 [label="bar\nsource1:10\n10 (0.09%)" fontsize=9 shape=box tooltip="bar testdata/source1:10 (10)" color="#b2b2b1" fillcolor="#ededed"]
9
+N6 [label="bar\nsource1:10\n0 of 100 (0.9%)" fontsize=8 shape=box tooltip="bar testdata/source1:10 (100)" color="#b2b0aa" fillcolor="#edecec"]
10
+N7 [label="foo\nsource1:4\n0 of 10 (0.09%)" fontsize=8 shape=box tooltip="foo testdata/source1:4 (10)" color="#b2b2b1" fillcolor="#ededed"]
11
+N2 -> N3 [label=" 11000" weight=100 penwidth=5 color="#b20000" tooltip="main testdata/source1:2 -> tee testdata/source2:2 (11000)" labeltooltip="main testdata/source1:2 -> tee testdata/source2:2 (11000)"]
12
+N3 -> N1 [label=" 10000" weight=91 penwidth=5 color="#b20500" tooltip="tee testdata/source2:2 -> tee testdata/source2:8 (10000)" labeltooltip="tee testdata/source2:2 -> tee testdata/source2:8 (10000)"]
13
+N6 -> N4 [label=" 100" color="#b2b0aa" tooltip="bar testdata/source1:10 -> tee testdata/source2:8 (100)" labeltooltip="bar testdata/source1:10 -> tee testdata/source2:8 (100)"]
14
+N2 -> N6 [label=" 100" color="#b2b0aa" tooltip="main testdata/source1:2 -> bar testdata/source1:10 (100)" labeltooltip="main testdata/source1:2 -> bar testdata/source1:10 (100)"]
15
+N7 -> N5 [label=" 10" color="#b2b2b1" tooltip="foo testdata/source1:4 -> bar testdata/source1:10 (10)" labeltooltip="foo testdata/source1:4 -> bar testdata/source1:10 (10)"]
16
+N2 -> N7 [label=" 10" color="#b2b2b1" tooltip="main testdata/source1:2 -> foo testdata/source1:4 (10)" labeltooltip="main testdata/source1:2 -> foo testdata/source1:4 (10)"]
17
+}

+ 49
- 0
src/internal/report/testdata/source.rpt Datei anzeigen

1
+Total: 11111
2
+ROUTINE ======================== bar in testdata/source1
3
+        10        110 (flat, cum)  0.99% of Total
4
+         .          .      5:source1 line 5;
5
+         .          .      6:source1 line 6;
6
+         .          .      7:source1 line 7;
7
+         .          .      8:source1 line 8;
8
+         .          .      9:source1 line 9;
9
+        10        110     10:source1 line 10;
10
+         .          .     11:source1 line 11;
11
+         .          .     12:source1 line 12;
12
+         .          .     13:source1 line 13;
13
+         .          .     14:source1 line 14;
14
+         .          .     15:source1 line 15;
15
+ROUTINE ======================== foo in testdata/source1
16
+         0         10 (flat, cum)  0.09% of Total
17
+         .          .      1:source1 line 1;
18
+         .          .      2:source1 line 2;
19
+         .          .      3:source1 line 3;
20
+         .         10      4:source1 line 4;
21
+         .          .      5:source1 line 5;
22
+         .          .      6:source1 line 6;
23
+         .          .      7:source1 line 7;
24
+         .          .      8:source1 line 8;
25
+         .          .      9:source1 line 9;
26
+ROUTINE ======================== main in testdata/source1
27
+         1      11111 (flat, cum)   100% of Total
28
+         .          .      1:source1 line 1;
29
+         1      11111      2:source1 line 2;
30
+         .          .      3:source1 line 3;
31
+         .          .      4:source1 line 4;
32
+         .          .      5:source1 line 5;
33
+         .          .      6:source1 line 6;
34
+         .          .      7:source1 line 7;
35
+ROUTINE ======================== tee in testdata/source2
36
+     11100      21100 (flat, cum) 189.90% of Total
37
+         .          .      1:source2 line 1;
38
+      1000      11000      2:source2 line 2;
39
+         .          .      3:source2 line 3;
40
+         .          .      4:source2 line 4;
41
+         .          .      5:source2 line 5;
42
+         .          .      6:source2 line 6;
43
+         .          .      7:source2 line 7;
44
+     10100      10100      8:source2 line 8;
45
+         .          .      9:source2 line 9;
46
+         .          .     10:source2 line 10;
47
+         .          .     11:source2 line 11;
48
+         .          .     12:source2 line 12;
49
+         .          .     13:source2 line 13;

+ 19
- 0
src/internal/report/testdata/source1 Datei anzeigen

1
+source1 line 1;
2
+source1 line 2;
3
+source1 line 3;
4
+source1 line 4;
5
+source1 line 5;
6
+source1 line 6;
7
+source1 line 7;
8
+source1 line 8;
9
+source1 line 9;
10
+source1 line 10;
11
+source1 line 11;
12
+source1 line 12;
13
+source1 line 13;
14
+source1 line 14;
15
+source1 line 15;
16
+source1 line 16;
17
+source1 line 17;
18
+source1 line 18;
19
+

+ 19
- 0
src/internal/report/testdata/source2 Datei anzeigen

1
+source2 line 1;
2
+source2 line 2;
3
+source2 line 3;
4
+source2 line 4;
5
+source2 line 5;
6
+source2 line 6;
7
+source2 line 7;
8
+source2 line 8;
9
+source2 line 9;
10
+source2 line 10;
11
+source2 line 11;
12
+source2 line 12;
13
+source2 line 13;
14
+source2 line 14;
15
+source2 line 15;
16
+source2 line 16;
17
+source2 line 17;
18
+source2 line 18;
19
+

+ 320
- 0
src/internal/symbolizer/symbolizer.go Datei anzeigen

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
+
15
+// Package symbolizer provides a routine to populate a profile with
16
+// symbol, file and line number information. It relies on the
17
+// addr2liner and demangle packages to do the actual work.
18
+package symbolizer
19
+
20
+import (
21
+	"fmt"
22
+	"io/ioutil"
23
+	"net/http"
24
+	"path/filepath"
25
+	"strings"
26
+
27
+	"internal/binutils"
28
+	"internal/plugin"
29
+	"profile"
30
+	"internal/symbolz"
31
+	"golang/demangle"
32
+)
33
+
34
+// Symbolizer implements the plugin.Symbolize interface.
35
+type Symbolizer struct {
36
+	Obj plugin.ObjTool
37
+	UI  plugin.UI
38
+}
39
+
40
+// Symbolize attempts to symbolize profile p. First uses binutils on
41
+// local binaries; if the source is a URL it attempts to get any
42
+// missed entries using symbolz.
43
+func (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *profile.Profile) error {
44
+	remote, local, force, demanglerMode := true, true, false, ""
45
+	for _, o := range strings.Split(strings.ToLower(mode), ":") {
46
+		switch o {
47
+		case "none", "no":
48
+			return nil
49
+		case "local", "fastlocal":
50
+			remote, local = false, true
51
+		case "remote":
52
+			remote, local = true, false
53
+		case "", "force":
54
+			force = true
55
+		default:
56
+			switch d := strings.TrimPrefix(o, "demangle="); d {
57
+			case "full", "none", "templates":
58
+				demanglerMode = d
59
+				force = true
60
+				continue
61
+			case "default":
62
+				continue
63
+			}
64
+			s.UI.PrintErr("ignoring unrecognized symbolization option: " + mode)
65
+			s.UI.PrintErr("expecting -symbolize=[local|fastlocal|remote|none][:force][:demangle=[none|full|templates|default]")
66
+		}
67
+	}
68
+
69
+	var err error
70
+	if local {
71
+		// Symbolize locally using binutils.
72
+		if err = localSymbolize(mode, p, s.Obj, s.UI); err == nil {
73
+			remote = false // Already symbolized, no need to apply remote symbolization.
74
+		}
75
+	}
76
+	if remote {
77
+		if err = symbolz.Symbolize(sources, postURL, p, s.UI); err != nil {
78
+			return err // Ran out of options.
79
+		}
80
+	}
81
+
82
+	Demangle(p, force, demanglerMode)
83
+	return nil
84
+}
85
+
86
+// postURL issues a POST to a URL over HTTP.
87
+func postURL(source, post string) ([]byte, error) {
88
+	resp, err := http.Post(source, "application/octet-stream", strings.NewReader(post))
89
+	if err != nil {
90
+		return nil, fmt.Errorf("http post %s: %v", source, err)
91
+	}
92
+	if resp.StatusCode != http.StatusOK {
93
+		return nil, fmt.Errorf("server response: %s", resp.Status)
94
+	}
95
+	defer resp.Body.Close()
96
+	return ioutil.ReadAll(resp.Body)
97
+}
98
+
99
+// localSymbolize adds symbol and line number information to all locations
100
+// in a profile. mode enables some options to control
101
+// symbolization.
102
+func localSymbolize(mode string, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI) error {
103
+	force := false
104
+	// Disable some mechanisms based on mode string.
105
+	for _, o := range strings.Split(strings.ToLower(mode), ":") {
106
+		switch {
107
+		case o == "force":
108
+			force = true
109
+		case o == "fastlocal":
110
+			if bu, ok := obj.(*binutils.Binutils); ok {
111
+				bu.SetFastSymbolization(true)
112
+			}
113
+		default:
114
+		}
115
+	}
116
+
117
+	mt, err := newMapping(prof, obj, ui, force)
118
+	if err != nil {
119
+		return err
120
+	}
121
+	defer mt.close()
122
+
123
+	functions := make(map[profile.Function]*profile.Function)
124
+	for _, l := range mt.prof.Location {
125
+		m := l.Mapping
126
+		segment := mt.segments[m]
127
+		if segment == nil {
128
+			// Nothing to do.
129
+			continue
130
+		}
131
+
132
+		stack, err := segment.SourceLine(l.Address)
133
+		if err != nil || len(stack) == 0 {
134
+			// No answers from addr2line.
135
+			continue
136
+		}
137
+
138
+		l.Line = make([]profile.Line, len(stack))
139
+		for i, frame := range stack {
140
+			if frame.Func != "" {
141
+				m.HasFunctions = true
142
+			}
143
+			if frame.File != "" {
144
+				m.HasFilenames = true
145
+			}
146
+			if frame.Line != 0 {
147
+				m.HasLineNumbers = true
148
+			}
149
+			f := &profile.Function{
150
+				Name:       frame.Func,
151
+				SystemName: frame.Func,
152
+				Filename:   frame.File,
153
+			}
154
+			if fp := functions[*f]; fp != nil {
155
+				f = fp
156
+			} else {
157
+				functions[*f] = f
158
+				f.ID = uint64(len(mt.prof.Function)) + 1
159
+				mt.prof.Function = append(mt.prof.Function, f)
160
+			}
161
+			l.Line[i] = profile.Line{
162
+				Function: f,
163
+				Line:     int64(frame.Line),
164
+			}
165
+		}
166
+
167
+		if len(stack) > 0 {
168
+			m.HasInlineFrames = true
169
+		}
170
+	}
171
+
172
+	return nil
173
+}
174
+
175
+// Demangle updates the function names in a profile with demangled C++
176
+// names, simplified according to demanglerMode. If force is set,
177
+// overwrite any names that appear already demangled.
178
+func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
179
+	if force {
180
+		// Remove the current demangled names to force demangling
181
+		for _, f := range prof.Function {
182
+			if f.Name != "" && f.SystemName != "" {
183
+				f.Name = f.SystemName
184
+			}
185
+		}
186
+	}
187
+
188
+	var options []demangle.Option
189
+	switch demanglerMode {
190
+	case "": // demangled, simplified: no parameters, no templates, no return type
191
+		options = []demangle.Option{demangle.NoParams, demangle.NoTemplateParams}
192
+	case "templates": // demangled, simplified: no parameters, no return type
193
+		options = []demangle.Option{demangle.NoParams}
194
+	case "full":
195
+		options = []demangle.Option{demangle.NoClones}
196
+	case "none": // no demangling
197
+		return
198
+	}
199
+
200
+	// Copy the options because they may be updated by the call.
201
+	o := make([]demangle.Option, len(options))
202
+	for _, fn := range prof.Function {
203
+		if fn.Name != "" && fn.SystemName != fn.Name {
204
+			continue // Already demangled.
205
+		}
206
+		copy(o, options)
207
+		if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName {
208
+			fn.Name = demangled
209
+			continue
210
+		}
211
+		// Could not demangle. Apply heuristics in case the name is
212
+		// already demangled.
213
+		name := fn.SystemName
214
+		if looksLikeDemangledCPlusPlus(name) {
215
+			if demanglerMode == "" || demanglerMode == "templates" {
216
+				name = removeMatching(name, '(', ')')
217
+			}
218
+			if demanglerMode == "" {
219
+				name = removeMatching(name, '<', '>')
220
+			}
221
+		}
222
+		fn.Name = name
223
+	}
224
+}
225
+
226
+// looksLikeDemangledCPlusPlus is a heuristic to decide if a name is
227
+// the result of demangling C++. If so, further heuristics will be
228
+// applied to simplify the name.
229
+func looksLikeDemangledCPlusPlus(demangled string) bool {
230
+	if strings.Contains(demangled, ".<") { // Skip java names of the form "class.<init>"
231
+		return false
232
+	}
233
+	return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
234
+}
235
+
236
+// removeMatching removes nested instances of start..end from name.
237
+func removeMatching(name string, start, end byte) string {
238
+	s := string(start) + string(end)
239
+	var nesting, first, current int
240
+	for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {
241
+		switch current += index; name[current] {
242
+		case start:
243
+			nesting++
244
+			if nesting == 1 {
245
+				first = current
246
+			}
247
+		case end:
248
+			nesting--
249
+			switch {
250
+			case nesting < 0:
251
+				return name // Mismatch, abort
252
+			case nesting == 0:
253
+				name = name[:first] + name[current+1:]
254
+				current = first - 1
255
+			}
256
+		}
257
+		current++
258
+	}
259
+	return name
260
+}
261
+
262
+// newMapping creates a mappingTable for a profile.
263
+func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
264
+	mt := &mappingTable{
265
+		prof:     prof,
266
+		segments: make(map[*profile.Mapping]plugin.ObjFile),
267
+	}
268
+
269
+	// Identify used mappings
270
+	mappings := make(map[*profile.Mapping]bool)
271
+	for _, l := range prof.Location {
272
+		mappings[l.Mapping] = true
273
+	}
274
+
275
+	for _, m := range prof.Mapping {
276
+		if !mappings[m] {
277
+			continue
278
+		}
279
+
280
+		// Do not attempt to re-symbolize a mapping that has already been symbolized.
281
+		if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
282
+			continue
283
+		}
284
+
285
+		// Skip well-known system mappings
286
+		name := filepath.Base(m.File)
287
+		if name == "" || name == "[vdso]" || strings.HasPrefix(name, "linux-vdso") {
288
+			continue
289
+		}
290
+
291
+		f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
292
+		if err != nil {
293
+			ui.PrintErr("Local symbolization failed for ", name, ": ", err)
294
+			continue
295
+		}
296
+		if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
297
+			ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
298
+			f.Close()
299
+			continue
300
+		}
301
+
302
+		mt.segments[m] = f
303
+	}
304
+
305
+	return mt, nil
306
+}
307
+
308
+// mappingTable contains the mechanisms for symbolization of a
309
+// profile.
310
+type mappingTable struct {
311
+	prof     *profile.Profile
312
+	segments map[*profile.Mapping]plugin.ObjFile
313
+}
314
+
315
+// Close releases any external processes being used for the mapping.
316
+func (mt *mappingTable) close() {
317
+	for _, segment := range mt.segments {
318
+		segment.Close()
319
+	}
320
+}

+ 192
- 0
src/internal/symbolizer/symbolizer_test.go Datei anzeigen

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
+
15
+package symbolizer
16
+
17
+import (
18
+	"fmt"
19
+	"regexp"
20
+	"testing"
21
+
22
+	"internal/plugin"
23
+	"profile"
24
+	"internal/proftest"
25
+)
26
+
27
+var testM = []*profile.Mapping{
28
+	{
29
+		ID:    1,
30
+		Start: 0x1000,
31
+		Limit: 0x5000,
32
+	},
33
+}
34
+
35
+var testL = []*profile.Location{
36
+	{
37
+		ID:      1,
38
+		Mapping: testM[0],
39
+		Address: 1000,
40
+	},
41
+	{
42
+		ID:      2,
43
+		Mapping: testM[0],
44
+		Address: 2000,
45
+	},
46
+	{
47
+		ID:      3,
48
+		Mapping: testM[0],
49
+		Address: 3000,
50
+	},
51
+	{
52
+		ID:      4,
53
+		Mapping: testM[0],
54
+		Address: 4000,
55
+	},
56
+	{
57
+		ID:      5,
58
+		Mapping: testM[0],
59
+		Address: 5000,
60
+	},
61
+}
62
+
63
+var testProfile = profile.Profile{
64
+	DurationNanos: 10e9,
65
+	SampleType: []*profile.ValueType{
66
+		{Type: "cpu", Unit: "cycles"},
67
+	},
68
+	Sample: []*profile.Sample{
69
+		{
70
+			Location: []*profile.Location{testL[0]},
71
+			Value:    []int64{1},
72
+		},
73
+		{
74
+			Location: []*profile.Location{testL[1], testL[0]},
75
+			Value:    []int64{10},
76
+		},
77
+		{
78
+			Location: []*profile.Location{testL[2], testL[0]},
79
+			Value:    []int64{100},
80
+		},
81
+		{
82
+			Location: []*profile.Location{testL[3], testL[0]},
83
+			Value:    []int64{1},
84
+		},
85
+		{
86
+			Location: []*profile.Location{testL[4], testL[3], testL[0]},
87
+			Value:    []int64{10000},
88
+		},
89
+	},
90
+	Location:   testL,
91
+	Mapping:    testM,
92
+	PeriodType: &profile.ValueType{Type: "cpu", Unit: "milliseconds"},
93
+	Period:     10,
94
+}
95
+
96
+func TestSymbolization(t *testing.T) {
97
+	prof := testProfile
98
+
99
+	if prof.HasFunctions() {
100
+		t.Error("unexpected function names")
101
+	}
102
+	if prof.HasFileLines() {
103
+		t.Error("unexpected filenames or line numbers")
104
+	}
105
+
106
+	b := mockObjTool{}
107
+	if err := localSymbolize("", &prof, b, &proftest.TestUI{T: t}); err != nil {
108
+		t.Fatalf("Symbolize(): %v", err)
109
+	}
110
+
111
+	for _, loc := range prof.Location {
112
+		if err := checkSymbolizedLocation(loc.Address, loc.Line); err != nil {
113
+			t.Errorf("location %d: %v", loc.Address, err)
114
+		}
115
+	}
116
+	if !prof.HasFunctions() {
117
+		t.Error("missing function names")
118
+	}
119
+	if !prof.HasFileLines() {
120
+		t.Error("missing filenames or line numbers")
121
+	}
122
+}
123
+
124
+func checkSymbolizedLocation(a uint64, got []profile.Line) error {
125
+	want, ok := mockAddresses[a]
126
+	if !ok {
127
+		return fmt.Errorf("unexpected address")
128
+	}
129
+	if len(want) != len(got) {
130
+		return fmt.Errorf("want len %d, got %d", len(want), len(got))
131
+	}
132
+
133
+	for i, w := range want {
134
+		g := got[i]
135
+		if g.Function.Name != w.Func {
136
+			return fmt.Errorf("want function: %q, got %q", w.Func, g.Function.Name)
137
+		}
138
+		if g.Function.Filename != w.File {
139
+			return fmt.Errorf("want filename: %q, got %q", w.File, g.Function.Filename)
140
+		}
141
+		if g.Line != int64(w.Line) {
142
+			return fmt.Errorf("want lineno: %d, got %d", w.Line, g.Line)
143
+		}
144
+	}
145
+	return nil
146
+}
147
+
148
+var mockAddresses = map[uint64][]plugin.Frame{
149
+	1000: []plugin.Frame{{"fun11", "file11.src", 10}},
150
+	2000: []plugin.Frame{{"fun21", "file21.src", 20}, {"fun22", "file22.src", 20}},
151
+	3000: []plugin.Frame{{"fun31", "file31.src", 30}, {"fun32", "file32.src", 30}, {"fun33", "file33.src", 30}},
152
+	4000: []plugin.Frame{{"fun41", "file41.src", 40}, {"fun42", "file42.src", 40}, {"fun43", "file43.src", 40}, {"fun44", "file44.src", 40}},
153
+	5000: []plugin.Frame{{"fun51", "file51.src", 50}, {"fun52", "file52.src", 50}, {"fun53", "file53.src", 50}, {"fun54", "file54.src", 50}, {"fun55", "file55.src", 50}},
154
+}
155
+
156
+type mockObjTool struct{}
157
+
158
+func (mockObjTool) Open(file string, start, limit, offset uint64) (plugin.ObjFile, error) {
159
+	return mockObjFile{frames: mockAddresses}, nil
160
+}
161
+
162
+func (mockObjTool) Disasm(file string, start, end uint64) ([]plugin.Inst, error) {
163
+	return nil, fmt.Errorf("disassembly not supported")
164
+}
165
+
166
+type mockObjFile struct {
167
+	frames map[uint64][]plugin.Frame
168
+}
169
+
170
+func (mockObjFile) Name() string {
171
+	return ""
172
+}
173
+
174
+func (mockObjFile) Base() uint64 {
175
+	return 0
176
+}
177
+
178
+func (mockObjFile) BuildID() string {
179
+	return ""
180
+}
181
+
182
+func (mf mockObjFile) SourceLine(addr uint64) ([]plugin.Frame, error) {
183
+	return mf.frames[addr], nil
184
+}
185
+
186
+func (mockObjFile) Symbols(r *regexp.Regexp, addr uint64) ([]*plugin.Sym, error) {
187
+	return []*plugin.Sym{}, nil
188
+}
189
+
190
+func (mockObjFile) Close() error {
191
+	return nil
192
+}

+ 161
- 0
src/internal/symbolz/symbolz.go Datei anzeigen

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
+
15
+// Package symbolz symbolizes a profile using the output from the symbolz
16
+// service.
17
+package symbolz
18
+
19
+import (
20
+	"bytes"
21
+	"fmt"
22
+	"io"
23
+	"net/url"
24
+	"path"
25
+	"regexp"
26
+	"strconv"
27
+	"strings"
28
+
29
+	"internal/plugin"
30
+	"profile"
31
+)
32
+
33
+var (
34
+	symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`)
35
+)
36
+
37
+// Symbolize symbolizes profile p by parsing data returned by a
38
+// symbolz handler. syms receives the symbolz query (hex addresses
39
+// separated by '+') and returns the symbolz output in a string.  It
40
+// symbolizes all locations based on their addresses, regardless of
41
+// mapping.
42
+func Symbolize(sources plugin.MappingSources, syms func(string, string) ([]byte, error), p *profile.Profile, ui plugin.UI) error {
43
+	for _, m := range p.Mapping {
44
+		if m.HasFunctions {
45
+			continue
46
+		}
47
+		mappingSources := sources[m.File]
48
+		if m.BuildID != "" {
49
+			mappingSources = append(mappingSources, sources[m.BuildID]...)
50
+		}
51
+		for _, source := range mappingSources {
52
+			if symz := symbolz(source.Source); symz != "" {
53
+				if err := symbolizeMapping(symz, int64(source.Start)-int64(m.Start), syms, m, p); err != nil {
54
+					return err
55
+				}
56
+				m.HasFunctions = true
57
+				break
58
+			}
59
+		}
60
+	}
61
+
62
+	return nil
63
+}
64
+
65
+// symbolz returns the corresponding symbolz source for a profile URL.
66
+func symbolz(source string) string {
67
+	if url, err := url.Parse(source); err == nil && url.Host != "" {
68
+		if strings.Contains(url.Path, "/") {
69
+			if dir := path.Dir(url.Path); dir == "/debug/pprof" {
70
+				// For Go language profile handlers in net/http/pprof package.
71
+				url.Path = "/debug/pprof/symbol"
72
+			} else {
73
+				url.Path = "/symbolz"
74
+			}
75
+			url.RawQuery = ""
76
+			return url.String()
77
+		}
78
+	}
79
+
80
+	return ""
81
+}
82
+
83
+// symbolizeMapping symbolizes locations belonging to a Mapping by querying
84
+// a symbolz handler. An offset is applied to all addresses to take care of
85
+// normalization occured for merged Mappings.
86
+func symbolizeMapping(source string, offset int64, syms func(string, string) ([]byte, error), m *profile.Mapping, p *profile.Profile) error {
87
+	// Construct query of addresses to symbolize.
88
+	var a []string
89
+	for _, l := range p.Location {
90
+		if l.Mapping == m && l.Address != 0 && len(l.Line) == 0 {
91
+			// Compensate for normalization.
92
+			addr := int64(l.Address) + offset
93
+			if addr < 0 {
94
+				return fmt.Errorf("unexpected negative adjusted address, mapping %v source %d, offset %d", l.Mapping, l.Address, offset)
95
+			}
96
+			a = append(a, fmt.Sprintf("%#x", addr))
97
+		}
98
+	}
99
+
100
+	if len(a) == 0 {
101
+		// No addresses to symbolize.
102
+		return nil
103
+	}
104
+
105
+	lines := make(map[uint64]profile.Line)
106
+	functions := make(map[string]*profile.Function)
107
+
108
+	b, err := syms(source, strings.Join(a, "+"))
109
+	if err != nil {
110
+		return err
111
+	}
112
+
113
+	buf := bytes.NewBuffer(b)
114
+	for {
115
+		l, err := buf.ReadString('\n')
116
+
117
+		if err != nil {
118
+			if err == io.EOF {
119
+				break
120
+			}
121
+			return err
122
+		}
123
+
124
+		if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 {
125
+			addr, err := strconv.ParseInt(symbol[1], 0, 64)
126
+			if err != nil {
127
+				return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err)
128
+			}
129
+			if addr < 0 {
130
+				return fmt.Errorf("unexpected negative adjusted address, source %s, offset %d", symbol[1], offset)
131
+			}
132
+			// Reapply offset expected by the profile.
133
+			addr -= offset
134
+
135
+			name := symbol[2]
136
+			fn := functions[name]
137
+			if fn == nil {
138
+				fn = &profile.Function{
139
+					ID:         uint64(len(p.Function) + 1),
140
+					Name:       name,
141
+					SystemName: name,
142
+				}
143
+				functions[name] = fn
144
+				p.Function = append(p.Function, fn)
145
+			}
146
+
147
+			lines[uint64(addr)] = profile.Line{Function: fn}
148
+		}
149
+	}
150
+
151
+	for _, l := range p.Location {
152
+		if l.Mapping != m {
153
+			continue
154
+		}
155
+		if line, ok := lines[l.Address]; ok {
156
+			l.Line = []profile.Line{line}
157
+		}
158
+	}
159
+
160
+	return nil
161
+}

+ 100
- 0
src/internal/symbolz/symbolz_test.go Datei anzeigen

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
+
15
+package symbolz
16
+
17
+import (
18
+	"fmt"
19
+	"strings"
20
+	"testing"
21
+
22
+	"internal/plugin"
23
+	"profile"
24
+	"internal/proftest"
25
+)
26
+
27
+func TestSymbolzURL(t *testing.T) {
28
+	for try, want := range map[string]string{
29
+		"http://host:8000/profilez":                        "http://host:8000/symbolz",
30
+		"http://host:8000/profilez?seconds=5":              "http://host:8000/symbolz",
31
+		"http://host:8000/profilez?seconds=5&format=proto": "http://host:8000/symbolz",
32
+		"http://host:8000/heapz?format=legacy":             "http://host:8000/symbolz",
33
+		"http://host:8000/debug/pprof/profile":             "http://host:8000/debug/pprof/symbol",
34
+		"http://host:8000/debug/pprof/profile?seconds=10":  "http://host:8000/debug/pprof/symbol",
35
+		"http://host:8000/debug/pprof/heap":                "http://host:8000/debug/pprof/symbol",
36
+	} {
37
+		if got := symbolz(try); got != want {
38
+			t.Errorf(`symbolz(%s)=%s, want "%s"`, try, got, want)
39
+		}
40
+	}
41
+}
42
+
43
+func TestSymbolize(t *testing.T) {
44
+	m := []*profile.Mapping{
45
+		{
46
+			ID:      1,
47
+			Start:   0x1000,
48
+			Limit:   0x5000,
49
+			BuildID: "buildid",
50
+		},
51
+	}
52
+	p := &profile.Profile{
53
+		Location: []*profile.Location{
54
+			{ID: 1, Mapping: m[0], Address: 0x1000},
55
+			{ID: 2, Mapping: m[0], Address: 0x2000},
56
+			{ID: 3, Mapping: m[0], Address: 0x3000},
57
+			{ID: 4, Mapping: m[0], Address: 0x4000},
58
+		},
59
+		Mapping: m,
60
+	}
61
+
62
+	s := plugin.MappingSources{
63
+		"buildid": []struct {
64
+			Source string
65
+			Start  uint64
66
+		}{
67
+			{Source: "http://localhost:80/profilez"},
68
+		},
69
+	}
70
+
71
+	if err := Symbolize(s, fetchSymbols, p, &proftest.TestUI{T: t}); err != nil {
72
+		t.Errorf("symbolz: %v", err)
73
+	}
74
+
75
+	if l := p.Location[0]; len(l.Line) != 0 {
76
+		t.Errorf("unexpected symbolization for %#x: %v", l.Address, l.Line)
77
+	}
78
+
79
+	for _, l := range p.Location[1:] {
80
+		if len(l.Line) != 1 {
81
+			t.Errorf("failed to symbolize %#x", l.Address)
82
+			continue
83
+		}
84
+		address := l.Address - l.Mapping.Start
85
+		if got, want := l.Line[0].Function.Name, fmt.Sprintf("%#x", address); got != want {
86
+			t.Errorf("symbolz %#x, got %s, want %s", address, got, want)
87
+		}
88
+	}
89
+}
90
+
91
+func fetchSymbols(source, post string) ([]byte, error) {
92
+	var symbolz string
93
+
94
+	addresses := strings.Split(post, "+")
95
+	// Do not symbolize the first symbol.
96
+	for _, address := range addresses[1:] {
97
+		symbolz += fmt.Sprintf("%s\t%s\n", address, address)
98
+	}
99
+	return []byte(symbolz), nil
100
+}

+ 30
- 0
src/pprof/pprof.go Datei anzeigen

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
+
15
+// pprof is a tool for collection, manipulation and visualization
16
+// of performance profiles.
17
+package main
18
+
19
+import (
20
+	"fmt"
21
+	"os"
22
+
23
+	"driver"
24
+)
25
+
26
+func main() {
27
+	if err := driver.PProf(&driver.Options{}); err != nil {
28
+		fmt.Fprintf(os.Stderr, "pprof: %v\n", err)
29
+	}
30
+}

+ 493
- 0
src/profile/encode.go Datei anzeigen

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
+
15
+package profile
16
+
17
+import (
18
+	"errors"
19
+	"fmt"
20
+	"sort"
21
+)
22
+
23
+func (p *Profile) decoder() []decoder {
24
+	return profileDecoder
25
+}
26
+
27
+// preEncode populates the unexported fields to be used by encode
28
+// (with suffix X) from the corresponding exported fields.  The
29
+// exported fields are cleared up to facilitate testing.
30
+func (p *Profile) preEncode() {
31
+	strings := make(map[string]int)
32
+	addString(strings, "")
33
+
34
+	for _, st := range p.SampleType {
35
+		st.typeX = addString(strings, st.Type)
36
+		st.unitX = addString(strings, st.Unit)
37
+	}
38
+
39
+	for _, s := range p.Sample {
40
+		s.labelX = nil
41
+		var keys []string
42
+		for k := range s.Label {
43
+			keys = append(keys, k)
44
+		}
45
+		sort.Strings(keys)
46
+		for _, k := range keys {
47
+			vs := s.Label[k]
48
+			for _, v := range vs {
49
+				s.labelX = append(s.labelX,
50
+					label{
51
+						keyX: addString(strings, k),
52
+						strX: addString(strings, v),
53
+					},
54
+				)
55
+			}
56
+		}
57
+		var numKeys []string
58
+		for k := range s.NumLabel {
59
+			numKeys = append(numKeys, k)
60
+		}
61
+		sort.Strings(numKeys)
62
+		for _, k := range numKeys {
63
+			vs := s.NumLabel[k]
64
+			for _, v := range vs {
65
+				s.labelX = append(s.labelX,
66
+					label{
67
+						keyX: addString(strings, k),
68
+						numX: v,
69
+					},
70
+				)
71
+			}
72
+		}
73
+		s.locationIDX = nil
74
+		for _, l := range s.Location {
75
+			s.locationIDX = append(s.locationIDX, l.ID)
76
+		}
77
+	}
78
+
79
+	for _, m := range p.Mapping {
80
+		m.fileX = addString(strings, m.File)
81
+		m.buildIDX = addString(strings, m.BuildID)
82
+	}
83
+
84
+	for _, l := range p.Location {
85
+		for i, ln := range l.Line {
86
+			if ln.Function != nil {
87
+				l.Line[i].functionIDX = ln.Function.ID
88
+			} else {
89
+				l.Line[i].functionIDX = 0
90
+			}
91
+		}
92
+		if l.Mapping != nil {
93
+			l.mappingIDX = l.Mapping.ID
94
+		} else {
95
+			l.mappingIDX = 0
96
+		}
97
+	}
98
+	for _, f := range p.Function {
99
+		f.nameX = addString(strings, f.Name)
100
+		f.systemNameX = addString(strings, f.SystemName)
101
+		f.filenameX = addString(strings, f.Filename)
102
+	}
103
+
104
+	p.dropFramesX = addString(strings, p.DropFrames)
105
+	p.keepFramesX = addString(strings, p.KeepFrames)
106
+
107
+	if pt := p.PeriodType; pt != nil {
108
+		pt.typeX = addString(strings, pt.Type)
109
+		pt.unitX = addString(strings, pt.Unit)
110
+	}
111
+
112
+	p.commentX = nil
113
+	for _, c := range p.Comments {
114
+		p.commentX = append(p.commentX, addString(strings, c))
115
+	}
116
+
117
+	p.stringTable = make([]string, len(strings))
118
+	for s, i := range strings {
119
+		p.stringTable[i] = s
120
+	}
121
+}
122
+
123
+func (p *Profile) encode(b *buffer) {
124
+	for _, x := range p.SampleType {
125
+		encodeMessage(b, 1, x)
126
+	}
127
+	for _, x := range p.Sample {
128
+		encodeMessage(b, 2, x)
129
+	}
130
+	for _, x := range p.Mapping {
131
+		encodeMessage(b, 3, x)
132
+	}
133
+	for _, x := range p.Location {
134
+		encodeMessage(b, 4, x)
135
+	}
136
+	for _, x := range p.Function {
137
+		encodeMessage(b, 5, x)
138
+	}
139
+	encodeStrings(b, 6, p.stringTable)
140
+	encodeInt64Opt(b, 7, p.dropFramesX)
141
+	encodeInt64Opt(b, 8, p.keepFramesX)
142
+	encodeInt64Opt(b, 9, p.TimeNanos)
143
+	encodeInt64Opt(b, 10, p.DurationNanos)
144
+	if pt := p.PeriodType; pt != nil && (pt.typeX != 0 || pt.unitX != 0) {
145
+		encodeMessage(b, 11, p.PeriodType)
146
+	}
147
+	encodeInt64Opt(b, 12, p.Period)
148
+	encodeInt64s(b, 13, p.commentX)
149
+}
150
+
151
+var profileDecoder = []decoder{
152
+	nil, // 0
153
+	// repeated ValueType sample_type = 1
154
+	func(b *buffer, m message) error {
155
+		x := new(ValueType)
156
+		pp := m.(*Profile)
157
+		pp.SampleType = append(pp.SampleType, x)
158
+		return decodeMessage(b, x)
159
+	},
160
+	// repeated Sample sample = 2
161
+	func(b *buffer, m message) error {
162
+		x := new(Sample)
163
+		pp := m.(*Profile)
164
+		pp.Sample = append(pp.Sample, x)
165
+		return decodeMessage(b, x)
166
+	},
167
+	// repeated Mapping mapping = 3
168
+	func(b *buffer, m message) error {
169
+		x := new(Mapping)
170
+		pp := m.(*Profile)
171
+		pp.Mapping = append(pp.Mapping, x)
172
+		return decodeMessage(b, x)
173
+	},
174
+	// repeated Location location = 4
175
+	func(b *buffer, m message) error {
176
+		x := new(Location)
177
+		pp := m.(*Profile)
178
+		pp.Location = append(pp.Location, x)
179
+		return decodeMessage(b, x)
180
+	},
181
+	// repeated Function function = 5
182
+	func(b *buffer, m message) error {
183
+		x := new(Function)
184
+		pp := m.(*Profile)
185
+		pp.Function = append(pp.Function, x)
186
+		return decodeMessage(b, x)
187
+	},
188
+	// repeated string string_table = 6
189
+	func(b *buffer, m message) error {
190
+		err := decodeStrings(b, &m.(*Profile).stringTable)
191
+		if err != nil {
192
+			return err
193
+		}
194
+		if *&m.(*Profile).stringTable[0] != "" {
195
+			return errors.New("string_table[0] must be ''")
196
+		}
197
+		return nil
198
+	},
199
+	// int64 drop_frames = 7
200
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).dropFramesX) },
201
+	// int64 keep_frames = 8
202
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).keepFramesX) },
203
+	// int64 time_nanos = 9
204
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).TimeNanos) },
205
+	// int64 duration_nanos = 10
206
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).DurationNanos) },
207
+	// ValueType period_type = 11
208
+	func(b *buffer, m message) error {
209
+		x := new(ValueType)
210
+		pp := m.(*Profile)
211
+		pp.PeriodType = x
212
+		return decodeMessage(b, x)
213
+	},
214
+	// int64 period = 12
215
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Profile).Period) },
216
+	// repeated int64 comment = 13
217
+	func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Profile).commentX) },
218
+}
219
+
220
+// postDecode takes the unexported fields populated by decode (with
221
+// suffix X) and populates the corresponding exported fields.
222
+// The unexported fields are cleared up to facilitate testing.
223
+func (p *Profile) postDecode() error {
224
+	var err error
225
+	mappings := make(map[uint64]*Mapping)
226
+	for _, m := range p.Mapping {
227
+		m.File, err = getString(p.stringTable, &m.fileX, err)
228
+		m.BuildID, err = getString(p.stringTable, &m.buildIDX, err)
229
+		mappings[m.ID] = m
230
+	}
231
+
232
+	functions := make(map[uint64]*Function)
233
+	for _, f := range p.Function {
234
+		f.Name, err = getString(p.stringTable, &f.nameX, err)
235
+		f.SystemName, err = getString(p.stringTable, &f.systemNameX, err)
236
+		f.Filename, err = getString(p.stringTable, &f.filenameX, err)
237
+		functions[f.ID] = f
238
+	}
239
+
240
+	locations := make(map[uint64]*Location)
241
+	for _, l := range p.Location {
242
+		l.Mapping = mappings[l.mappingIDX]
243
+		l.mappingIDX = 0
244
+		for i, ln := range l.Line {
245
+			if id := ln.functionIDX; id != 0 {
246
+				l.Line[i].Function = functions[id]
247
+				if l.Line[i].Function == nil {
248
+					return fmt.Errorf("Function ID %d not found", id)
249
+				}
250
+				l.Line[i].functionIDX = 0
251
+			}
252
+		}
253
+		locations[l.ID] = l
254
+	}
255
+
256
+	for _, st := range p.SampleType {
257
+		st.Type, err = getString(p.stringTable, &st.typeX, err)
258
+		st.Unit, err = getString(p.stringTable, &st.unitX, err)
259
+	}
260
+
261
+	for _, s := range p.Sample {
262
+		labels := make(map[string][]string)
263
+		numLabels := make(map[string][]int64)
264
+		for _, l := range s.labelX {
265
+			var key, value string
266
+			key, err = getString(p.stringTable, &l.keyX, err)
267
+			if l.strX != 0 {
268
+				value, err = getString(p.stringTable, &l.strX, err)
269
+				labels[key] = append(labels[key], value)
270
+			} else if l.numX != 0 {
271
+				numLabels[key] = append(numLabels[key], l.numX)
272
+			}
273
+		}
274
+		if len(labels) > 0 {
275
+			s.Label = labels
276
+		}
277
+		if len(numLabels) > 0 {
278
+			s.NumLabel = numLabels
279
+		}
280
+		s.Location = nil
281
+		for _, lid := range s.locationIDX {
282
+			s.Location = append(s.Location, locations[lid])
283
+		}
284
+		s.locationIDX = nil
285
+	}
286
+
287
+	p.DropFrames, err = getString(p.stringTable, &p.dropFramesX, err)
288
+	p.KeepFrames, err = getString(p.stringTable, &p.keepFramesX, err)
289
+
290
+	if pt := p.PeriodType; pt == nil {
291
+		p.PeriodType = &ValueType{}
292
+	}
293
+
294
+	if pt := p.PeriodType; pt != nil {
295
+		pt.Type, err = getString(p.stringTable, &pt.typeX, err)
296
+		pt.Unit, err = getString(p.stringTable, &pt.unitX, err)
297
+	}
298
+
299
+	for _, i := range p.commentX {
300
+		var c string
301
+		c, err = getString(p.stringTable, &i, err)
302
+		p.Comments = append(p.Comments, c)
303
+	}
304
+
305
+	p.commentX = nil
306
+	p.stringTable = nil
307
+	return err
308
+}
309
+
310
+func (p *ValueType) decoder() []decoder {
311
+	return valueTypeDecoder
312
+}
313
+
314
+func (p *ValueType) encode(b *buffer) {
315
+	encodeInt64Opt(b, 1, p.typeX)
316
+	encodeInt64Opt(b, 2, p.unitX)
317
+}
318
+
319
+var valueTypeDecoder = []decoder{
320
+	nil, // 0
321
+	// optional int64 type = 1
322
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).typeX) },
323
+	// optional int64 unit = 2
324
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*ValueType).unitX) },
325
+}
326
+
327
+func (p *Sample) decoder() []decoder {
328
+	return sampleDecoder
329
+}
330
+
331
+func (p *Sample) encode(b *buffer) {
332
+	encodeUint64s(b, 1, p.locationIDX)
333
+	encodeInt64s(b, 2, p.Value)
334
+	for _, x := range p.labelX {
335
+		encodeMessage(b, 3, x)
336
+	}
337
+}
338
+
339
+var sampleDecoder = []decoder{
340
+	nil, // 0
341
+	// repeated uint64 location = 1
342
+	func(b *buffer, m message) error { return decodeUint64s(b, &m.(*Sample).locationIDX) },
343
+	// repeated int64 value = 2
344
+	func(b *buffer, m message) error { return decodeInt64s(b, &m.(*Sample).Value) },
345
+	// repeated Label label = 3
346
+	func(b *buffer, m message) error {
347
+		s := m.(*Sample)
348
+		n := len(s.labelX)
349
+		s.labelX = append(s.labelX, label{})
350
+		return decodeMessage(b, &s.labelX[n])
351
+	},
352
+}
353
+
354
+func (p label) decoder() []decoder {
355
+	return labelDecoder
356
+}
357
+
358
+func (p label) encode(b *buffer) {
359
+	encodeInt64Opt(b, 1, p.keyX)
360
+	encodeInt64Opt(b, 2, p.strX)
361
+	encodeInt64Opt(b, 3, p.numX)
362
+}
363
+
364
+var labelDecoder = []decoder{
365
+	nil, // 0
366
+	// optional int64 key = 1
367
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).keyX) },
368
+	// optional int64 str = 2
369
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).strX) },
370
+	// optional int64 num = 3
371
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*label).numX) },
372
+}
373
+
374
+func (p *Mapping) decoder() []decoder {
375
+	return mappingDecoder
376
+}
377
+
378
+func (p *Mapping) encode(b *buffer) {
379
+	encodeUint64Opt(b, 1, p.ID)
380
+	encodeUint64Opt(b, 2, p.Start)
381
+	encodeUint64Opt(b, 3, p.Limit)
382
+	encodeUint64Opt(b, 4, p.Offset)
383
+	encodeInt64Opt(b, 5, p.fileX)
384
+	encodeInt64Opt(b, 6, p.buildIDX)
385
+	encodeBoolOpt(b, 7, p.HasFunctions)
386
+	encodeBoolOpt(b, 8, p.HasFilenames)
387
+	encodeBoolOpt(b, 9, p.HasLineNumbers)
388
+	encodeBoolOpt(b, 10, p.HasInlineFrames)
389
+}
390
+
391
+var mappingDecoder = []decoder{
392
+	nil, // 0
393
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).ID) },            // optional uint64 id = 1
394
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Start) },         // optional uint64 memory_offset = 2
395
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Limit) },         // optional uint64 memory_limit = 3
396
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Mapping).Offset) },        // optional uint64 file_offset = 4
397
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).fileX) },          // optional int64 filename = 5
398
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Mapping).buildIDX) },       // optional int64 build_id = 6
399
+	func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFunctions) },    // optional bool has_functions = 7
400
+	func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasFilenames) },    // optional bool has_filenames = 8
401
+	func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasLineNumbers) },  // optional bool has_line_numbers = 9
402
+	func(b *buffer, m message) error { return decodeBool(b, &m.(*Mapping).HasInlineFrames) }, // optional bool has_inline_frames = 10
403
+}
404
+
405
+func (p *Location) decoder() []decoder {
406
+	return locationDecoder
407
+}
408
+
409
+func (p *Location) encode(b *buffer) {
410
+	encodeUint64Opt(b, 1, p.ID)
411
+	encodeUint64Opt(b, 2, p.mappingIDX)
412
+	encodeUint64Opt(b, 3, p.Address)
413
+	for i := range p.Line {
414
+		encodeMessage(b, 4, &p.Line[i])
415
+	}
416
+}
417
+
418
+var locationDecoder = []decoder{
419
+	nil, // 0
420
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).ID) },         // optional uint64 id = 1;
421
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).mappingIDX) }, // optional uint64 mapping_id = 2;
422
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Location).Address) },    // optional uint64 address = 3;
423
+	func(b *buffer, m message) error { // repeated Line line = 4
424
+		pp := m.(*Location)
425
+		n := len(pp.Line)
426
+		pp.Line = append(pp.Line, Line{})
427
+		return decodeMessage(b, &pp.Line[n])
428
+	},
429
+}
430
+
431
+func (p *Line) decoder() []decoder {
432
+	return lineDecoder
433
+}
434
+
435
+func (p *Line) encode(b *buffer) {
436
+	encodeUint64Opt(b, 1, p.functionIDX)
437
+	encodeInt64Opt(b, 2, p.Line)
438
+}
439
+
440
+var lineDecoder = []decoder{
441
+	nil, // 0
442
+	// optional uint64 function_id = 1
443
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Line).functionIDX) },
444
+	// optional int64 line = 2
445
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Line).Line) },
446
+}
447
+
448
+func (p *Function) decoder() []decoder {
449
+	return functionDecoder
450
+}
451
+
452
+func (p *Function) encode(b *buffer) {
453
+	encodeUint64Opt(b, 1, p.ID)
454
+	encodeInt64Opt(b, 2, p.nameX)
455
+	encodeInt64Opt(b, 3, p.systemNameX)
456
+	encodeInt64Opt(b, 4, p.filenameX)
457
+	encodeInt64Opt(b, 5, p.StartLine)
458
+}
459
+
460
+var functionDecoder = []decoder{
461
+	nil, // 0
462
+	// optional uint64 id = 1
463
+	func(b *buffer, m message) error { return decodeUint64(b, &m.(*Function).ID) },
464
+	// optional int64 function_name = 2
465
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).nameX) },
466
+	// optional int64 function_system_name = 3
467
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).systemNameX) },
468
+	// repeated int64 filename = 4
469
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).filenameX) },
470
+	// optional int64 start_line = 5
471
+	func(b *buffer, m message) error { return decodeInt64(b, &m.(*Function).StartLine) },
472
+}
473
+
474
+func addString(strings map[string]int, s string) int64 {
475
+	i, ok := strings[s]
476
+	if !ok {
477
+		i = len(strings)
478
+		strings[s] = i
479
+	}
480
+	return int64(i)
481
+}
482
+
483
+func getString(strings []string, strng *int64, err error) (string, error) {
484
+	if err != nil {
485
+		return "", err
486
+	}
487
+	s := int(*strng)
488
+	if s < 0 || s >= len(strings) {
489
+		return "", errMalformed
490
+	}
491
+	*strng = 0
492
+	return strings[s], nil
493
+}

+ 171
- 0
src/profile/filter.go Datei anzeigen

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
+
15
+package profile
16
+
17
+// Implements methods to filter samples from profiles.
18
+
19
+import "regexp"
20
+
21
+// FilterSamplesByName filters the samples in a profile and only keeps
22
+// samples where at least one frame matches focus but none match ignore.
23
+// Returns true is the corresponding regexp matched at least one sample.
24
+func (p *Profile) FilterSamplesByName(focus, ignore, hide, show *regexp.Regexp) (fm, im, hm, hnm bool) {
25
+	focusOrIgnore := make(map[uint64]bool)
26
+	hidden := make(map[uint64]bool)
27
+	for _, l := range p.Location {
28
+		if ignore != nil && l.matchesName(ignore) {
29
+			im = true
30
+			focusOrIgnore[l.ID] = false
31
+		} else if focus == nil || l.matchesName(focus) {
32
+			fm = true
33
+			focusOrIgnore[l.ID] = true
34
+		}
35
+
36
+		if hide != nil && l.matchesName(hide) {
37
+			hm = true
38
+			l.Line = l.unmatchedLines(hide)
39
+			if len(l.Line) == 0 {
40
+				hidden[l.ID] = true
41
+			}
42
+		}
43
+		if show != nil {
44
+			hnm = true
45
+			l.Line = l.matchedLines(show)
46
+			if len(l.Line) == 0 {
47
+				hidden[l.ID] = true
48
+			}
49
+		}
50
+	}
51
+
52
+	s := make([]*Sample, 0, len(p.Sample))
53
+	for _, sample := range p.Sample {
54
+		if focusedAndNotIgnored(sample.Location, focusOrIgnore) {
55
+			if len(hidden) > 0 {
56
+				var locs []*Location
57
+				for _, loc := range sample.Location {
58
+					if !hidden[loc.ID] {
59
+						locs = append(locs, loc)
60
+					}
61
+				}
62
+				if len(locs) == 0 {
63
+					// Remove sample with no locations (by not adding it to s).
64
+					continue
65
+				}
66
+				sample.Location = locs
67
+			}
68
+			s = append(s, sample)
69
+		}
70
+	}
71
+	p.Sample = s
72
+
73
+	return
74
+}
75
+
76
+// matchesName returns whether the location matches the regular
77
+// expression. It checks any available function names, file names, and
78
+// mapping object filename.
79
+func (loc *Location) matchesName(re *regexp.Regexp) bool {
80
+	for _, ln := range loc.Line {
81
+		if fn := ln.Function; fn != nil {
82
+			if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
83
+				return true
84
+			}
85
+		}
86
+	}
87
+	if m := loc.Mapping; m != nil && re.MatchString(m.File) {
88
+		return true
89
+	}
90
+	return false
91
+}
92
+
93
+// unmatchedLines returns the lines in the location that do not match
94
+// the regular expression.
95
+func (loc *Location) unmatchedLines(re *regexp.Regexp) []Line {
96
+	if m := loc.Mapping; m != nil && re.MatchString(m.File) {
97
+		return nil
98
+	}
99
+	var lines []Line
100
+	for _, ln := range loc.Line {
101
+		if fn := ln.Function; fn != nil {
102
+			if re.MatchString(fn.Name) || re.MatchString(fn.Filename) {
103
+				continue
104
+			}
105
+		}
106
+		lines = append(lines, ln)
107
+	}
108
+	return lines
109
+}
110
+
111
+// matchedLines returns the lines in the location that match
112
+// the regular expression.
113
+func (loc *Location) matchedLines(re *regexp.Regexp) []Line {
114
+	var lines []Line
115
+	for _, ln := range loc.Line {
116
+		if fn := ln.Function; fn != nil {
117
+			if !re.MatchString(fn.Name) && !re.MatchString(fn.Filename) {
118
+				continue
119
+			}
120
+		}
121
+		lines = append(lines, ln)
122
+	}
123
+	return lines
124
+}
125
+
126
+// focusedAndNotIgnored looks up a slice of ids against a map of
127
+// focused/ignored locations. The map only contains locations that are
128
+// explicitly focused or ignored. Returns whether there is at least
129
+// one focused location but no ignored locations.
130
+func focusedAndNotIgnored(locs []*Location, m map[uint64]bool) bool {
131
+	var f bool
132
+	for _, loc := range locs {
133
+		if focus, focusOrIgnore := m[loc.ID]; focusOrIgnore {
134
+			if focus {
135
+				// Found focused location. Must keep searching in case there
136
+				// is an ignored one as well.
137
+				f = true
138
+			} else {
139
+				// Found ignored location. Can return false right away.
140
+				return false
141
+			}
142
+		}
143
+	}
144
+	return f
145
+}
146
+
147
+// TagMatch selects tags for filtering
148
+type TagMatch func(s *Sample) bool
149
+
150
+// FilterSamplesByTag removes all samples from the profile, except
151
+// those that match focus and do not match the ignore regular
152
+// expression.
153
+func (p *Profile) FilterSamplesByTag(focus, ignore TagMatch) (fm, im bool) {
154
+	samples := make([]*Sample, 0, len(p.Sample))
155
+	for _, s := range p.Sample {
156
+		focused, ignored := true, false
157
+		if focus != nil {
158
+			focused = focus(s)
159
+		}
160
+		if ignore != nil {
161
+			ignored = ignore(s)
162
+		}
163
+		fm = fm || focused
164
+		im = im || ignored
165
+		if focused && !ignored {
166
+			samples = append(samples, s)
167
+		}
168
+	}
169
+	p.Sample = samples
170
+	return
171
+}

+ 308
- 0
src/profile/legacy_java_profile.go Datei anzeigen

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
+
15
+// This file implements parsers to convert java legacy profiles into
16
+// the profile.proto format.
17
+
18
+package profile
19
+
20
+import (
21
+	"bytes"
22
+	"fmt"
23
+	"io"
24
+	"path/filepath"
25
+	"regexp"
26
+	"strconv"
27
+	"strings"
28
+)
29
+
30
+var (
31
+	attributeRx            = regexp.MustCompile(`([\w ]+)=([\w ]+)`)
32
+	javaSampleRx           = regexp.MustCompile(` *(\d+) +(\d+) +@ +([ x0-9a-f]*)`)
33
+	javaLocationRx         = regexp.MustCompile(`^\s*0x([[:xdigit:]]+)\s+(.*)\s*$`)
34
+	javaLocationFileLineRx = regexp.MustCompile(`^(.*)\s+\((.+):(-?[[:digit:]]+)\)$`)
35
+	javaLocationPathRx     = regexp.MustCompile(`^(.*)\s+\((.*)\)$`)
36
+)
37
+
38
+// javaCPUProfile returns a new Profile from profilez data.
39
+// b is the profile bytes after the header, period is the profiling
40
+// period, and parse is a function to parse 8-byte chunks from the
41
+// profile in its native endianness.
42
+func javaCPUProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) {
43
+	p := &Profile{
44
+		Period:     period * 1000,
45
+		PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},
46
+		SampleType: []*ValueType{{Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}},
47
+	}
48
+	var err error
49
+	var locs map[uint64]*Location
50
+	if b, locs, err = parseCPUSamples(b, parse, false, p); err != nil {
51
+		return nil, err
52
+	}
53
+
54
+	if err = parseJavaLocations(b, locs, p); err != nil {
55
+		return nil, err
56
+	}
57
+
58
+	// Strip out addresses for better merge.
59
+	if err = p.Aggregate(true, true, true, true, false); err != nil {
60
+		return nil, err
61
+	}
62
+
63
+	return p, nil
64
+}
65
+
66
+// parseJavaProfile returns a new profile from heapz or contentionz
67
+// data. b is the profile bytes after the header.
68
+func parseJavaProfile(b []byte) (*Profile, error) {
69
+	h := bytes.SplitAfterN(b, []byte("\n"), 2)
70
+	if len(h) < 2 {
71
+		return nil, errUnrecognized
72
+	}
73
+
74
+	p := &Profile{
75
+		PeriodType: &ValueType{},
76
+	}
77
+	header := string(bytes.TrimSpace(h[0]))
78
+
79
+	var err error
80
+	var pType string
81
+	switch header {
82
+	case "--- heapz 1 ---":
83
+		pType = "heap"
84
+	case "--- contentionz 1 ---":
85
+		pType = "contention"
86
+	default:
87
+		return nil, errUnrecognized
88
+	}
89
+
90
+	if b, err = parseJavaHeader(pType, h[1], p); err != nil {
91
+		return nil, err
92
+	}
93
+	var locs map[uint64]*Location
94
+	if b, locs, err = parseJavaSamples(pType, b, p); err != nil {
95
+		return nil, err
96
+	}
97
+	if err = parseJavaLocations(b, locs, p); err != nil {
98
+		return nil, err
99
+	}
100
+
101
+	// Strip out addresses for better merge.
102
+	if err = p.Aggregate(true, true, true, true, false); err != nil {
103
+		return nil, err
104
+	}
105
+
106
+	return p, nil
107
+}
108
+
109
+// parseJavaHeader parses the attribute section on a java profile and
110
+// populates a profile. Returns the remainder of the buffer after all
111
+// attributes.
112
+func parseJavaHeader(pType string, b []byte, p *Profile) ([]byte, error) {
113
+	nextNewLine := bytes.IndexByte(b, byte('\n'))
114
+	for nextNewLine != -1 {
115
+		line := string(bytes.TrimSpace(b[0:nextNewLine]))
116
+		if line != "" {
117
+			h := attributeRx.FindStringSubmatch(line)
118
+			if h == nil {
119
+				// Not a valid attribute, exit.
120
+				return b, nil
121
+			}
122
+
123
+			attribute, value := strings.TrimSpace(h[1]), strings.TrimSpace(h[2])
124
+			var err error
125
+			switch pType + "/" + attribute {
126
+			case "heap/format", "cpu/format", "contention/format":
127
+				if value != "java" {
128
+					return nil, errUnrecognized
129
+				}
130
+			case "heap/resolution":
131
+				p.SampleType = []*ValueType{
132
+					{Type: "inuse_objects", Unit: "count"},
133
+					{Type: "inuse_space", Unit: value},
134
+				}
135
+			case "contention/resolution":
136
+				p.SampleType = []*ValueType{
137
+					{Type: "contentions", Unit: value},
138
+					{Type: "delay", Unit: value},
139
+				}
140
+			case "contention/sampling period":
141
+				p.PeriodType = &ValueType{
142
+					Type: "contentions", Unit: "count",
143
+				}
144
+				if p.Period, err = strconv.ParseInt(value, 0, 64); err != nil {
145
+					return nil, fmt.Errorf("failed to parse attribute %s: %v", line, err)
146
+				}
147
+			case "contention/ms since reset":
148
+				millis, err := strconv.ParseInt(value, 0, 64)
149
+				if err != nil {
150
+					return nil, fmt.Errorf("failed to parse attribute %s: %v", line, err)
151
+				}
152
+				p.DurationNanos = millis * 1000 * 1000
153
+			default:
154
+				return nil, errUnrecognized
155
+			}
156
+		}
157
+		// Grab next line.
158
+		b = b[nextNewLine+1:]
159
+		nextNewLine = bytes.IndexByte(b, byte('\n'))
160
+	}
161
+	return b, nil
162
+}
163
+
164
+// parseJavaSamples parses the samples from a java profile and
165
+// populates the Samples in a profile. Returns the remainder of the
166
+// buffer after the samples.
167
+func parseJavaSamples(pType string, b []byte, p *Profile) ([]byte, map[uint64]*Location, error) {
168
+	nextNewLine := bytes.IndexByte(b, byte('\n'))
169
+	locs := make(map[uint64]*Location)
170
+	for nextNewLine != -1 {
171
+		line := string(bytes.TrimSpace(b[0:nextNewLine]))
172
+		if line != "" {
173
+			sample := javaSampleRx.FindStringSubmatch(line)
174
+			if sample == nil {
175
+				// Not a valid sample, exit.
176
+				return b, locs, nil
177
+			}
178
+
179
+			// Java profiles have data/fields inverted compared to other
180
+			// profile types.
181
+			value1, value2, addrs := sample[2], sample[1], sample[3]
182
+
183
+			var sloc []*Location
184
+			for _, addr := range parseHexAddresses(addrs) {
185
+				loc := locs[addr]
186
+				if locs[addr] == nil {
187
+					loc = &Location{
188
+						Address: addr,
189
+					}
190
+					p.Location = append(p.Location, loc)
191
+					locs[addr] = loc
192
+				}
193
+				sloc = append(sloc, loc)
194
+			}
195
+			s := &Sample{
196
+				Value:    make([]int64, 2),
197
+				Location: sloc,
198
+			}
199
+
200
+			var err error
201
+			if s.Value[0], err = strconv.ParseInt(value1, 0, 64); err != nil {
202
+				return nil, nil, fmt.Errorf("parsing sample %s: %v", line, err)
203
+			}
204
+			if s.Value[1], err = strconv.ParseInt(value2, 0, 64); err != nil {
205
+				return nil, nil, fmt.Errorf("parsing sample %s: %v", line, err)
206
+			}
207
+
208
+			switch pType {
209
+			case "heap":
210
+				const javaHeapzSamplingRate = 524288 // 512K
211
+				s.NumLabel = map[string][]int64{"bytes": []int64{s.Value[1] / s.Value[0]}}
212
+				s.Value[0], s.Value[1] = scaleHeapSample(s.Value[0], s.Value[1], javaHeapzSamplingRate)
213
+			case "contention":
214
+				if period := p.Period; period != 0 {
215
+					s.Value[0] = s.Value[0] * p.Period
216
+					s.Value[1] = s.Value[1] * p.Period
217
+				}
218
+			}
219
+			p.Sample = append(p.Sample, s)
220
+		}
221
+		// Grab next line.
222
+		b = b[nextNewLine+1:]
223
+		nextNewLine = bytes.IndexByte(b, byte('\n'))
224
+	}
225
+	return b, locs, nil
226
+}
227
+
228
+// parseJavaLocations parses the location information in a java
229
+// profile and populates the Locations in a profile. It uses the
230
+// location addresses from the profile as both the ID of each
231
+// location.
232
+func parseJavaLocations(b []byte, locs map[uint64]*Location, p *Profile) error {
233
+	r := bytes.NewBuffer(b)
234
+	fns := make(map[string]*Function)
235
+	for {
236
+		line, err := r.ReadString('\n')
237
+		if err != nil {
238
+			if err != io.EOF {
239
+				return err
240
+			}
241
+			if line == "" {
242
+				break
243
+			}
244
+		}
245
+
246
+		if line = strings.TrimSpace(line); line == "" {
247
+			continue
248
+		}
249
+
250
+		jloc := javaLocationRx.FindStringSubmatch(line)
251
+		if len(jloc) != 3 {
252
+			continue
253
+		}
254
+		addr, err := strconv.ParseUint(jloc[1], 16, 64)
255
+		if err != nil {
256
+			return fmt.Errorf("parsing sample %s: %v", line, err)
257
+		}
258
+		loc := locs[addr]
259
+		if loc == nil {
260
+			// Unused/unseen
261
+			continue
262
+		}
263
+		var lineFunc, lineFile string
264
+		var lineNo int64
265
+
266
+		if fileLine := javaLocationFileLineRx.FindStringSubmatch(jloc[2]); len(fileLine) == 4 {
267
+			// Found a line of the form: "function (file:line)"
268
+			lineFunc, lineFile = fileLine[1], fileLine[2]
269
+			if n, err := strconv.ParseInt(fileLine[3], 10, 64); err == nil && n > 0 {
270
+				lineNo = n
271
+			}
272
+		} else if filePath := javaLocationPathRx.FindStringSubmatch(jloc[2]); len(filePath) == 3 {
273
+			// If there's not a file:line, it's a shared library path.
274
+			// The path isn't interesting, so just give the .so.
275
+			lineFunc, lineFile = filePath[1], filepath.Base(filePath[2])
276
+		} else if strings.Contains(jloc[2], "generated stub/JIT") {
277
+			lineFunc = "STUB"
278
+		} else {
279
+			// Treat whole line as the function name. This is used by the
280
+			// java agent for internal states such as "GC" or "VM".
281
+			lineFunc = jloc[2]
282
+		}
283
+		fn := fns[lineFunc]
284
+
285
+		if fn == nil {
286
+			fn = &Function{
287
+				Name:       lineFunc,
288
+				SystemName: lineFunc,
289
+				Filename:   lineFile,
290
+			}
291
+			fns[lineFunc] = fn
292
+			p.Function = append(p.Function, fn)
293
+		}
294
+		loc.Line = []Line{
295
+			{
296
+				Function: fn,
297
+				Line:     lineNo,
298
+			},
299
+		}
300
+		loc.Address = 0
301
+	}
302
+
303
+	p.remapLocationIDs()
304
+	p.remapFunctionIDs()
305
+	p.remapMappingIDs()
306
+
307
+	return nil
308
+}

+ 0
- 0
src/profile/legacy_profile.go Datei anzeigen


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.